From 74aa0bc6779af38018a03fd2cf4419fe85917904 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 07:31:45 +0200 Subject: Adding upstream version 2.9.4. Signed-off-by: Daniel Baumann --- src/providers/ad/ad_access.c | 548 +++ src/providers/ad/ad_access.h | 65 + src/providers/ad/ad_autofs.c | 50 + src/providers/ad/ad_cldap_ping.c | 768 +++ src/providers/ad/ad_common.c | 1753 +++++++ src/providers/ad/ad_common.h | 258 + src/providers/ad/ad_domain_info.c | 477 ++ src/providers/ad/ad_domain_info.h | 42 + src/providers/ad/ad_dyndns.c | 295 ++ src/providers/ad/ad_gpo.c | 5105 ++++++++++++++++++++ src/providers/ad/ad_gpo.h | 65 + src/providers/ad/ad_gpo_child.c | 821 ++++ src/providers/ad/ad_gpo_child_utils.c | 242 + src/providers/ad/ad_gpo_ndr.c | 526 ++ src/providers/ad/ad_id.c | 1547 ++++++ src/providers/ad/ad_id.h | 77 + src/providers/ad/ad_init.c | 716 +++ src/providers/ad/ad_machine_pw_renewal.c | 424 ++ src/providers/ad/ad_opts.c | 344 ++ src/providers/ad/ad_opts.h | 57 + src/providers/ad/ad_pac.c | 750 +++ src/providers/ad/ad_pac.h | 87 + src/providers/ad/ad_pac_common.c | 465 ++ src/providers/ad/ad_refresh.c | 240 + src/providers/ad/ad_resolver.c | 484 ++ src/providers/ad/ad_resolver.h | 41 + src/providers/ad/ad_srv.c | 496 ++ src/providers/ad/ad_srv.h | 78 + src/providers/ad/ad_subdomains.c | 2785 +++++++++++ src/providers/ad/ad_subdomains.h | 36 + src/providers/ad/ad_sudo.c | 56 + src/providers/backend.h | 228 + src/providers/be_dyndns.c | 1371 ++++++ src/providers/be_dyndns.h | 140 + src/providers/be_ptask.c | 548 +++ src/providers/be_ptask.h | 152 + src/providers/be_ptask_private.h | 47 + src/providers/be_refresh.c | 581 +++ src/providers/be_refresh.h | 99 + src/providers/data_provider.h | 274 ++ src/providers/data_provider/dp.c | 288 ++ src/providers/data_provider/dp.h | 228 + src/providers/data_provider/dp_builtin.c | 118 + src/providers/data_provider/dp_builtin.h | 50 + src/providers/data_provider/dp_client.c | 245 + src/providers/data_provider/dp_custom_data.h | 88 + src/providers/data_provider/dp_flags.h | 29 + src/providers/data_provider/dp_iface.h | 250 + src/providers/data_provider/dp_iface_backend.c | 59 + src/providers/data_provider/dp_iface_failover.c | 328 ++ src/providers/data_provider/dp_methods.c | 128 + src/providers/data_provider/dp_modules.c | 224 + src/providers/data_provider/dp_private.h | 139 + src/providers/data_provider/dp_reply_std.c | 150 + src/providers/data_provider/dp_request.c | 500 ++ src/providers/data_provider/dp_request.h | 86 + src/providers/data_provider/dp_resp_client.c | 275 ++ src/providers/data_provider/dp_target_auth.c | 345 ++ src/providers/data_provider/dp_target_autofs.c | 275 ++ src/providers/data_provider/dp_target_hostid.c | 126 + src/providers/data_provider/dp_target_id.c | 1084 +++++ src/providers/data_provider/dp_target_resolver.c | 148 + src/providers/data_provider/dp_target_subdomains.c | 122 + src/providers/data_provider/dp_target_sudo.c | 205 + src/providers/data_provider/dp_targets.c | 485 ++ src/providers/data_provider_be.c | 894 ++++ src/providers/data_provider_callbacks.c | 306 ++ src/providers/data_provider_fo.c | 945 ++++ src/providers/data_provider_opts.c | 482 ++ src/providers/data_provider_req.c | 59 + src/providers/data_provider_req.h | 54 + src/providers/fail_over.c | 1861 +++++++ src/providers/fail_over.h | 245 + src/providers/fail_over_srv.c | 719 +++ src/providers/fail_over_srv.h | 133 + src/providers/files/files_auth.c | 69 + src/providers/files/files_certmap.c | 183 + src/providers/files/files_id.c | 222 + src/providers/files/files_init.c | 261 + src/providers/files/files_ops.c | 1484 ++++++ src/providers/files/files_private.h | 100 + src/providers/ipa/ipa_access.c | 787 +++ src/providers/ipa/ipa_access.h | 76 + src/providers/ipa/ipa_auth.c | 478 ++ src/providers/ipa/ipa_auth.h | 42 + src/providers/ipa/ipa_autofs.c | 65 + src/providers/ipa/ipa_common.c | 1370 ++++++ src/providers/ipa/ipa_common.h | 326 ++ src/providers/ipa/ipa_config.c | 165 + src/providers/ipa/ipa_config.h | 55 + src/providers/ipa/ipa_deskprofile_config.c | 156 + src/providers/ipa/ipa_deskprofile_config.h | 45 + src/providers/ipa/ipa_deskprofile_private.h | 50 + src/providers/ipa/ipa_deskprofile_rules.c | 367 ++ src/providers/ipa/ipa_deskprofile_rules.h | 43 + src/providers/ipa/ipa_deskprofile_rules_util.c | 1147 +++++ src/providers/ipa/ipa_deskprofile_rules_util.h | 74 + src/providers/ipa/ipa_dn.c | 145 + src/providers/ipa/ipa_dn.h | 43 + src/providers/ipa/ipa_dyndns.c | 269 ++ src/providers/ipa/ipa_dyndns.h | 35 + src/providers/ipa/ipa_hbac_common.c | 748 +++ src/providers/ipa/ipa_hbac_hosts.c | 335 ++ src/providers/ipa/ipa_hbac_private.h | 132 + src/providers/ipa/ipa_hbac_rules.c | 313 ++ src/providers/ipa/ipa_hbac_rules.h | 41 + src/providers/ipa/ipa_hbac_services.c | 686 +++ src/providers/ipa/ipa_hbac_users.c | 369 ++ src/providers/ipa/ipa_hostid.c | 30 + src/providers/ipa/ipa_hosts.c | 365 ++ src/providers/ipa/ipa_hosts.h | 44 + src/providers/ipa/ipa_id.c | 1562 ++++++ src/providers/ipa/ipa_id.h | 159 + src/providers/ipa/ipa_idmap.c | 521 ++ src/providers/ipa/ipa_init.c | 960 ++++ src/providers/ipa/ipa_netgroups.c | 1056 ++++ src/providers/ipa/ipa_opts.c | 428 ++ src/providers/ipa/ipa_opts.h | 71 + src/providers/ipa/ipa_refresh.c | 220 + src/providers/ipa/ipa_rules_common.c | 455 ++ src/providers/ipa/ipa_rules_common.h | 89 + src/providers/ipa/ipa_s2n_exop.c | 3228 +++++++++++++ src/providers/ipa/ipa_selinux.c | 1698 +++++++ src/providers/ipa/ipa_selinux.h | 50 + src/providers/ipa/ipa_selinux_maps.c | 222 + src/providers/ipa/ipa_selinux_maps.h | 45 + src/providers/ipa/ipa_session.c | 861 ++++ src/providers/ipa/ipa_session.h | 54 + src/providers/ipa/ipa_srv.c | 224 + src/providers/ipa/ipa_srv.h | 48 + src/providers/ipa/ipa_subdomains.c | 3180 ++++++++++++ src/providers/ipa/ipa_subdomains.h | 177 + src/providers/ipa/ipa_subdomains_ext_groups.c | 1213 +++++ src/providers/ipa/ipa_subdomains_id.c | 1827 +++++++ src/providers/ipa/ipa_subdomains_passkey.c | 146 + src/providers/ipa/ipa_subdomains_passkey.h | 45 + src/providers/ipa/ipa_subdomains_server.c | 1215 +++++ src/providers/ipa/ipa_subdomains_utils.c | 100 + src/providers/ipa/ipa_sudo.c | 337 ++ src/providers/ipa/ipa_sudo.h | 134 + src/providers/ipa/ipa_sudo_async.c | 1141 +++++ src/providers/ipa/ipa_sudo_conversion.c | 1369 ++++++ src/providers/ipa/ipa_sudo_refresh.c | 470 ++ src/providers/ipa/ipa_utils.c | 63 + src/providers/ipa/ipa_views.c | 653 +++ src/providers/ipa/selinux_child.c | 422 ++ src/providers/krb5/krb5_access.c | 220 + src/providers/krb5/krb5_auth.c | 1356 ++++++ src/providers/krb5/krb5_auth.h | 158 + src/providers/krb5/krb5_ccache.c | 796 +++ src/providers/krb5/krb5_ccache.h | 73 + src/providers/krb5/krb5_child.c | 4247 ++++++++++++++++ src/providers/krb5/krb5_child_handler.c | 1022 ++++ src/providers/krb5/krb5_common.c | 1261 +++++ src/providers/krb5/krb5_common.h | 249 + .../krb5/krb5_delayed_online_authentication.c | 386 ++ src/providers/krb5/krb5_init.c | 229 + src/providers/krb5/krb5_init_shared.c | 105 + src/providers/krb5/krb5_init_shared.h | 29 + src/providers/krb5/krb5_keytab.c | 231 + src/providers/krb5/krb5_opts.c | 50 + src/providers/krb5/krb5_opts.h | 30 + src/providers/krb5/krb5_renew_tgt.c | 631 +++ src/providers/krb5/krb5_utils.c | 605 +++ src/providers/krb5/krb5_utils.h | 59 + src/providers/krb5/krb5_wait_queue.c | 373 ++ src/providers/ldap/ldap_access.c | 128 + src/providers/ldap/ldap_auth.c | 1641 +++++++ src/providers/ldap/ldap_auth.h | 49 + src/providers/ldap/ldap_child.c | 788 +++ src/providers/ldap/ldap_common.c | 891 ++++ src/providers/ldap/ldap_common.h | 486 ++ src/providers/ldap/ldap_id.c | 1955 ++++++++ src/providers/ldap/ldap_id_cleanup.c | 520 ++ src/providers/ldap/ldap_id_enum.c | 212 + src/providers/ldap/ldap_id_netgroup.c | 247 + src/providers/ldap/ldap_id_services.c | 308 ++ src/providers/ldap/ldap_id_subid.c | 255 + src/providers/ldap/ldap_init.c | 529 ++ src/providers/ldap/ldap_options.c | 827 ++++ src/providers/ldap/ldap_opts.c | 425 ++ src/providers/ldap/ldap_opts.h | 69 + src/providers/ldap/ldap_resolver_cleanup.c | 230 + src/providers/ldap/ldap_resolver_enum.c | 299 ++ src/providers/ldap/ldap_resolver_enum.h | 44 + src/providers/ldap/sdap.c | 2129 ++++++++ src/providers/ldap/sdap.h | 746 +++ src/providers/ldap/sdap_access.c | 2402 +++++++++ src/providers/ldap/sdap_access.h | 114 + src/providers/ldap/sdap_ad_groups.c | 69 + src/providers/ldap/sdap_async.c | 3169 ++++++++++++ src/providers/ldap/sdap_async.h | 455 ++ src/providers/ldap/sdap_async_ad.h | 59 + src/providers/ldap/sdap_async_autofs.c | 1479 ++++++ src/providers/ldap/sdap_async_connection.c | 2386 +++++++++ src/providers/ldap/sdap_async_enum.c | 773 +++ src/providers/ldap/sdap_async_enum.h | 49 + src/providers/ldap/sdap_async_groups.c | 2471 ++++++++++ src/providers/ldap/sdap_async_hosts.c | 209 + src/providers/ldap/sdap_async_initgroups.c | 3647 ++++++++++++++ src/providers/ldap/sdap_async_initgroups_ad.c | 1742 +++++++ src/providers/ldap/sdap_async_iphost.c | 640 +++ src/providers/ldap/sdap_async_ipnetwork.c | 625 +++ src/providers/ldap/sdap_async_nested_groups.c | 2997 ++++++++++++ src/providers/ldap/sdap_async_netgroups.c | 778 +++ src/providers/ldap/sdap_async_private.h | 196 + src/providers/ldap/sdap_async_resolver_enum.c | 318 ++ src/providers/ldap/sdap_async_resolver_enum.h | 36 + src/providers/ldap/sdap_async_services.c | 644 +++ src/providers/ldap/sdap_async_sudo.c | 698 +++ src/providers/ldap/sdap_async_sudo_hostinfo.c | 516 ++ src/providers/ldap/sdap_async_users.c | 1209 +++++ src/providers/ldap/sdap_autofs.c | 491 ++ src/providers/ldap/sdap_autofs.h | 62 + src/providers/ldap/sdap_certmap.c | 150 + src/providers/ldap/sdap_child_helpers.c | 515 ++ src/providers/ldap/sdap_domain.c | 232 + src/providers/ldap/sdap_dyndns.c | 713 +++ src/providers/ldap/sdap_dyndns.h | 49 + src/providers/ldap/sdap_fd_events.c | 357 ++ src/providers/ldap/sdap_hostid.c | 324 ++ src/providers/ldap/sdap_hostid.h | 40 + src/providers/ldap/sdap_id_op.c | 1054 ++++ src/providers/ldap/sdap_id_op.h | 76 + src/providers/ldap/sdap_idmap.c | 629 +++ src/providers/ldap/sdap_idmap.h | 63 + src/providers/ldap/sdap_iphost.c | 375 ++ src/providers/ldap/sdap_ipnetwork.c | 378 ++ src/providers/ldap/sdap_online_check.c | 244 + src/providers/ldap/sdap_ops.c | 553 +++ src/providers/ldap/sdap_ops.h | 99 + src/providers/ldap/sdap_range.c | 142 + src/providers/ldap/sdap_range.h | 34 + src/providers/ldap/sdap_refresh.c | 238 + src/providers/ldap/sdap_reinit.c | 335 ++ src/providers/ldap/sdap_sudo.c | 231 + src/providers/ldap/sdap_sudo.h | 106 + src/providers/ldap/sdap_sudo_refresh.c | 485 ++ src/providers/ldap/sdap_sudo_shared.c | 227 + src/providers/ldap/sdap_sudo_shared.h | 42 + src/providers/ldap/sdap_users.h | 42 + src/providers/ldap/sdap_utils.c | 235 + src/providers/proxy/proxy.h | 187 + src/providers/proxy/proxy_auth.c | 877 ++++ src/providers/proxy/proxy_certmap.c | 191 + src/providers/proxy/proxy_child.c | 606 +++ src/providers/proxy/proxy_client.c | 134 + src/providers/proxy/proxy_hosts.c | 768 +++ src/providers/proxy/proxy_id.c | 1962 ++++++++ src/providers/proxy/proxy_init.c | 519 ++ src/providers/proxy/proxy_ipnetworks.c | 628 +++ src/providers/proxy/proxy_netgroup.c | 206 + src/providers/proxy/proxy_services.c | 372 ++ src/providers/simple/simple_access.c | 346 ++ src/providers/simple/simple_access.h | 47 + src/providers/simple/simple_access_check.c | 847 ++++ src/providers/simple/simple_access_pvt.h | 43 + src/providers/sssd_be.exports | 4 + 258 files changed, 139201 insertions(+) create mode 100644 src/providers/ad/ad_access.c create mode 100644 src/providers/ad/ad_access.h create mode 100644 src/providers/ad/ad_autofs.c create mode 100644 src/providers/ad/ad_cldap_ping.c create mode 100644 src/providers/ad/ad_common.c create mode 100644 src/providers/ad/ad_common.h create mode 100644 src/providers/ad/ad_domain_info.c create mode 100644 src/providers/ad/ad_domain_info.h create mode 100644 src/providers/ad/ad_dyndns.c create mode 100644 src/providers/ad/ad_gpo.c create mode 100644 src/providers/ad/ad_gpo.h create mode 100644 src/providers/ad/ad_gpo_child.c create mode 100644 src/providers/ad/ad_gpo_child_utils.c create mode 100644 src/providers/ad/ad_gpo_ndr.c create mode 100644 src/providers/ad/ad_id.c create mode 100644 src/providers/ad/ad_id.h create mode 100644 src/providers/ad/ad_init.c create mode 100644 src/providers/ad/ad_machine_pw_renewal.c create mode 100644 src/providers/ad/ad_opts.c create mode 100644 src/providers/ad/ad_opts.h create mode 100644 src/providers/ad/ad_pac.c create mode 100644 src/providers/ad/ad_pac.h create mode 100644 src/providers/ad/ad_pac_common.c create mode 100644 src/providers/ad/ad_refresh.c create mode 100644 src/providers/ad/ad_resolver.c create mode 100644 src/providers/ad/ad_resolver.h create mode 100644 src/providers/ad/ad_srv.c create mode 100644 src/providers/ad/ad_srv.h create mode 100644 src/providers/ad/ad_subdomains.c create mode 100644 src/providers/ad/ad_subdomains.h create mode 100644 src/providers/ad/ad_sudo.c create mode 100644 src/providers/backend.h create mode 100644 src/providers/be_dyndns.c create mode 100644 src/providers/be_dyndns.h create mode 100644 src/providers/be_ptask.c create mode 100644 src/providers/be_ptask.h create mode 100644 src/providers/be_ptask_private.h create mode 100644 src/providers/be_refresh.c create mode 100644 src/providers/be_refresh.h create mode 100644 src/providers/data_provider.h create mode 100644 src/providers/data_provider/dp.c create mode 100644 src/providers/data_provider/dp.h create mode 100644 src/providers/data_provider/dp_builtin.c create mode 100644 src/providers/data_provider/dp_builtin.h create mode 100644 src/providers/data_provider/dp_client.c create mode 100644 src/providers/data_provider/dp_custom_data.h create mode 100644 src/providers/data_provider/dp_flags.h create mode 100644 src/providers/data_provider/dp_iface.h create mode 100644 src/providers/data_provider/dp_iface_backend.c create mode 100644 src/providers/data_provider/dp_iface_failover.c create mode 100644 src/providers/data_provider/dp_methods.c create mode 100644 src/providers/data_provider/dp_modules.c create mode 100644 src/providers/data_provider/dp_private.h create mode 100644 src/providers/data_provider/dp_reply_std.c create mode 100644 src/providers/data_provider/dp_request.c create mode 100644 src/providers/data_provider/dp_request.h create mode 100644 src/providers/data_provider/dp_resp_client.c create mode 100644 src/providers/data_provider/dp_target_auth.c create mode 100644 src/providers/data_provider/dp_target_autofs.c create mode 100644 src/providers/data_provider/dp_target_hostid.c create mode 100644 src/providers/data_provider/dp_target_id.c create mode 100644 src/providers/data_provider/dp_target_resolver.c create mode 100644 src/providers/data_provider/dp_target_subdomains.c create mode 100644 src/providers/data_provider/dp_target_sudo.c create mode 100644 src/providers/data_provider/dp_targets.c create mode 100644 src/providers/data_provider_be.c create mode 100644 src/providers/data_provider_callbacks.c create mode 100644 src/providers/data_provider_fo.c create mode 100644 src/providers/data_provider_opts.c create mode 100644 src/providers/data_provider_req.c create mode 100644 src/providers/data_provider_req.h create mode 100644 src/providers/fail_over.c create mode 100644 src/providers/fail_over.h create mode 100644 src/providers/fail_over_srv.c create mode 100644 src/providers/fail_over_srv.h create mode 100644 src/providers/files/files_auth.c create mode 100644 src/providers/files/files_certmap.c create mode 100644 src/providers/files/files_id.c create mode 100644 src/providers/files/files_init.c create mode 100644 src/providers/files/files_ops.c create mode 100644 src/providers/files/files_private.h create mode 100644 src/providers/ipa/ipa_access.c create mode 100644 src/providers/ipa/ipa_access.h create mode 100644 src/providers/ipa/ipa_auth.c create mode 100644 src/providers/ipa/ipa_auth.h create mode 100644 src/providers/ipa/ipa_autofs.c create mode 100644 src/providers/ipa/ipa_common.c create mode 100644 src/providers/ipa/ipa_common.h create mode 100644 src/providers/ipa/ipa_config.c create mode 100644 src/providers/ipa/ipa_config.h create mode 100644 src/providers/ipa/ipa_deskprofile_config.c create mode 100644 src/providers/ipa/ipa_deskprofile_config.h create mode 100644 src/providers/ipa/ipa_deskprofile_private.h create mode 100644 src/providers/ipa/ipa_deskprofile_rules.c create mode 100644 src/providers/ipa/ipa_deskprofile_rules.h create mode 100644 src/providers/ipa/ipa_deskprofile_rules_util.c create mode 100644 src/providers/ipa/ipa_deskprofile_rules_util.h create mode 100644 src/providers/ipa/ipa_dn.c create mode 100644 src/providers/ipa/ipa_dn.h create mode 100644 src/providers/ipa/ipa_dyndns.c create mode 100644 src/providers/ipa/ipa_dyndns.h create mode 100644 src/providers/ipa/ipa_hbac_common.c create mode 100644 src/providers/ipa/ipa_hbac_hosts.c create mode 100644 src/providers/ipa/ipa_hbac_private.h create mode 100644 src/providers/ipa/ipa_hbac_rules.c create mode 100644 src/providers/ipa/ipa_hbac_rules.h create mode 100644 src/providers/ipa/ipa_hbac_services.c create mode 100644 src/providers/ipa/ipa_hbac_users.c create mode 100644 src/providers/ipa/ipa_hostid.c create mode 100644 src/providers/ipa/ipa_hosts.c create mode 100644 src/providers/ipa/ipa_hosts.h create mode 100644 src/providers/ipa/ipa_id.c create mode 100644 src/providers/ipa/ipa_id.h create mode 100644 src/providers/ipa/ipa_idmap.c create mode 100644 src/providers/ipa/ipa_init.c create mode 100644 src/providers/ipa/ipa_netgroups.c create mode 100644 src/providers/ipa/ipa_opts.c create mode 100644 src/providers/ipa/ipa_opts.h create mode 100644 src/providers/ipa/ipa_refresh.c create mode 100644 src/providers/ipa/ipa_rules_common.c create mode 100644 src/providers/ipa/ipa_rules_common.h create mode 100644 src/providers/ipa/ipa_s2n_exop.c create mode 100644 src/providers/ipa/ipa_selinux.c create mode 100644 src/providers/ipa/ipa_selinux.h create mode 100644 src/providers/ipa/ipa_selinux_maps.c create mode 100644 src/providers/ipa/ipa_selinux_maps.h create mode 100644 src/providers/ipa/ipa_session.c create mode 100644 src/providers/ipa/ipa_session.h create mode 100644 src/providers/ipa/ipa_srv.c create mode 100644 src/providers/ipa/ipa_srv.h create mode 100644 src/providers/ipa/ipa_subdomains.c create mode 100644 src/providers/ipa/ipa_subdomains.h create mode 100644 src/providers/ipa/ipa_subdomains_ext_groups.c create mode 100644 src/providers/ipa/ipa_subdomains_id.c create mode 100644 src/providers/ipa/ipa_subdomains_passkey.c create mode 100644 src/providers/ipa/ipa_subdomains_passkey.h create mode 100644 src/providers/ipa/ipa_subdomains_server.c create mode 100644 src/providers/ipa/ipa_subdomains_utils.c create mode 100644 src/providers/ipa/ipa_sudo.c create mode 100644 src/providers/ipa/ipa_sudo.h create mode 100644 src/providers/ipa/ipa_sudo_async.c create mode 100644 src/providers/ipa/ipa_sudo_conversion.c create mode 100644 src/providers/ipa/ipa_sudo_refresh.c create mode 100644 src/providers/ipa/ipa_utils.c create mode 100644 src/providers/ipa/ipa_views.c create mode 100644 src/providers/ipa/selinux_child.c create mode 100644 src/providers/krb5/krb5_access.c create mode 100644 src/providers/krb5/krb5_auth.c create mode 100644 src/providers/krb5/krb5_auth.h create mode 100644 src/providers/krb5/krb5_ccache.c create mode 100644 src/providers/krb5/krb5_ccache.h create mode 100644 src/providers/krb5/krb5_child.c create mode 100644 src/providers/krb5/krb5_child_handler.c create mode 100644 src/providers/krb5/krb5_common.c create mode 100644 src/providers/krb5/krb5_common.h create mode 100644 src/providers/krb5/krb5_delayed_online_authentication.c create mode 100644 src/providers/krb5/krb5_init.c create mode 100644 src/providers/krb5/krb5_init_shared.c create mode 100644 src/providers/krb5/krb5_init_shared.h create mode 100644 src/providers/krb5/krb5_keytab.c create mode 100644 src/providers/krb5/krb5_opts.c create mode 100644 src/providers/krb5/krb5_opts.h create mode 100644 src/providers/krb5/krb5_renew_tgt.c create mode 100644 src/providers/krb5/krb5_utils.c create mode 100644 src/providers/krb5/krb5_utils.h create mode 100644 src/providers/krb5/krb5_wait_queue.c create mode 100644 src/providers/ldap/ldap_access.c create mode 100644 src/providers/ldap/ldap_auth.c create mode 100644 src/providers/ldap/ldap_auth.h create mode 100644 src/providers/ldap/ldap_child.c create mode 100644 src/providers/ldap/ldap_common.c create mode 100644 src/providers/ldap/ldap_common.h create mode 100644 src/providers/ldap/ldap_id.c create mode 100644 src/providers/ldap/ldap_id_cleanup.c create mode 100644 src/providers/ldap/ldap_id_enum.c create mode 100644 src/providers/ldap/ldap_id_netgroup.c create mode 100644 src/providers/ldap/ldap_id_services.c create mode 100644 src/providers/ldap/ldap_id_subid.c create mode 100644 src/providers/ldap/ldap_init.c create mode 100644 src/providers/ldap/ldap_options.c create mode 100644 src/providers/ldap/ldap_opts.c create mode 100644 src/providers/ldap/ldap_opts.h create mode 100644 src/providers/ldap/ldap_resolver_cleanup.c create mode 100644 src/providers/ldap/ldap_resolver_enum.c create mode 100644 src/providers/ldap/ldap_resolver_enum.h create mode 100644 src/providers/ldap/sdap.c create mode 100644 src/providers/ldap/sdap.h create mode 100644 src/providers/ldap/sdap_access.c create mode 100644 src/providers/ldap/sdap_access.h create mode 100644 src/providers/ldap/sdap_ad_groups.c create mode 100644 src/providers/ldap/sdap_async.c create mode 100644 src/providers/ldap/sdap_async.h create mode 100644 src/providers/ldap/sdap_async_ad.h create mode 100644 src/providers/ldap/sdap_async_autofs.c create mode 100644 src/providers/ldap/sdap_async_connection.c create mode 100644 src/providers/ldap/sdap_async_enum.c create mode 100644 src/providers/ldap/sdap_async_enum.h create mode 100644 src/providers/ldap/sdap_async_groups.c create mode 100644 src/providers/ldap/sdap_async_hosts.c create mode 100644 src/providers/ldap/sdap_async_initgroups.c create mode 100644 src/providers/ldap/sdap_async_initgroups_ad.c create mode 100644 src/providers/ldap/sdap_async_iphost.c create mode 100644 src/providers/ldap/sdap_async_ipnetwork.c create mode 100644 src/providers/ldap/sdap_async_nested_groups.c create mode 100644 src/providers/ldap/sdap_async_netgroups.c create mode 100644 src/providers/ldap/sdap_async_private.h create mode 100644 src/providers/ldap/sdap_async_resolver_enum.c create mode 100644 src/providers/ldap/sdap_async_resolver_enum.h create mode 100644 src/providers/ldap/sdap_async_services.c create mode 100644 src/providers/ldap/sdap_async_sudo.c create mode 100644 src/providers/ldap/sdap_async_sudo_hostinfo.c create mode 100644 src/providers/ldap/sdap_async_users.c create mode 100644 src/providers/ldap/sdap_autofs.c create mode 100644 src/providers/ldap/sdap_autofs.h create mode 100644 src/providers/ldap/sdap_certmap.c create mode 100644 src/providers/ldap/sdap_child_helpers.c create mode 100644 src/providers/ldap/sdap_domain.c create mode 100644 src/providers/ldap/sdap_dyndns.c create mode 100644 src/providers/ldap/sdap_dyndns.h create mode 100644 src/providers/ldap/sdap_fd_events.c create mode 100644 src/providers/ldap/sdap_hostid.c create mode 100644 src/providers/ldap/sdap_hostid.h create mode 100644 src/providers/ldap/sdap_id_op.c create mode 100644 src/providers/ldap/sdap_id_op.h create mode 100644 src/providers/ldap/sdap_idmap.c create mode 100644 src/providers/ldap/sdap_idmap.h create mode 100644 src/providers/ldap/sdap_iphost.c create mode 100644 src/providers/ldap/sdap_ipnetwork.c create mode 100644 src/providers/ldap/sdap_online_check.c create mode 100644 src/providers/ldap/sdap_ops.c create mode 100644 src/providers/ldap/sdap_ops.h create mode 100644 src/providers/ldap/sdap_range.c create mode 100644 src/providers/ldap/sdap_range.h create mode 100644 src/providers/ldap/sdap_refresh.c create mode 100644 src/providers/ldap/sdap_reinit.c create mode 100644 src/providers/ldap/sdap_sudo.c create mode 100644 src/providers/ldap/sdap_sudo.h create mode 100644 src/providers/ldap/sdap_sudo_refresh.c create mode 100644 src/providers/ldap/sdap_sudo_shared.c create mode 100644 src/providers/ldap/sdap_sudo_shared.h create mode 100644 src/providers/ldap/sdap_users.h create mode 100644 src/providers/ldap/sdap_utils.c create mode 100644 src/providers/proxy/proxy.h create mode 100644 src/providers/proxy/proxy_auth.c create mode 100644 src/providers/proxy/proxy_certmap.c create mode 100644 src/providers/proxy/proxy_child.c create mode 100644 src/providers/proxy/proxy_client.c create mode 100644 src/providers/proxy/proxy_hosts.c create mode 100644 src/providers/proxy/proxy_id.c create mode 100644 src/providers/proxy/proxy_init.c create mode 100644 src/providers/proxy/proxy_ipnetworks.c create mode 100644 src/providers/proxy/proxy_netgroup.c create mode 100644 src/providers/proxy/proxy_services.c create mode 100644 src/providers/simple/simple_access.c create mode 100644 src/providers/simple/simple_access.h create mode 100644 src/providers/simple/simple_access_check.c create mode 100644 src/providers/simple/simple_access_pvt.h create mode 100644 src/providers/sssd_be.exports (limited to 'src/providers') diff --git a/src/providers/ad/ad_access.c b/src/providers/ad/ad_access.c new file mode 100644 index 0000000..378d277 --- /dev/null +++ b/src/providers/ad/ad_access.c @@ -0,0 +1,548 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "src/util/util.h" +#include "src/providers/data_provider.h" +#include "src/providers/backend.h" +#include "src/providers/ad/ad_access.h" +#include "providers/ad/ad_gpo.h" +#include "src/providers/ad/ad_common.h" +#include "src/providers/ldap/sdap_access.h" + +/* + * More advanced format can be used to restrict the filter to a specific + * domain or a specific forest. This format is KEYWORD:NAME:FILTER + * + * KEYWORD can be one of DOM or FOREST + * KEYWORD can be missing + * NAME is a label. + * - if KEYWORD equals DOM or missing completely, the filter is applied + * for users from domain named NAME only + * - if KEYWORD equals FOREST, the filter is applied on users from + * forest named NAME only + * examples of valid filters are: + * apply filter on domain called dom1 only: + * dom1:(memberOf=cn=admins,ou=groups,dc=dom1,dc=com) + * apply filter on domain called dom2 only: + * DOM:dom2:(memberOf=cn=admins,ou=groups,dc=dom2,dc=com) + * apply filter on forest called EXAMPLE.COM only: + * FOREST:EXAMPLE.COM:(memberOf=cn=admins,ou=groups,dc=example,dc=com) + * + * If any of the extended formats are used, the filter MUST be enclosed + * already. + */ + +/* From least specific */ +#define AD_FILTER_GENERIC 0x01 +#define AD_FILTER_FOREST 0x02 +#define AD_FILTER_DOMAIN 0x04 + +#define KW_FOREST "FOREST" +#define KW_DOMAIN "DOM" + +/* parse filter in the format domain_name:filter */ +static errno_t +parse_sub_filter(TALLOC_CTX *mem_ctx, const char *full_filter, + char **filter, char **sub_name, int *flags, + const int flagconst) +{ + char *specdelim; + + specdelim = strchr(full_filter, ':'); + if (specdelim == NULL) return EINVAL; + + /* Make sure the filter is already enclosed in brackets */ + if (*(specdelim+1) != '(') return EINVAL; + + *sub_name = talloc_strndup(mem_ctx, full_filter, specdelim - full_filter); + *filter = talloc_strdup(mem_ctx, specdelim+1); + if (*sub_name == NULL || *filter == NULL) return ENOMEM; + + *flags = flagconst; + return EOK; +} + +static inline errno_t +parse_dom_filter(TALLOC_CTX *mem_ctx, const char *dom_filter, + char **filter, char **domname, int *flags) +{ + return parse_sub_filter(mem_ctx, dom_filter, filter, domname, + flags, AD_FILTER_DOMAIN); +} + +static inline errno_t +parse_forest_filter(TALLOC_CTX *mem_ctx, const char *forest_filter, + char **filter, char **forest_name, int *flags) +{ + return parse_sub_filter(mem_ctx, forest_filter, filter, forest_name, + flags, AD_FILTER_FOREST); +} + + +static errno_t +parse_filter(TALLOC_CTX *mem_ctx, const char *full_filter, + char **filter, char **spec, int *flags) +{ + char *kwdelim, *specdelim; + + if (filter == NULL || spec == NULL || flags == NULL) return EINVAL; + + kwdelim = strchr(full_filter, ':'); + if (kwdelim != NULL) { + specdelim = strchr(kwdelim+1, ':'); + + if (specdelim == NULL) { + /* There is a single keyword. Treat it as a domain name */ + return parse_dom_filter(mem_ctx, full_filter, filter, spec, flags); + } else if (strncmp(full_filter, "DOM", kwdelim-full_filter) == 0) { + /* The format must be DOM:domain_name:filter */ + if (specdelim && specdelim-kwdelim <= 1) { + /* Check if there is some domain_name */ + return EINVAL; + } + + return parse_dom_filter(mem_ctx, kwdelim + 1, filter, spec, flags); + } else if (strncmp(full_filter, "FOREST", kwdelim-full_filter) == 0) { + /* The format must be FOREST:forest_name:filter */ + if (specdelim && specdelim-kwdelim <= 1) { + /* Check if there is some domain_name */ + return EINVAL; + } + + return parse_forest_filter(mem_ctx, kwdelim + 1, + filter, spec, flags); + } + + /* Malformed option */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Keyword in filter [%s] did not match expected format\n", + full_filter); + return EINVAL; + } + + /* No keyword. Easy. */ + *filter = talloc_strdup(mem_ctx, full_filter); + if (*filter == NULL) return ENOMEM; + + *spec = NULL; + *flags = AD_FILTER_GENERIC; + return EOK; +} + +static errno_t +ad_parse_access_filter(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + const char *filter_list, + char **_filter) +{ + char **filters; + int nfilters; + errno_t ret; + char *best_match; + int best_flags; + char *filter; + char *spec; + int flags; + TALLOC_CTX *tmp_ctx; + int i = 0; + + if (_filter == NULL) return EINVAL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + if (filter_list == NULL) { + *_filter = NULL; + ret = EOK; + goto done; + } + + ret = split_on_separator(tmp_ctx, filter_list, '?', true, true, + &filters, &nfilters); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse the list of ad_access_filters\n"); + goto done; + } + + best_match = NULL; + best_flags = 0; + for (i=0; i < nfilters; i++) { + ret = parse_filter(tmp_ctx, filters[i], &filter, &spec, &flags); + if (ret != EOK) { + /* Skip the faulty filter. At worst, the user won't be + * allowed access */ + DEBUG(SSSDBG_MINOR_FAILURE, "Access filter [%s] could not be " + "parsed, skipping\n", filters[i]); + continue; + } + + if (flags & AD_FILTER_DOMAIN && strcasecmp(spec, dom->name) != 0) { + /* If the filter specifies a domain, it must match the + * domain the user comes from + */ + continue; + } + + if (flags & AD_FILTER_FOREST && strcasecmp(spec, dom->forest) != 0) { + /* If the filter specifies a forest, it must match the + * forest the user comes from + */ + continue; + } + + if (flags > best_flags) { + best_flags = flags; + best_match = filter; + } + } + + ret = EOK; + /* Make sure the result is enclosed in brackets */ + *_filter = sdap_get_access_filter(mem_ctx, best_match); +done: + talloc_free(tmp_ctx); + return ret; +} + +struct ad_access_state { + struct tevent_context *ev; + struct ad_access_ctx *ctx; + struct pam_data *pd; + struct be_ctx *be_ctx; + struct sss_domain_info *domain; + + char *filter; + struct sdap_id_conn_ctx **clist; + int cindex; +}; + +static errno_t +ad_sdap_access_step(struct tevent_req *req, struct sdap_id_conn_ctx *conn); +static void +ad_sdap_access_done(struct tevent_req *req); + +static struct tevent_req * +ad_access_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + struct ad_access_ctx *ctx, + struct pam_data *pd) +{ + struct tevent_req *req; + struct ad_access_state *state; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ad_access_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ctx = ctx; + state->pd = pd; + state->be_ctx = be_ctx; + state->domain = domain; + + ret = ad_parse_access_filter(state, domain, ctx->sdap_access_ctx->filter, + &state->filter); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine the best filter\n"); + ret = ERR_ACCESS_DENIED; + goto done; + } + + state->clist = ad_gc_conn_list(state, ctx->ad_id_ctx, domain); + if (state->clist == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ad_sdap_access_step(req, state->clist[state->cindex]); + if (ret != EOK) { + goto done; + } + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + + tevent_req_post(req, ev); + } + return req; +} + +static errno_t +ad_sdap_access_step(struct tevent_req *req, struct sdap_id_conn_ctx *conn) +{ + struct tevent_req *subreq; + struct ad_access_state *state; + struct sdap_access_ctx *req_ctx; + + state = tevent_req_data(req, struct ad_access_state); + + req_ctx = talloc(state, struct sdap_access_ctx); + if (req_ctx == NULL) { + return ENOMEM; + } + req_ctx->id_ctx = state->ctx->sdap_access_ctx->id_ctx; + req_ctx->filter = state->filter; + memcpy(&req_ctx->access_rule, + state->ctx->sdap_access_ctx->access_rule, + sizeof(int) * LDAP_ACCESS_LAST); + + subreq = sdap_access_send(state, state->ev, state->be_ctx, + state->domain, req_ctx, + conn, state->pd); + if (subreq == NULL) { + talloc_free(req_ctx); + return ENOMEM; + } + tevent_req_set_callback(subreq, ad_sdap_access_done, req); + return EOK; +} + +static void +ad_gpo_access_done(struct tevent_req *subreq); + +static void +ad_sdap_access_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_access_state *state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_access_state); + + ret = sdap_access_recv(subreq); + talloc_zfree(subreq); + + if (ret != EOK) { + switch (ret) { + case ERR_ACCOUNT_EXPIRED: + tevent_req_error(req, ret); + return; + + case ERR_ACCESS_DENIED: + /* Retry on ACCESS_DENIED, too, to make sure that we don't + * miss out any attributes not present in GC + * FIXME - this is slow. We should retry only if GC failed + * and LDAP succeeded after the first ACCESS_DENIED + */ + break; + + default: + break; + } + + /* If possible, retry with LDAP */ + state->cindex++; + if (state->clist[state->cindex] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Error retrieving access check result: %s\n", + sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = ad_sdap_access_step(req, state->clist[state->cindex]); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Another check in progress */ + + return; + } + + switch (state->ctx->gpo_access_control_mode) { + case GPO_ACCESS_CONTROL_DISABLED: + /* do not evaluate gpos; mark request done */ + tevent_req_done(req); + return; + case GPO_ACCESS_CONTROL_PERMISSIVE: + case GPO_ACCESS_CONTROL_ENFORCING: + /* continue on to evaluate gpos */ + break; + default: + tevent_req_error(req, EINVAL); + return; + } + + subreq = ad_gpo_access_send(state, + state->be_ctx->ev, + state->domain, + state->ctx, + state->pd->user, + state->pd->service); + + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ad_gpo_access_done, req); + +} + +static void +ad_gpo_access_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_access_state *state; + errno_t ret; + enum gpo_access_control_mode mode; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_access_state); + mode = state->ctx->gpo_access_control_mode; + + ret = ad_gpo_access_recv(subreq); + talloc_zfree(subreq); + + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "GPO-based access control successful.\n"); + tevent_req_done(req); + } else { + DEBUG(SSSDBG_OP_FAILURE, "GPO-based access control failed.\n"); + if (mode == GPO_ACCESS_CONTROL_ENFORCING) { + tevent_req_error(req, ret); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Ignoring error: [%d](%s); GPO-based access control failed, " + "but GPO is not in enforcing mode.\n", + ret, sss_strerror(ret)); + sss_log_ext(SSS_LOG_WARNING, LOG_AUTHPRIV, "Warning: user would " + "have been denied GPO-based logon access if the " + "ad_gpo_access_control option were set to enforcing mode."); + tevent_req_done(req); + } + } +} + +static errno_t +ad_access_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ad_pam_access_handler_state { + struct pam_data *pd; +}; + +static void ad_pam_access_handler_done(struct tevent_req *subreq); + +struct tevent_req * +ad_pam_access_handler_send(TALLOC_CTX *mem_ctx, + struct ad_access_ctx *access_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct ad_pam_access_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct ad_pam_access_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + + subreq = ad_access_send(state, params->ev, params->be_ctx, + params->domain, access_ctx, pd); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, ad_pam_access_handler_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ad_pam_access_handler_done(struct tevent_req *subreq) +{ + struct ad_pam_access_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_pam_access_handler_state); + + ret = ad_access_recv(subreq); + talloc_free(subreq); + switch (ret) { + case EOK: + state->pd->pam_status = PAM_SUCCESS; + break; + case ERR_ACCESS_DENIED: + state->pd->pam_status = PAM_PERM_DENIED; + break; + case ERR_ACCOUNT_EXPIRED: + state->pd->pam_status = PAM_ACCT_EXPIRED; + break; + default: + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +ad_pam_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct ad_pam_access_handler_state *state = NULL; + + state = tevent_req_data(req, struct ad_pam_access_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/ad/ad_access.h b/src/providers/ad/ad_access.h new file mode 100644 index 0000000..34d5597 --- /dev/null +++ b/src/providers/ad/ad_access.h @@ -0,0 +1,65 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef AD_ACCESS_H_ +#define AD_ACCESS_H_ + +#include "providers/data_provider.h" + +struct ad_access_ctx { + struct dp_option *ad_options; + struct sdap_access_ctx *sdap_access_ctx; + struct ad_id_ctx *ad_id_ctx; + /* supported GPO access control modes */ + enum gpo_access_control_mode { + GPO_ACCESS_CONTROL_DISABLED, + GPO_ACCESS_CONTROL_PERMISSIVE, + GPO_ACCESS_CONTROL_ENFORCING + } gpo_access_control_mode; + int gpo_cache_timeout; + /* supported GPO map options */ + enum gpo_map_type { + GPO_MAP_INTERACTIVE = 0, + GPO_MAP_REMOTE_INTERACTIVE, + GPO_MAP_NETWORK, + GPO_MAP_BATCH, + GPO_MAP_SERVICE, + GPO_MAP_PERMIT, + GPO_MAP_DENY, + GPO_MAP_NUM_OPTS + } gpo_map_type; + hash_table_t *gpo_map_options_table; + enum gpo_map_type gpo_default_right; +}; + +struct tevent_req * +ad_pam_access_handler_send(TALLOC_CTX *mem_ctx, + struct ad_access_ctx *access_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +ad_pam_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +#endif /* AD_ACCESS_H_ */ diff --git a/src/providers/ad/ad_autofs.c b/src/providers/ad/ad_autofs.c new file mode 100644 index 0000000..c1d7219 --- /dev/null +++ b/src/providers/ad/ad_autofs.c @@ -0,0 +1,50 @@ +/* + SSSD + + AD autofs Provider Initialization functions + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ad/ad_common.h" +#include "providers/ldap/sdap_autofs.h" + +errno_t ad_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + int ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing autofs AD back end\n"); + + ret = sdap_autofs_init(mem_ctx, be_ctx, id_ctx->sdap_id_ctx, dp_methods); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD autofs [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = ad_get_autofs_options(id_ctx->ad_options, be_ctx->cdb, + be_ctx->conf_path); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD autofs [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} diff --git a/src/providers/ad/ad_cldap_ping.c b/src/providers/ad/ad_cldap_ping.c new file mode 100644 index 0000000..8d14c7e --- /dev/null +++ b/src/providers/ad/ad_cldap_ping.c @@ -0,0 +1,768 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2020 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "util/sss_ldap.h" +#include "resolv/async_resolv.h" +#include "providers/backend.h" +#include "providers/ad/ad_srv.h" +#include "providers/ad/ad_common.h" +#include "providers/fail_over.h" +#include "providers/fail_over_srv.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async.h" +#include "db/sysdb.h" + +#ifdef HAVE_LDAP_IS_LDAPC_URL +#define AD_PING_PROTOCOL "cldap" +#else +#define AD_PING_PROTOCOL "ldap" +#endif + +struct ad_cldap_ping_dc_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct be_resolv_ctx *be_res; + struct fo_server_info *dc; + struct sdap_handle *sh; + const char *ad_domain; + + char *site; + char *forest; +}; + +static void ad_cldap_ping_dc_connect_done(struct tevent_req *subreq); +static void ad_cldap_ping_dc_done(struct tevent_req *subreq); + +static struct tevent_req *ad_cldap_ping_dc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct be_resolv_ctx *be_res, + enum host_database *host_db, + struct fo_server_info *dc, + const char *ad_domain) +{ + struct ad_cldap_ping_dc_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ad_cldap_ping_dc_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->be_res = be_res; + state->dc = dc; + state->ad_domain = ad_domain; + + subreq = sdap_connect_host_send(state, ev, opts, be_res->resolv, + be_res->family_order, host_db, + AD_PING_PROTOCOL, dc->host, dc->port, + false); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ad_cldap_ping_dc_connect_done, req); + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void ad_cldap_ping_dc_connect_done(struct tevent_req *subreq) +{ + static const char *attrs[] = {AD_AT_NETLOGON, NULL}; + struct ad_cldap_ping_dc_state *state; + struct tevent_req *req; + char *ntver; + char *filter; + int timeout; + errno_t ret; + div_t timeout_int; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_cldap_ping_dc_state); + + ret = sdap_connect_host_recv(state, subreq, &state->sh); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ntver = sss_ldap_encode_ndr_uint32(state, NETLOGON_NT_VERSION_5EX | + NETLOGON_NT_VERSION_WITH_CLOSEST_SITE); + if (ntver == NULL) { + ret = ENOMEM; + goto done; + } + + filter = talloc_asprintf(state, "(&(%s=%s)(%s=%s))", AD_AT_DNS_DOMAIN, + state->ad_domain, AD_AT_NT_VERSION, ntver); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + /* DP_RES_OPT_RESOLVER_SERVER_TIMEOUT is in milli-seconds and + * sdap_get_generic_send() expects seconds */ + timeout_int = div(dp_opt_get_int(state->be_res->opts, + DP_RES_OPT_RESOLVER_SERVER_TIMEOUT), + 1000); + timeout = (timeout_int.quot > 0) ? timeout_int.quot : 1; + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, "", + LDAP_SCOPE_BASE, filter, attrs, NULL, + 0, timeout, false); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ad_cldap_ping_dc_done, req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } +} + +static void ad_cldap_ping_dc_done(struct tevent_req *subreq) +{ + struct ad_cldap_ping_dc_state *state; + struct tevent_req *req; + struct sysdb_attrs **reply; + size_t reply_count; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_cldap_ping_dc_state); + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + + talloc_zfree(subreq); + talloc_zfree(state->sh); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "%s:%d: unable to get netlogon information\n", + state->dc->host, state->dc->port); + goto done; + } + + if (reply_count == 0) { + DEBUG(SSSDBG_OP_FAILURE, "%s:%d: no netlogon information available\n", + state->dc->host, state->dc->port); + ret = ENOENT; + goto done; + } + + ret = netlogon_get_domain_info(state, reply[0], true, NULL, &state->site, + &state->forest); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "%s:%d: unable to retrieve site name [%d]: %s\n", + state->dc->host, state->dc->port, ret, sss_strerror(ret)); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "%s:%d: found site (%s) and forest (%s)\n", + state->dc->host, state->dc->port, state->site, state->forest); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ad_cldap_ping_dc_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_site, + const char **_forest) +{ + struct ad_cldap_ping_dc_state *state = NULL; + state = tevent_req_data(req, struct ad_cldap_ping_dc_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_site = talloc_steal(mem_ctx, state->site); + *_forest = talloc_steal(mem_ctx, state->forest); + + return EOK; +} + +struct ad_cldap_ping_parallel_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct be_resolv_ctx *be_res; + enum host_database *host_db; + const char *ad_domain; + struct fo_server_info *dc_list; + size_t dc_count; + + TALLOC_CTX *reqs_ctx; + struct tevent_timer *te; + int active_requests; + size_t next_dc; + int batch; + + const char *site; + const char *forest; +}; + +static void ad_cldap_ping_parallel_batch(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *data); +static void ad_cldap_ping_parallel_done(struct tevent_req *subreq); + +static struct tevent_req * +ad_cldap_ping_parallel_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct be_resolv_ctx *be_res, + enum host_database *host_db, + struct fo_server_info *dc_list, + size_t dc_count, + const char *ad_domain) +{ + struct ad_cldap_ping_parallel_state *state; + struct tevent_req *req; + struct timeval tv = {0, 0}; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ad_cldap_ping_parallel_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->be_res = be_res; + state->host_db = host_db; + state->ad_domain = ad_domain; + state->dc_list = dc_list; + state->dc_count = dc_count; + + state->reqs_ctx = talloc_new(state); + if (state->reqs_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + state->next_dc = 0; + state->batch = 1; + ad_cldap_ping_parallel_batch(ev, NULL, tv, req); + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void ad_cldap_ping_parallel_batch(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *data) +{ + struct ad_cldap_ping_parallel_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + uint32_t delay; + size_t limit; + size_t i; + + req = talloc_get_type(data, struct tevent_req); + state = tevent_req_data(req, struct ad_cldap_ping_parallel_state); + + state->te = NULL; + + /* Issue three batches in total to avoid pinging too many domain controllers + * if not necessary. The first batch (5 pings) is issued immediately and we + * will wait 400ms for it to finish. If we don't get a reply in time we + * issue next batch (5 pings) and wait 200ms. If we still have no reply, + * we contact remaining domain controllers. + * + * This follows algorithm described at section 5.4.5.3 of MS-DISO: + * https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/WinArchive/%5bMS-DISO%5d.pdf + */ + switch (state->batch) { + case 1: + case 2: + limit = MIN(state->dc_count, 5 + state->next_dc); + delay = 400000 / state->batch; + break; + default: + limit = state->dc_count; + delay = 0; + } + + for (i = state->next_dc; i < limit; i++) { + DEBUG(SSSDBG_TRACE_ALL, "Batch %d: %s:%d\n", state->batch, + state->dc_list[i].host, state->dc_list[i].port); + } + + for (; state->next_dc < limit; state->next_dc++) { + subreq = ad_cldap_ping_dc_send(state->reqs_ctx, ev, state->opts, + state->be_res, state->host_db, + &state->dc_list[state->next_dc], + state->ad_domain); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to create new ping request\n"); + goto fail; + } + + state->active_requests++; + tevent_req_set_callback(subreq, ad_cldap_ping_parallel_done, req); + } + + state->batch++; + if (delay > 0) { + tv = tevent_timeval_current_ofs(0, delay); + state->te = tevent_add_timer(ev, state->reqs_ctx, tv, + ad_cldap_ping_parallel_batch, req); + if (state->te == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to schedule next batch!\n"); + goto fail; + } + } + + return; + +fail: + if (state->active_requests == 0) { + tevent_req_error(req, ENOMEM); + if (state->batch == 1) { + tevent_req_post(req, ev); + } + } +} + +static void ad_cldap_ping_parallel_done(struct tevent_req *subreq) +{ + struct ad_cldap_ping_parallel_state *state; + struct timeval tv = {0, 0}; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_cldap_ping_parallel_state); + + ret = ad_cldap_ping_dc_recv(state, subreq, &state->site, &state->forest); + talloc_zfree(subreq); + state->active_requests--; + + if (ret == EOK) { + /* We have the answer. Terminate other attempts and finish. */ + talloc_zfree(state->reqs_ctx); + tevent_req_done(req); + } else if (state->active_requests == 0) { + /* There are still servers to try, don't wait for the timer. */ + if (state->next_dc < state->dc_count) { + talloc_zfree(state->te); + ad_cldap_ping_parallel_batch(state->ev, NULL, tv, req); + return; + } + /* There is no available server. */ + tevent_req_error(req, ENOENT); + } + + /* Wait for another request to finish. */ +} + +static errno_t ad_cldap_ping_parallel_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_site, + const char **_forest) +{ + struct ad_cldap_ping_parallel_state *state = NULL; + state = tevent_req_data(req, struct ad_cldap_ping_parallel_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_site = talloc_steal(mem_ctx, state->site); + *_forest = talloc_steal(mem_ctx, state->forest); + + return EOK; +} + +struct ad_cldap_ping_domain_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct be_resolv_ctx *be_res; + enum host_database *host_db; + const char *ad_domain; + + struct fo_server_info *dc_list; + size_t dc_count; + const char *site; + const char *forest; +}; + +static void ad_cldap_ping_domain_discovery_done(struct tevent_req *subreq); +static void ad_cldap_ping_domain_done(struct tevent_req *subreq); + +static struct tevent_req * +ad_cldap_ping_domain_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct be_resolv_ctx *be_res, + enum host_database *host_db, + const char *ad_domain, + const char *discovery_domain) +{ + struct ad_cldap_ping_domain_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + const char **domains; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ad_cldap_ping_domain_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->be_res = be_res; + state->host_db = host_db; + state->ad_domain = ad_domain; + + domains = talloc_zero_array(state, const char *, 2); + if (domains == NULL) { + ret = ENOMEM; + goto done; + } + + domains[0] = discovery_domain; + domains[1] = NULL; + if (domains[0] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bad argument (discovery_domain)"); + ret = ENOMEM; + goto done; + } + + /* Even though we use CLDAP (UDP) to perform the ping we need to discover + * domain controllers in TCP namespace as they are not automatically + * available under UDP. */ + subreq = fo_discover_srv_send(state, ev, be_res->resolv, "ldap", + FO_PROTO_TCP, domains); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ad_cldap_ping_domain_discovery_done, req); + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void ad_cldap_ping_domain_discovery_done(struct tevent_req *subreq) +{ + struct ad_cldap_ping_domain_state *state; + struct tevent_req *req; + char *domain; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_cldap_ping_domain_state); + + ret = fo_discover_srv_recv(state, subreq, &domain, NULL, &state->dc_list, + &state->dc_count); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Found %zu domain controllers in domain %s\n", + state->dc_count, domain); + + subreq = ad_cldap_ping_parallel_send(state, state->ev, state->opts, + state->be_res, state->host_db, + state->dc_list, state->dc_count, + state->ad_domain); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ad_cldap_ping_domain_done, req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } +} + +static void ad_cldap_ping_domain_done(struct tevent_req *subreq) +{ + struct ad_cldap_ping_domain_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_cldap_ping_domain_state); + + ret = ad_cldap_ping_parallel_recv(state, subreq, &state->site, + &state->forest); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ad_cldap_ping_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_site, + const char **_forest) +{ + struct ad_cldap_ping_domain_state *state = NULL; + state = tevent_req_data(req, struct ad_cldap_ping_domain_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_site = talloc_steal(mem_ctx, state->site); + *_forest = talloc_steal(mem_ctx, state->forest); + + return EOK; +} + +struct ad_cldap_ping_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct be_resolv_ctx *be_res; + enum host_database *host_db; + const char *ad_domain; + const char *discovery_domain; + bool all_tried; + + const char *site; + const char *forest; +}; + +static errno_t ad_cldap_ping_step(struct tevent_req *req, + const char *domain); +static void ad_cldap_ping_done(struct tevent_req *subreq); + +struct tevent_req *ad_cldap_ping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ad_srv_plugin_ctx *srv_ctx, + const char *discovery_domain) +{ + struct ad_cldap_ping_state *state; + struct tevent_req *req; + const char *domain; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ad_cldap_ping_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (!srv_ctx->renew_site) { + state->site = talloc_strdup(state, srv_ctx->ad_options->current_site); + state->forest = talloc_strdup(state, + srv_ctx->ad_options->current_forest); + if ((srv_ctx->ad_options->current_site != NULL && state->site == NULL) + || (srv_ctx->ad_options->current_forest != NULL + && state->forest == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to copy current site or forest name.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "CLDAP ping is not necessary, using site '%s' and forest '%s'\n", + state->site != NULL ? state->site : "unknown", + state->forest != NULL ? state->forest : "unknown"); + ret = EOK; + goto done; + } + + if (strcmp(srv_ctx->ad_domain, discovery_domain) != 0) { + DEBUG(SSSDBG_TRACE_ALL, "Trying to discover domain [%s] " + "which is not our local domain [%s], skipping CLDAP ping.\n", + discovery_domain, srv_ctx->ad_domain); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Sending CLDAP ping\n"); + + state->ev = ev; + state->opts = srv_ctx->opts; + state->be_res = srv_ctx->be_res; + state->host_db = srv_ctx->host_dbs; + state->ad_domain = srv_ctx->ad_domain; + state->discovery_domain = discovery_domain; + + /* If possible, lookup the information in the current site first. */ + if (srv_ctx->ad_options->current_site != NULL) { + state->all_tried = false; + domain = ad_site_dns_discovery_domain(state, + srv_ctx->ad_options->current_site, + discovery_domain); + if (domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!"); + ret = ENOMEM; + goto done; + } + } else { + state->all_tried = true; + domain = discovery_domain; + } + + ret = ad_cldap_ping_step(req, domain); + if (ret != EOK) { + goto done; + } + + return req; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ad_cldap_ping_step(struct tevent_req *req, + const char *domain) +{ + struct ad_cldap_ping_state *state; + struct tevent_req *subreq; + struct timeval tv; + int timeout; + + state = tevent_req_data(req, struct ad_cldap_ping_state); + + subreq = ad_cldap_ping_domain_send(state, state->ev, state->opts, + state->be_res, state->host_db, + state->ad_domain, domain); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ad_cldap_ping_done, req); + + timeout = dp_opt_get_int(state->be_res->opts, + DP_RES_OPT_RESOLVER_OP_TIMEOUT); + if (timeout > 0) { + tv = tevent_timeval_current_ofs(timeout, 0); + tevent_req_set_endtime(subreq, state->ev, tv); + } + + return EOK; +} + +static void ad_cldap_ping_done(struct tevent_req *subreq) +{ + struct ad_cldap_ping_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_cldap_ping_state); + + ret = ad_cldap_ping_domain_recv(state, subreq, &state->site, + &state->forest); + talloc_zfree(subreq); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Found site: %s\n", state->site); + DEBUG(SSSDBG_TRACE_FUNC, "Found forest: %s\n", state->forest); + tevent_req_done(req); + return; + } + + if (!state->all_tried) { + state->all_tried = true; + ret = ad_cldap_ping_step(req, state->discovery_domain); + if (ret == EOK) { + return; + } + } + + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get site and forest information [%d]: %s\n", + ret, sss_strerror(ret)); + + tevent_req_error(req, ret); +} + +errno_t ad_cldap_ping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_site, + const char **_forest) +{ + struct ad_cldap_ping_state *state = NULL; + state = tevent_req_data(req, struct ad_cldap_ping_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_site = talloc_steal(mem_ctx, state->site); + *_forest = talloc_steal(mem_ctx, state->forest); + + return EOK; +} diff --git a/src/providers/ad/ad_common.c b/src/providers/ad/ad_common.c new file mode 100644 index 0000000..6215b64 --- /dev/null +++ b/src/providers/ad/ad_common.c @@ -0,0 +1,1753 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ +#include + +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_opts.h" +#include "providers/be_dyndns.h" +#include "providers/fail_over.h" + +struct ad_server_data { + bool gc; +}; + +errno_t ad_set_search_bases(struct sdap_options *id_opts, + struct sdap_domain *sdap); +static errno_t ad_set_sdap_options(struct ad_options *ad_opts, + struct sdap_options *id_opts); + +static struct sdap_options * +ad_create_default_sdap_options(TALLOC_CTX *mem_ctx, + struct data_provider *dp) +{ + struct sdap_options *id_opts; + errno_t ret; + + id_opts = talloc_zero(mem_ctx, struct sdap_options); + if (!id_opts) { + return NULL; + } + id_opts->dp = dp; + + ret = dp_copy_defaults(id_opts, + ad_def_ldap_opts, + SDAP_OPTS_BASIC, + &id_opts->basic); + if (ret != EOK) { + goto fail; + } + + /* Get sdap option maps */ + + /* General Attribute Map */ + ret = sdap_copy_map(id_opts, + ad_2008r2_attr_map, + SDAP_AT_GENERAL, + &id_opts->gen_map); + if (ret != EOK) { + goto fail; + } + + /* User map */ + ret = sdap_copy_map(id_opts, + ad_2008r2_user_map, + SDAP_OPTS_USER, + &id_opts->user_map); + if (ret != EOK) { + goto fail; + } + id_opts->user_map_cnt = SDAP_OPTS_USER; + + /* Group map */ + ret = sdap_copy_map(id_opts, + ad_2008r2_group_map, + SDAP_OPTS_GROUP, + &id_opts->group_map); + if (ret != EOK) { + goto fail; + } + + /* Netgroup map */ + ret = sdap_copy_map(id_opts, + ad_netgroup_map, + SDAP_OPTS_NETGROUP, + &id_opts->netgroup_map); + if (ret != EOK) { + goto fail; + } + + /* Services map */ + ret = sdap_copy_map(id_opts, + ad_service_map, + SDAP_OPTS_SERVICES, + &id_opts->service_map); + if (ret != EOK) { + goto fail; + } + + /* IP host map */ + ret = sdap_copy_map(id_opts, + ad_iphost_map, + SDAP_OPTS_IPHOST, + &id_opts->iphost_map); + if (ret != EOK) { + goto fail; + } + + /* IP network map */ + ret = sdap_copy_map(id_opts, + ad_ipnetwork_map, + SDAP_OPTS_IPNETWORK, + &id_opts->ipnetwork_map); + if (ret != EOK) { + goto fail; + } + + return id_opts; + +fail: + talloc_free(id_opts); + return NULL; +} + +static errno_t +ad_create_sdap_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sdap_options **_id_opts) +{ + struct sdap_options *id_opts; + errno_t ret = EOK; + + if (cdb == NULL || conf_path == NULL) { + /* Fallback to defaults if there is no confdb */ + id_opts = ad_create_default_sdap_options(mem_ctx, dp); + if (id_opts == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize default sdap options\n"); + ret = EIO; + } + /* Nothing to do without cdb */ + goto done; + } + + id_opts = talloc_zero(mem_ctx, struct sdap_options); + if (!id_opts) { + ret = ENOMEM; + goto done; + } + + ret = dp_get_options(id_opts, cdb, conf_path, + ad_def_ldap_opts, + SDAP_OPTS_BASIC, + &id_opts->basic); + if (ret != EOK) { + goto done; + } + + /* sssd-ad can't use simple bind, ignore option that potentially can be set + * for sssd-ldap in the same domain + */ + ret = dp_opt_set_string(id_opts->basic, SDAP_DEFAULT_AUTHTOK_TYPE, NULL); + if (ret != EOK) { + goto done; + } + + /* Get sdap option maps */ + + /* General Attribute Map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_2008r2_attr_map, + SDAP_AT_GENERAL, + &id_opts->gen_map); + if (ret != EOK) { + goto done; + } + + /* User map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_2008r2_user_map, + SDAP_OPTS_USER, + &id_opts->user_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_extend_map_with_list(id_opts, id_opts, + SDAP_USER_EXTRA_ATTRS, + id_opts->user_map, + SDAP_OPTS_USER, + &id_opts->user_map, + &id_opts->user_map_cnt); + if (ret != EOK) { + goto done; + } + + /* Group map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_2008r2_group_map, + SDAP_OPTS_GROUP, + &id_opts->group_map); + if (ret != EOK) { + goto done; + } + + /* Netgroup map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_netgroup_map, + SDAP_OPTS_NETGROUP, + &id_opts->netgroup_map); + if (ret != EOK) { + goto done; + } + + /* Services map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_service_map, + SDAP_OPTS_SERVICES, + &id_opts->service_map); + if (ret != EOK) { + goto done; + } + + /* IP host map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_iphost_map, + SDAP_OPTS_IPHOST, + &id_opts->iphost_map); + if (ret != EOK) { + goto done; + } + + /* IP network map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_ipnetwork_map, + SDAP_OPTS_IPNETWORK, + &id_opts->ipnetwork_map); + if (ret != EOK) { + goto done; + } + + ret = EOK; +done: + if (ret == EOK) { + *_id_opts = id_opts; + } else { + talloc_free(id_opts); + } + + return ret; +} + +struct ad_options * +ad_create_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sss_domain_info *subdom) +{ + struct ad_options *ad_options; + errno_t ret; + + ad_options = talloc_zero(mem_ctx, struct ad_options); + if (ad_options == NULL) return NULL; + + if (cdb != NULL && conf_path != NULL) { + ret = dp_get_options(ad_options, + cdb, + conf_path, + ad_basic_opts, + AD_OPTS_BASIC, + &ad_options->basic); + } else { + /* Fallback to reading the defaults only if no confdb + * is available */ + ret = dp_copy_defaults(ad_options, + ad_basic_opts, + AD_OPTS_BASIC, + &ad_options->basic); + } + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get basic AD options\n"); + talloc_free(ad_options); + return NULL; + } + + ret = ad_create_sdap_options(ad_options, + cdb, + conf_path, + dp, + &ad_options->id); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD LDAP options\n"); + talloc_free(ad_options); + return NULL; + } + + return ad_options; +} + +static errno_t +set_common_ad_trust_opts(struct ad_options *ad_options, + const char *realm, + const char *ad_domain, + const char *hostname, + const char *keytab) +{ + errno_t ret; + + ret = dp_opt_set_string(ad_options->basic, AD_KRB5_REALM, realm); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set AD krb5 realm\n"); + return ret; + } + + ret = dp_opt_set_string(ad_options->basic, AD_DOMAIN, ad_domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set AD domain\n"); + return ret; + } + + ret = dp_opt_set_string(ad_options->basic, AD_HOSTNAME, hostname); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set AD hostname\n"); + return ret; + } + + if (keytab != NULL) { + ret = dp_opt_set_string(ad_options->basic, AD_KEYTAB, keytab); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set keytab\n"); + return ret; + } + } + + return EOK; +} + +struct ad_options * +ad_create_2way_trust_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + const char *realm, + struct sss_domain_info *subdom, + const char *hostname, + const char *keytab) +{ + struct ad_options *ad_options; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "2way trust is defined to domain '%s'\n", + subdom->name); + + ad_options = ad_create_options(mem_ctx, cdb, conf_path, dp, subdom); + if (ad_options == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "ad_create_options failed\n"); + return NULL; + } + + ret = set_common_ad_trust_opts(ad_options, realm, subdom->name, hostname, + keytab); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "set_common_ad_trust_opts failed\n"); + talloc_free(ad_options); + return NULL; + } + + ret = ad_set_sdap_options(ad_options, ad_options->id); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ad_set_sdap_options failed\n"); + talloc_free(ad_options); + return NULL; + } + + return ad_options; +} + +struct ad_options * +ad_create_1way_trust_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *subdom_conf_path, + struct data_provider *dp, + struct sss_domain_info *subdom, + const char *hostname, + const char *keytab, + const char *sasl_authid) +{ + struct ad_options *ad_options; + const char *realm; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "1way trust is defined to domain '%s'\n", + subdom->name); + + ad_options = ad_create_options(mem_ctx, cdb, subdom_conf_path, dp, subdom); + if (ad_options == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "ad_create_options failed\n"); + return NULL; + } + + realm = get_uppercase_realm(ad_options, subdom->name); + if (!realm) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to get uppercase realm\n"); + talloc_free(ad_options); + return NULL; + } + + ret = set_common_ad_trust_opts(ad_options, realm, + subdom->name, hostname, keytab); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "set_common_ad_trust_opts failed [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(ad_options); + return NULL; + } + + /* Set SDAP_SASL_AUTHID to the trust principal */ + ret = dp_opt_set_string(ad_options->id->basic, + SDAP_SASL_AUTHID, sasl_authid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set SASL authid\n"); + talloc_free(ad_options); + return NULL; + } + + ret = ad_set_sdap_options(ad_options, ad_options->id); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ad_set_sdap_options failed [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(ad_options); + return NULL; + } + + return ad_options; +} + +static errno_t +ad_try_to_get_fqdn(const char *hostname, + char *buf, + size_t buflen) +{ + int ret; + struct addrinfo *res; + struct addrinfo hints; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_CANONNAME; + + ret = getaddrinfo(hostname, NULL, &hints, &res); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "getaddrinfo failed: %s\n", + gai_strerror(ret)); + return ret; + } + + strncpy(buf, res->ai_canonname, buflen-1); + buf[buflen-1] = '\0'; + + freeaddrinfo(res); + + return EOK; +} + +errno_t +ad_get_common_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sss_domain_info *dom, + struct ad_options **_opts) +{ + errno_t ret; + int gret; + struct ad_options *opts = NULL; + char *domain; + char *server; + char *realm; + char *ad_hostname; + char hostname[HOST_NAME_MAX + 1]; + char fqdn[HOST_NAME_MAX + 1]; + char *case_sensitive_opt; + const char *opt_override; + + opts = talloc_zero(mem_ctx, struct ad_options); + if (!opts) return ENOMEM; + + ret = dp_get_options(opts, cdb, conf_path, + ad_basic_opts, + AD_OPTS_BASIC, + &opts->basic); + if (ret != EOK) { + goto done; + } + + /* If the AD domain name wasn't explicitly set, assume that it + * matches the SSSD domain name + */ + domain = dp_opt_get_string(opts->basic, AD_DOMAIN); + if (!domain) { + ret = dp_opt_set_string(opts->basic, AD_DOMAIN, dom->name); + if (ret != EOK) { + goto done; + } + domain = dom->name; + } + + /* Did we get an explicit server name, or are we discovering it? */ + server = dp_opt_get_string(opts->basic, AD_SERVER); + if (!server) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No AD server set, will use service discovery!\n"); + } + + /* Set the machine's hostname to the local host name if it + * wasn't explicitly specified. + */ + ad_hostname = dp_opt_get_string(opts->basic, AD_HOSTNAME); + if (ad_hostname == NULL) { + gret = gethostname(hostname, sizeof(hostname)); + if (gret != 0) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "gethostname failed [%s].\n", + strerror(ret)); + goto done; + } + hostname[HOST_NAME_MAX] = '\0'; + + if (strchr(hostname, '.') == NULL) { + ret = ad_try_to_get_fqdn(hostname, fqdn, sizeof(fqdn)); + if (ret == EOK) { + DEBUG(SSSDBG_CONF_SETTINGS, + "The hostname [%s] has been expanded to FQDN [%s]. " + "If sssd should really use the short hostname, please " + "set ad_hostname explicitly.\n", hostname, fqdn); + strncpy(hostname, fqdn, HOST_NAME_MAX); + hostname[HOST_NAME_MAX] = '\0'; + } + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Setting ad_hostname to [%s].\n", hostname); + ret = dp_opt_set_string(opts->basic, AD_HOSTNAME, hostname); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Setting ad_hostname failed [%s].\n", + strerror(ret)); + goto done; + } + } + + + /* Always use the upper-case AD domain for the kerberos realm */ + realm = get_uppercase_realm(opts, domain); + if (!realm) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(opts->basic, AD_KRB5_REALM, realm); + if (ret != EOK) { + goto done; + } + + /* Active Directory is always case-insensitive */ + ret = confdb_get_string(cdb, mem_ctx, conf_path, + CONFDB_DOMAIN_CASE_SENSITIVE, "false", + &case_sensitive_opt); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "condb_get_string failed.\n"); + goto done; + } + + if (strcasecmp(case_sensitive_opt, "true") == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Warning: AD domain can not be set as case-sensitive.\n"); + dom->case_sensitive = false; + dom->case_preserve = false; + } else if (strcasecmp(case_sensitive_opt, "false") == 0) { + dom->case_sensitive = false; + dom->case_preserve = false; + } else if (strcasecmp(case_sensitive_opt, "preserving") == 0) { + dom->case_sensitive = false; + dom->case_preserve = true; + } else { + DEBUG(SSSDBG_FATAL_FAILURE, + "Invalid value for %s\n", CONFDB_DOMAIN_CASE_SENSITIVE); + goto done; + } + + opt_override = dom->case_preserve ? "preserving" : "false"; + + /* Set this in the confdb so that the responders pick it + * up when they start up. + */ + ret = confdb_set_string(cdb, conf_path, "case_sensitive", opt_override); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set domain option case_sensitive: [%s]\n", + strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Setting domain option case_sensitive to [%s]\n", opt_override); + + ret = EOK; + *_opts = opts; + +done: + if (ret != EOK) { + talloc_zfree(opts); + } + return ret; +} + +static void +ad_resolve_callback(void *private_data, struct fo_server *server); + +static errno_t +_ad_servers_init(struct ad_service *service, + struct be_ctx *bectx, + const char *fo_service, + const char *fo_gc_service, + const char *servers, + const char *ad_domain, + bool primary) +{ + size_t i; + size_t j; + errno_t ret = 0; + char **list; + struct ad_server_data *sdata; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + /* Split the server list */ + ret = split_on_separator(tmp_ctx, servers, ',', true, true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse server list!\n"); + goto done; + } + + for (j = 0; list[j]; j++) { + if (resolv_is_address(list[j])) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "ad_server [%s] is detected as IP address, " + "this can cause GSSAPI/GSS-SPNEGO problems\n", list[j]); + } + } + + /* Add each of these servers to the failover service */ + for (i = 0; list[i]; i++) { + if (be_fo_is_srv_identifier(list[i])) { + if (!primary) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add server [%s] to failover service: " + "SRV resolution only allowed for primary servers!\n", + list[i]); + continue; + } + + sdata = talloc(service, struct ad_server_data); + if (sdata == NULL) { + ret = ENOMEM; + goto done; + } + sdata->gc = true; + + ret = be_fo_add_srv_server(bectx, fo_gc_service, "gc", + ad_domain, BE_FO_PROTO_TCP, + false, sdata); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to add service discovery to failover: [%s]\n", + strerror(ret)); + goto done; + } + + sdata = talloc(service, struct ad_server_data); + if (sdata == NULL) { + ret = ENOMEM; + goto done; + } + sdata->gc = false; + + ret = be_fo_add_srv_server(bectx, fo_service, "ldap", + ad_domain, BE_FO_PROTO_TCP, + false, sdata); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to add service discovery to failover: [%s]\n", + strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Added service discovery for AD\n"); + continue; + } + + /* It could be ipv6 address in square brackets. Remove + * the brackets if needed. */ + ret = remove_ipv6_brackets(list[i]); + if (ret != EOK) { + goto done; + } + + sdata = talloc(service, struct ad_server_data); + if (sdata == NULL) { + ret = ENOMEM; + goto done; + } + sdata->gc = true; + + ret = be_fo_add_server(bectx, fo_gc_service, list[i], 0, sdata, primary); + if (ret && ret != EEXIST) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + sdata = talloc(service, struct ad_server_data); + if (sdata == NULL) { + ret = ENOMEM; + goto done; + } + sdata->gc = false; + + ret = be_fo_add_server(bectx, fo_service, list[i], 0, sdata, primary); + if (ret && ret != EEXIST) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Added failover server %s\n", list[i]); + } +done: + talloc_free(tmp_ctx); + return ret; +} + +static inline errno_t +ad_primary_servers_init(struct ad_service *service, + struct be_ctx *bectx, const char *servers, + const char *fo_service, const char *fo_gc_service, + const char *ad_domain) +{ + return _ad_servers_init(service, bectx, fo_service, + fo_gc_service, servers, ad_domain, true); +} + +static inline errno_t +ad_backup_servers_init(struct ad_service *service, + struct be_ctx *bectx, const char *servers, + const char *fo_service, const char *fo_gc_service, + const char *ad_domain) +{ + return _ad_servers_init(service, bectx, fo_service, + fo_gc_service, servers, ad_domain, false); +} + +static int ad_user_data_cmp(void *ud1, void *ud2) +{ + struct ad_server_data *sd1, *sd2; + + sd1 = talloc_get_type(ud1, struct ad_server_data); + sd2 = talloc_get_type(ud2, struct ad_server_data); + if (sd1 == NULL || sd2 == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No user data\n"); + return sd1 == sd2 ? 0 : 1; + } + + if (sd1->gc == sd2->gc) { + return 0; + } + + return 1; +} + +static void ad_online_cb(void *pvt) +{ + struct ad_service *service = talloc_get_type(pvt, struct ad_service); + + if (service == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid private pointer\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "The AD provider is online\n"); +} + +errno_t +ad_failover_init(TALLOC_CTX *mem_ctx, struct be_ctx *bectx, + const char *primary_servers, + const char *backup_servers, + const char *krb5_realm, + const char *ad_service, + const char *ad_gc_service, + const char *ad_domain, + bool use_kdcinfo, + bool ad_use_ldaps, + size_t n_lookahead_primary, + size_t n_lookahead_backup, + struct ad_service **_service) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct ad_service *service; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + service = talloc_zero(tmp_ctx, struct ad_service); + if (!service) { + ret = ENOMEM; + goto done; + } + + if (ad_use_ldaps) { + service->ldap_scheme = "ldaps"; + service->port = LDAPS_PORT; + service->gc_port = AD_GC_LDAPS_PORT; + } else { + service->ldap_scheme = "ldap"; + service->port = LDAP_PORT; + service->gc_port = AD_GC_PORT; + } + + service->sdap = talloc_zero(service, struct sdap_service); + service->gc = talloc_zero(service, struct sdap_service); + if (!service->sdap || !service->gc) { + ret = ENOMEM; + goto done; + } + + service->sdap->name = talloc_strdup(service->sdap, ad_service); + service->gc->name = talloc_strdup(service->gc, ad_gc_service); + if (!service->sdap->name || !service->gc->name) { + ret = ENOMEM; + goto done; + } + + service->krb5_service = krb5_service_new(service, bectx, + ad_service, krb5_realm, + use_kdcinfo, + n_lookahead_primary, + n_lookahead_backup); + if (!service->krb5_service) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_service(bectx, ad_service, ad_user_data_cmp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create failover service!\n"); + goto done; + } + + ret = be_fo_add_service(bectx, ad_gc_service, ad_user_data_cmp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create GC failover service!\n"); + goto done; + } + + service->sdap->kinit_service_name = service->krb5_service->name; + service->gc->kinit_service_name = service->krb5_service->name; + + if (!krb5_realm) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm set\n"); + ret = EINVAL; + goto done; + } + + if (!primary_servers) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No primary servers defined, using service discovery\n"); + primary_servers = BE_SRV_IDENTIFIER; + } + + ret = ad_primary_servers_init(service, bectx, + primary_servers, ad_service, + ad_gc_service, ad_domain); + if (ret != EOK) { + goto done; + } + + if (backup_servers) { + ret = ad_backup_servers_init(service, bectx, + backup_servers, ad_service, + ad_gc_service, ad_domain); + if (ret != EOK) { + goto done; + } + } + + ret = be_add_online_cb(bectx, bectx, ad_online_cb, service, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not set up AD online callback\n"); + goto done; + } + + ret = be_fo_service_add_callback(mem_ctx, bectx, ad_service, + ad_resolve_callback, service); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to add failover callback! [%s]\n", strerror(ret)); + goto done; + } + + ret = be_fo_service_add_callback(mem_ctx, bectx, ad_gc_service, + ad_resolve_callback, service); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to add failover callback! [%s]\n", strerror(ret)); + goto done; + } + + *_service = talloc_steal(mem_ctx, service); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +void +ad_failover_reset(struct be_ctx *bectx, + struct ad_service *adsvc) +{ + if (adsvc == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "NULL service\n"); + return; + } + + sdap_service_reset_fo(bectx, adsvc->sdap); + sdap_service_reset_fo(bectx, adsvc->gc); +} + +static bool +ad_krb5info_file_filter(struct fo_server *server) +{ + struct ad_server_data *sdata = NULL; + if (server == NULL) return true; + + sdata = fo_get_server_user_data(server); + if (sdata && sdata->gc) { + /* Only write kdcinfo files for local servers */ + return true; + } + return false; +} + +static void +ad_resolve_callback(void *private_data, struct fo_server *server) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct ad_service *service; + struct resolv_hostent *srvaddr; + struct sockaddr *sockaddr; + char *address; + char *new_uri; + int new_port; + socklen_t sockaddr_len; + const char *srv_name; + struct ad_server_data *sdata = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); + return; + } + + sdata = fo_get_server_user_data(server); + if (fo_is_srv_lookup(server) == false && sdata == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No user data?\n"); + ret = EINVAL; + goto done; + } + + service = talloc_get_type(private_data, struct ad_service); + if (!service) { + ret = EINVAL; + goto done; + } + + srvaddr = fo_get_server_hostent(server); + if (!srvaddr) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No hostent available for server (%s)\n", + fo_get_server_str_name(server)); + ret = EINVAL; + goto done; + } + + address = resolv_get_string_address(tmp_ctx, srvaddr); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "resolv_get_string_address failed.\n"); + ret = EIO; + goto done; + } + + srv_name = fo_get_server_name(server); + if (srv_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not get server host name\n"); + ret = EINVAL; + goto done; + } + + new_uri = talloc_asprintf(service->sdap, "%s://%s", service->ldap_scheme, + srv_name); + if (!new_uri) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy URI\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_CONF_SETTINGS, "Constructed uri '%s'\n", new_uri); + + sockaddr = resolv_get_sockaddr_address(tmp_ctx, srvaddr, service->port, + &sockaddr_len); + if (sockaddr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "resolv_get_sockaddr_address failed.\n"); + ret = EIO; + goto done; + } + + /* free old one and replace with new one */ + if (sdata == NULL || !sdata->gc) { + /* do not update LDAP data during GC lookups because the selected server + * might be from a different domain. */ + talloc_zfree(service->sdap->uri); + service->sdap->uri = new_uri; + talloc_zfree(service->sdap->sockaddr); + service->sdap->sockaddr = talloc_steal(service->sdap, sockaddr); + service->sdap->sockaddr_len = sockaddr_len; + } + + talloc_zfree(service->gc->uri); + talloc_zfree(service->gc->sockaddr); + if (sdata && sdata->gc) { + if (service->gc_port == AD_GC_LDAPS_PORT) { + new_port = service->gc_port; + } else { + new_port = fo_get_server_port(server); + new_port = (new_port == 0) ? service->gc_port : new_port; + } + + service->gc->uri = talloc_asprintf(service->gc, "%s:%d", + new_uri, new_port); + + service->gc->sockaddr = resolv_get_sockaddr_address(service->gc, + srvaddr, + new_port, + &sockaddr_len); + service->gc->sockaddr_len = sockaddr_len; + } else { + /* Make sure there always is an URI even if we know that this + * server doesn't support GC. That way the lookup would go through + * just not return anything + */ + service->gc->uri = talloc_strdup(service->gc, service->sdap->uri); + service->gc->sockaddr = talloc_memdup(service->gc, service->sdap->sockaddr, + service->sdap->sockaddr_len); + service->gc->sockaddr_len = service->sdap->sockaddr_len; + } + + if (!service->gc->uri) { + DEBUG(SSSDBG_CRIT_FAILURE, "NULL GC URI\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_CONF_SETTINGS, "Constructed GC uri '%s'\n", service->gc->uri); + + if (service->gc->sockaddr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "NULL GC sockaddr\n"); + ret = EIO; + goto done; + } + + if (service->krb5_service->write_kdcinfo && !(sdata != NULL && sdata->gc)) { + /* write KDC info file only if this is not GC lookup */ + ret = write_krb5info_file_from_fo_server(service->krb5_service, + server, + true, + SSS_KRB5KDC_FO_SRV, + ad_krb5info_file_filter); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "write to %s/kdcinfo.%s failed, authentication might fail.\n", + PUBCONF_PATH, service->krb5_service->realm); + } + } + + ret = EOK; +done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error: %d [%s]\n", ret, strerror(ret)); + } + talloc_free(tmp_ctx); + return; +} + +void ad_set_ssf_and_mech_for_ldaps(struct sdap_options *id_opts) +{ + int ret; + + DEBUG(SSSDBG_TRACE_ALL, "Setting ssf and mech for ldaps usage.\n"); + ret = dp_opt_set_int(id_opts->basic, SDAP_SASL_MINSSF, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set SASL minssf for ldaps usage, ignored.\n"); + } + ret = dp_opt_set_int(id_opts->basic, SDAP_SASL_MAXSSF, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set SASL maxssf for ldaps usage, ignored.\n"); + } + +#ifndef ALLOW_GSS_SPNEGO_FOR_ZERO_MAXSSF + /* There is an issue in cyrus-sasl with respect to GSS-SPNEGO and + * maxssf==0. Until the fix + * https://github.com/cyrusimap/cyrus-sasl/pull/603 is widely used we + * switch to GSSAPI by default when using AD with LDAPS where maxssf==0 is + * required. */ + ret = dp_opt_set_string(id_opts->basic, SDAP_SASL_MECH, "GSSAPI"); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set SASL mech for ldaps usage, ignored.\n"); + } +#endif +} + +static errno_t +ad_set_sdap_options(struct ad_options *ad_opts, + struct sdap_options *id_opts) +{ + errno_t ret; + char *krb5_realm; + char *keytab_path; + const char *schema; + + /* We only support Kerberos password policy with AD, so + * force that on. + */ + ret = dp_opt_set_string(id_opts->basic, + SDAP_PWD_POLICY, + PWD_POL_OPT_MIT); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not set password policy\n"); + goto done; + } + + /* Set the Kerberos Realm for GSSAPI or GSS-SPNEGO */ + krb5_realm = dp_opt_get_string(ad_opts->basic, AD_KRB5_REALM); + if (!krb5_realm) { + /* Should be impossible, this is set in ad_get_common_options() */ + DEBUG(SSSDBG_FATAL_FAILURE, "No Kerberos realm\n"); + ret = EINVAL; + goto done; + } + + ret = dp_opt_set_string(id_opts->basic, SDAP_KRB5_REALM, krb5_realm); + if (ret != EOK) goto done; + DEBUG(SSSDBG_CONF_SETTINGS, + "Option %s set to %s\n", + id_opts->basic[SDAP_KRB5_REALM].opt_name, + krb5_realm); + + keytab_path = dp_opt_get_string(ad_opts->basic, AD_KEYTAB); + if (keytab_path) { + ret = dp_opt_set_string(id_opts->basic, SDAP_KRB5_KEYTAB, + keytab_path); + if (ret != EOK) goto done; + DEBUG(SSSDBG_CONF_SETTINGS, + "Option %s set to %s\n", + id_opts->basic[SDAP_KRB5_KEYTAB].opt_name, + keytab_path); + } + + id_opts->allow_remote_domain_local_groups = dp_opt_get_bool(ad_opts->basic, + AD_ALLOW_REMOTE_DOMAIN_LOCAL); + + ret = sdap_set_sasl_options(id_opts, + dp_opt_get_string(ad_opts->basic, + AD_HOSTNAME), + dp_opt_get_string(ad_opts->basic, + AD_KRB5_REALM), + keytab_path); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set the SASL-related options\n"); + goto done; + } + + if (dp_opt_get_bool(ad_opts->basic, AD_USE_LDAPS)) { + ad_set_ssf_and_mech_for_ldaps(id_opts); + } + + /* Warn if the user is doing something silly like overriding the schema + * with the AD provider + */ + schema = dp_opt_get_string(id_opts->basic, SDAP_SCHEMA); + if (schema != NULL && strcasecmp(schema, "ad") != 0) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "The AD provider only supports the AD LDAP schema. " + "SSSD will ignore the ldap_schema option value and proceed " + "with ldap_schema=ad\n"); + } + + /* fix schema to AD */ + id_opts->schema_type = SDAP_SCHEMA_AD; + + ad_opts->id = id_opts; + ret = EOK; +done: + return ret; +} + +errno_t +ad_get_id_options(struct ad_options *ad_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sdap_options **_opts) +{ + struct sdap_options *id_opts; + errno_t ret; + + ret = ad_create_sdap_options(ad_opts, cdb, conf_path, dp, &id_opts); + if (ret != EOK) { + return ENOMEM; + } + + ret = ad_set_sdap_options(ad_opts, id_opts); + if (ret != EOK) { + talloc_free(id_opts); + return ret; + } + + ret = sdap_domain_add(id_opts, + ad_opts->id_ctx->sdap_id_ctx->be->domain, + NULL); + if (ret != EOK) { + talloc_free(id_opts); + return ret; + } + + /* Set up search bases if they were assigned explicitly */ + ret = ad_set_search_bases(id_opts, NULL); + if (ret != EOK) { + talloc_free(id_opts); + return ret; + } + + *_opts = id_opts; + return EOK; +} + +errno_t +ad_get_autofs_options(struct ad_options *ad_opts, + struct confdb_ctx *cdb, + const char *conf_path) +{ + errno_t ret; + + /* autofs maps */ + ret = sdap_get_map(ad_opts->id, + cdb, + conf_path, + ad_autofs_mobject_map, + SDAP_OPTS_AUTOFS_MAP, + &ad_opts->id->autofs_mobject_map); + if (ret != EOK) { + return ret; + } + + ret = sdap_get_map(ad_opts->id, + cdb, + conf_path, + ad_autofs_entry_map, + SDAP_OPTS_AUTOFS_ENTRY, + &ad_opts->id->autofs_entry_map); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t +ad_set_search_bases(struct sdap_options *id_opts, + struct sdap_domain *sdom) +{ + errno_t ret; + char *default_search_base = NULL; + size_t o; + struct sdap_domain *sdap_dom; + bool has_default; + struct ldb_context *ldb; + const int search_base_options[] = { SDAP_USER_SEARCH_BASE, + SDAP_GROUP_SEARCH_BASE, + SDAP_NETGROUP_SEARCH_BASE, + SDAP_SERVICE_SEARCH_BASE, + -1 }; + + /* AD servers provide defaultNamingContext, so we will + * rely on that to specify the search base unless it has + * been specifically overridden. + */ + + if (sdom != NULL) { + sdap_dom = sdom; + } else { + /* If no specific sdom was given, use the first in the list. */ + sdap_dom = id_opts->sdom; + } + ldb = sysdb_ctx_get_ldb(sdap_dom->dom->sysdb); + + has_default = sdap_dom->search_bases != NULL; + + if (has_default == false) { + default_search_base = + dp_opt_get_string(id_opts->basic, SDAP_SEARCH_BASE); + } + + if (default_search_base && has_default == false) { + /* set search bases if they are not */ + for (o = 0; search_base_options[o] != -1; o++) { + if (NULL == dp_opt_get_string(id_opts->basic, + search_base_options[o])) { + ret = dp_opt_set_string(id_opts->basic, + search_base_options[o], + default_search_base); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_CONF_SETTINGS, + "Option %s set to %s\n", + id_opts->basic[search_base_options[o]].opt_name, + dp_opt_get_string(id_opts->basic, + search_base_options[o])); + } + } + } else { + DEBUG(SSSDBG_CONF_SETTINGS, + "Search base not set. SSSD will attempt to discover it later, " + "when connecting to the LDAP server.\n"); + } + + /* Default search */ + ret = sdap_parse_search_base(id_opts, ldb, id_opts->basic, + SDAP_SEARCH_BASE, + &sdap_dom->search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* User search */ + ret = sdap_parse_search_base(id_opts, ldb, id_opts->basic, + SDAP_USER_SEARCH_BASE, + &sdap_dom->user_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Group search base */ + ret = sdap_parse_search_base(id_opts, ldb, id_opts->basic, + SDAP_GROUP_SEARCH_BASE, + &sdap_dom->group_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Netgroup search */ + ret = sdap_parse_search_base(id_opts, ldb, id_opts->basic, + SDAP_NETGROUP_SEARCH_BASE, + &sdap_dom->netgroup_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Service search */ + ret = sdap_parse_search_base(id_opts, ldb, id_opts->basic, + SDAP_SERVICE_SEARCH_BASE, + &sdap_dom->service_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + ret = EOK; +done: + return ret; +} + +errno_t +ad_get_auth_options(TALLOC_CTX *mem_ctx, + struct ad_options *ad_opts, + struct be_ctx *bectx, + struct dp_option **_opts) +{ + errno_t ret; + struct dp_option *krb5_options; + const char *ad_servers; + const char *krb5_realm; + + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + /* Get krb5 options */ + ret = dp_get_options(tmp_ctx, bectx->cdb, bectx->conf_path, + ad_def_krb5_opts, KRB5_OPTS, + &krb5_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not read Kerberos options from the configuration\n"); + goto done; + } + + ad_servers = dp_opt_get_string(ad_opts->basic, AD_SERVER); + + /* Force the krb5_servers to match the ad_servers */ + ret = dp_opt_set_string(krb5_options, KRB5_KDC, ad_servers); + if (ret != EOK) goto done; + DEBUG(SSSDBG_CONF_SETTINGS, + "Option %s set to %s\n", + krb5_options[KRB5_KDC].opt_name, + ad_servers); + + /* Set krb5 realm */ + /* Set the Kerberos Realm for GSSAPI/GSS-SPNEGO */ + krb5_realm = dp_opt_get_string(ad_opts->basic, AD_KRB5_REALM); + if (!krb5_realm) { + /* Should be impossible, this is set in ad_get_common_options() */ + DEBUG(SSSDBG_FATAL_FAILURE, "No Kerberos realm\n"); + ret = EINVAL; + goto done; + } + + /* Force the kerberos realm to match the AD_KRB5_REALM (which may have + * been upper-cased in ad_common_options() + */ + ret = dp_opt_set_string(krb5_options, KRB5_REALM, krb5_realm); + if (ret != EOK) goto done; + DEBUG(SSSDBG_CONF_SETTINGS, + "Option %s set to %s\n", + krb5_options[KRB5_REALM].opt_name, + krb5_realm); + + /* Set flag that controls whether we want to write the + * kdcinfo files at all + */ + ad_opts->service->krb5_service->write_kdcinfo = \ + dp_opt_get_bool(krb5_options, KRB5_USE_KDCINFO); + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + krb5_options[KRB5_USE_KDCINFO].opt_name, + ad_opts->service->krb5_service->write_kdcinfo ? "true" : "false"); + sss_krb5_parse_lookahead( + dp_opt_get_string(krb5_options, KRB5_KDCINFO_LOOKAHEAD), + &ad_opts->service->krb5_service->lookahead_primary, + &ad_opts->service->krb5_service->lookahead_backup); + + *_opts = talloc_steal(mem_ctx, krb5_options); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t ad_get_dyndns_options(struct be_ctx *be_ctx, + struct ad_options *ad_opts) +{ + errno_t ret; + + ret = be_nsupdate_init(ad_opts, be_ctx, ad_dyndns_opts, + &ad_opts->dyndns_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot initialize AD dyndns opts [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + + +struct ad_id_ctx * +ad_id_ctx_init(struct ad_options *ad_opts, struct be_ctx *bectx) +{ + struct sdap_id_ctx *sdap_ctx; + struct ad_id_ctx *ad_ctx; + + ad_ctx = talloc_zero(ad_opts, struct ad_id_ctx); + if (ad_ctx == NULL) { + return NULL; + } + ad_ctx->ad_options = ad_opts; + + sdap_ctx = sdap_id_ctx_new(ad_ctx, bectx, ad_opts->service->sdap); + if (sdap_ctx == NULL) { + talloc_free(ad_ctx); + return NULL; + } + ad_ctx->sdap_id_ctx = sdap_ctx; + ad_ctx->ldap_ctx = sdap_ctx->conn; + + ad_ctx->gc_ctx = sdap_id_ctx_conn_add(sdap_ctx, ad_opts->service->gc); + if (ad_ctx->gc_ctx == NULL) { + talloc_free(ad_ctx); + return NULL; + } + + return ad_ctx; +} + +errno_t +ad_resolver_ctx_init(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *ad_id_ctx, + struct ad_resolver_ctx **out_ctx) +{ + struct sdap_resolver_ctx *sdap_ctx; + struct ad_resolver_ctx *ad_ctx; + errno_t ret; + + ad_ctx = talloc_zero(mem_ctx, struct ad_resolver_ctx); + if (ad_ctx == NULL) { + return ENOMEM; + } + ad_ctx->ad_id_ctx = ad_id_ctx; + + ret = sdap_resolver_ctx_new(ad_ctx, ad_id_ctx->sdap_id_ctx, &sdap_ctx); + if (ret != EOK) { + talloc_free(ad_ctx); + return ret; + } + ad_ctx->sdap_resolver_ctx = sdap_ctx; + + *out_ctx = ad_ctx; + + return EOK; +} + +struct sdap_id_conn_ctx * +ad_get_dom_ldap_conn(struct ad_id_ctx *ad_ctx, struct sss_domain_info *dom) +{ + struct sdap_id_conn_ctx *conn; + struct sdap_domain *sdom; + struct ad_id_ctx *subdom_id_ctx; + + sdom = sdap_domain_get(ad_ctx->sdap_id_ctx->opts, dom); + if (sdom == NULL || sdom->pvt == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n", + dom->name); + return NULL; + } + subdom_id_ctx = talloc_get_type(sdom->pvt, struct ad_id_ctx); + conn = subdom_id_ctx->ldap_ctx; + + if (IS_SUBDOMAIN(sdom->dom) == true && conn != NULL) { + /* Regardless of connection types, a subdomain error must not be + * allowed to set the whole back end offline, rather report an error + * and let the caller deal with it (normally disable the subdomain + */ + conn->ignore_mark_offline = true; + } + + return conn; +} + +struct sdap_id_conn_ctx ** +ad_gc_conn_list(TALLOC_CTX *mem_ctx, struct ad_id_ctx *ad_ctx, + struct sss_domain_info *dom) +{ + struct sdap_id_conn_ctx **clist; + int cindex = 0; + + clist = talloc_zero_array(mem_ctx, struct sdap_id_conn_ctx *, 3); + if (clist == NULL) return NULL; + + /* Always try GC first */ + if (dp_opt_get_bool(ad_ctx->ad_options->basic, AD_ENABLE_GC)) { + clist[cindex] = ad_ctx->gc_ctx; + clist[cindex]->ignore_mark_offline = true; + clist[cindex]->no_mpg_user_fallback = true; + cindex++; + } + + clist[cindex] = ad_get_dom_ldap_conn(ad_ctx, dom); + + return clist; +} + +struct sdap_id_conn_ctx ** +ad_ldap_conn_list(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *ad_ctx, + struct sss_domain_info *dom) +{ + struct sdap_id_conn_ctx **clist; + + clist = talloc_zero_array(mem_ctx, struct sdap_id_conn_ctx *, 2); + if (clist == NULL) { + return NULL; + } + + clist[0] = ad_get_dom_ldap_conn(ad_ctx, dom); + + clist[1] = NULL; + return clist; +} + +struct sdap_id_conn_ctx ** +ad_user_conn_list(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *ad_ctx, + struct sss_domain_info *dom) +{ + struct sdap_id_conn_ctx **clist; + int cindex = 0; + + clist = talloc_zero_array(mem_ctx, struct sdap_id_conn_ctx *, 3); + if (clist == NULL) { + return NULL; + } + + /* Try GC first for users from trusted domains, but go to LDAP + * for users from non-trusted domains to get all POSIX attrs + */ + if (dp_opt_get_bool(ad_ctx->ad_options->basic, AD_ENABLE_GC) + && IS_SUBDOMAIN(dom)) { + clist[cindex] = ad_ctx->gc_ctx; + clist[cindex]->ignore_mark_offline = true; + cindex++; + } + + /* Users from primary domain can be just downloaded from LDAP. + * The domain's LDAP connection also works as a fallback + */ + clist[cindex] = ad_get_dom_ldap_conn(ad_ctx, dom); + + return clist; +} + +errno_t ad_inherit_opts_if_needed(struct dp_option *parent_opts, + struct dp_option *subdom_opts, + struct confdb_ctx *cdb, + const char *subdom_conf_path, + int opt_id) +{ + int ret; + bool is_default = true; + char *dummy = NULL; + + switch (parent_opts[opt_id].type) { + case DP_OPT_STRING: + is_default = (dp_opt_get_cstring(parent_opts, opt_id) == NULL); + break; + case DP_OPT_BOOL: + /* For booleans it is hard to say if the option is set or not since + * both possible values are valid ones. So we check if the value is + * different from the default and skip if it is the default. In this + * case the sub-domain option would either be the default as well or + * manully set and in both cases we do not have to change it. */ + is_default = (parent_opts[opt_id].val.boolean + == parent_opts[opt_id].def_val.boolean); + break; + default: + DEBUG(SSSDBG_TRACE_FUNC, "Unsupported type, skipping.\n"); + } + + if (!is_default) { + ret = confdb_get_string(cdb, NULL, subdom_conf_path, + parent_opts[opt_id].opt_name, NULL, &dummy); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed.\n"); + goto done; + } + + if (dummy == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Option [%s] is set in parent domain but not set for " + "sub-domain, inheriting it from parent.\n", + parent_opts[opt_id].opt_name); + dp_option_inherit(opt_id, parent_opts, subdom_opts); + } + } + + ret = EOK; + +done: + talloc_free(dummy); + + return ret; +} + +errno_t +ad_options_switch_site(struct ad_options *ad_options, struct be_ctx *be_ctx, + const char *new_site, const char *new_forest) +{ + const char *site; + const char *forest; + errno_t ret; + + /* Switch forest. */ + if (new_forest != NULL + && (ad_options->current_forest == NULL + || strcmp(ad_options->current_forest, new_forest) != 0)) { + forest = talloc_strdup(ad_options, new_forest); + if (forest == NULL) { + return ENOMEM; + } + + talloc_zfree(ad_options->current_forest); + ad_options->current_forest = forest; + } + + if (new_site == NULL) { + return EOK; + } + + if (ad_options->current_site != NULL + && strcmp(ad_options->current_site, new_site) == 0) { + return EOK; + } + + site = talloc_strdup(ad_options, new_site); + if (site == NULL) { + return ENOMEM; + } + + talloc_zfree(ad_options->current_site); + ad_options->current_site = site; + + ret = sysdb_set_site(be_ctx->domain, ad_options->current_site); + if (ret != EOK) { + /* Not fatal. */ + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to store site information " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + return EOK; +} diff --git a/src/providers/ad/ad_common.h b/src/providers/ad/ad_common.h new file mode 100644 index 0000000..99cebe2 --- /dev/null +++ b/src/providers/ad/ad_common.h @@ -0,0 +1,258 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef AD_COMMON_H_ +#define AD_COMMON_H_ + +#include "util/util.h" +#include "providers/ldap/ldap_common.h" + +#define AD_SERVICE_NAME "AD" +#define AD_GC_SERVICE_NAME "AD_GC" +/* The port the Global Catalog runs on */ +#define AD_GC_PORT 3268 +#define AD_GC_LDAPS_PORT 3269 + +#define AD_AT_OBJECT_SID "objectSID" +#define AD_AT_DNS_DOMAIN "DnsDomain" +#define AD_AT_NT_VERSION "NtVer" +#define AD_AT_NETLOGON "netlogon" + +#define MASTER_DOMAIN_SID_FILTER "objectclass=domain" + +struct ad_options; + +enum ad_basic_opt { + AD_DOMAIN = 0, + AD_ENABLED_DOMAINS, + AD_SERVER, + AD_BACKUP_SERVER, + AD_HOSTNAME, + AD_KEYTAB, + AD_KRB5_REALM, + AD_ENABLE_DNS_SITES, + AD_ACCESS_FILTER, + AD_ENABLE_GC, + AD_GPO_ACCESS_CONTROL, + AD_GPO_IMPLICIT_DENY, + AD_GPO_IGNORE_UNREADABLE, + AD_GPO_CACHE_TIMEOUT, + AD_GPO_MAP_INTERACTIVE, + AD_GPO_MAP_REMOTE_INTERACTIVE, + AD_GPO_MAP_NETWORK, + AD_GPO_MAP_BATCH, + AD_GPO_MAP_SERVICE, + AD_GPO_MAP_PERMIT, + AD_GPO_MAP_DENY, + AD_GPO_DEFAULT_RIGHT, + AD_SITE, + AD_KRB5_CONFD_PATH, + AD_MAXIMUM_MACHINE_ACCOUNT_PASSWORD_AGE, + AD_MACHINE_ACCOUNT_PASSWORD_RENEWAL_OPTS, + AD_UPDATE_SAMBA_MACHINE_ACCOUNT_PASSWORD, + AD_USE_LDAPS, + AD_ALLOW_REMOTE_DOMAIN_LOCAL, + + AD_OPTS_BASIC /* opts counter */ +}; + +struct ad_id_ctx { + struct sdap_id_ctx *sdap_id_ctx; + struct sdap_id_conn_ctx *ldap_ctx; + struct sdap_id_conn_ctx *gc_ctx; + struct ad_options *ad_options; +}; + +struct ad_resolver_ctx { + struct sdap_resolver_ctx *sdap_resolver_ctx; + struct ad_id_ctx *ad_id_ctx; +}; + +struct ad_service { + struct sdap_service *sdap; + struct sdap_service *gc; + struct krb5_service *krb5_service; + const char *ldap_scheme; + int port; + int gc_port; +}; + +struct ad_options { + /* Common options */ + struct dp_option *basic; + struct ad_service *service; + + /* ID Provider */ + struct sdap_options *id; + struct ad_id_ctx *id_ctx; + + /* Auth and chpass Provider */ + struct krb5_ctx *auth_ctx; + + /* Dynamic DNS updates */ + struct be_resolv_ctx *be_res; + struct be_nsupdate_ctx *dyndns_ctx; + + /* Discovered site and forest names */ + const char *current_site; + const char *current_forest; +}; + +errno_t +ad_get_common_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sss_domain_info *dom, + struct ad_options **_opts); + +/* FIXME: ad_get_common_options and ad_create_options are + * similar. The later is subdomain specific. It may be + * good to merge the two into one more generic funtion. */ +struct ad_options *ad_create_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sss_domain_info *subdom); + +struct ad_options *ad_create_2way_trust_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + const char *realm, + struct sss_domain_info *subdom, + const char *hostname, + const char *keytab); + +struct ad_options *ad_create_1way_trust_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sss_domain_info *subdom, + const char *hostname, + const char *keytab, + const char *sasl_authid); + +errno_t ad_set_search_bases(struct sdap_options *id_opts, + struct sdap_domain *sdap); + +errno_t +ad_failover_init(TALLOC_CTX *mem_ctx, struct be_ctx *ctx, + const char *primary_servers, + const char *backup_servers, + const char *krb5_realm, + const char *ad_service, + const char *ad_gc_service, + const char *ad_domain, + bool use_kdcinfo, + bool ad_use_ldaps, + size_t n_lookahead_primary, + size_t n_lookahead_backup, + struct ad_service **_service); + +void +ad_failover_reset(struct be_ctx *bectx, + struct ad_service *adsvc); + +errno_t +ad_get_id_options(struct ad_options *ad_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sdap_options **_opts); +errno_t +ad_get_autofs_options(struct ad_options *ad_opts, + struct confdb_ctx *cdb, + const char *conf_path); +errno_t +ad_get_auth_options(TALLOC_CTX *mem_ctx, + struct ad_options *ad_opts, + struct be_ctx *bectx, + struct dp_option **_opts); + +errno_t +ad_get_dyndns_options(struct be_ctx *be_ctx, + struct ad_options *ad_opts); + +void ad_set_ssf_and_mech_for_ldaps(struct sdap_options *id_opts); + +struct ad_id_ctx * +ad_id_ctx_init(struct ad_options *ad_opts, struct be_ctx *bectx); + +errno_t +ad_resolver_ctx_init(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *ad_id_ctx, + struct ad_resolver_ctx **out_ctx); + +struct sdap_id_conn_ctx ** +ad_gc_conn_list(TALLOC_CTX *mem_ctx, struct ad_id_ctx *ad_ctx, + struct sss_domain_info *dom); + +struct sdap_id_conn_ctx ** +ad_ldap_conn_list(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *ad_ctx, + struct sss_domain_info *dom); + +struct sdap_id_conn_ctx ** +ad_user_conn_list(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *ad_ctx, + struct sss_domain_info *dom); + +struct sdap_id_conn_ctx * +ad_get_dom_ldap_conn(struct ad_id_ctx *ad_ctx, struct sss_domain_info *dom); + +/* AD dynamic DNS updates */ +errno_t ad_dyndns_init(struct be_ctx *be_ctx, + struct ad_options *ctx); + +errno_t ad_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx, + struct dp_method *dp_methods); + +errno_t ad_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx, + struct dp_method *dp_methods); + +errno_t ad_machine_account_password_renewal_init(struct be_ctx *be_ctx, + struct ad_options *ad_opts); + +errno_t netlogon_get_domain_info(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *reply, + bool check_next_nearest_site_as_well, + char **_flat_name, + char **_site, + char **_forest); + +errno_t ad_inherit_opts_if_needed(struct dp_option *parent_opts, + struct dp_option *suddom_opts, + struct confdb_ctx *cdb, + const char *subdom_conf_path, + int opt_id); + +errno_t ad_refresh_init(struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx); + +errno_t +ad_options_switch_site(struct ad_options *ad_options, struct be_ctx *be_ctx, + const char *new_site, const char *new_forest); +#endif /* AD_COMMON_H_ */ diff --git a/src/providers/ad/ad_domain_info.c b/src/providers/ad/ad_domain_info.c new file mode 100644 index 0000000..6338d41 --- /dev/null +++ b/src/providers/ad/ad_domain_info.c @@ -0,0 +1,477 @@ +/* + SSSD + + AD Domain Info Module + + Authors: + Sumit Bose + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include + +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ad/ad_domain_info.h" +#include "providers/ad/ad_common.h" +#include "util/util.h" + +errno_t netlogon_get_domain_info(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *reply, + bool check_next_nearest_site_as_well, + char **_flat_name, + char **_site, + char **_forest) +{ + errno_t ret; + struct ldb_message_element *el; + DATA_BLOB blob; + struct ndr_pull *ndr_pull = NULL; + enum ndr_err_code ndr_err; + struct netlogon_samlogon_response response; + TALLOC_CTX *tmp_ctx; + + ret = sysdb_attrs_get_el(reply, AD_AT_NETLOGON, &el); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_el() failed\n"); + return ret; + } + + if (el->num_values == 0) { + DEBUG(SSSDBG_OP_FAILURE, "netlogon has no value\n"); + return ENOENT; + } else if (el->num_values > 1) { + DEBUG(SSSDBG_OP_FAILURE, "More than one netlogon value?\n"); + return EIO; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + blob.data = el->values[0].data; + blob.length = el->values[0].length; + + /* The ndr_pull_* calls do not use ndr_pull as a talloc context to + * allocate memory but the second argument of ndr_pull_init_blob(). To + * make sure no memory is leaked here a temporary talloc context is + * needed. */ + ndr_pull = ndr_pull_init_blob(&blob, tmp_ctx); + if (ndr_pull == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ndr_pull_init_blob() failed.\n"); + ret = ENOMEM; + goto done; + } + + ndr_err = ndr_pull_netlogon_samlogon_response(ndr_pull, NDR_SCALARS, + &response); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(SSSDBG_OP_FAILURE, "ndr_pull_netlogon_samlogon_response() " + "failed [%d]\n", ndr_err); + ret = EBADMSG; + goto done; + } + + if (!(response.ntver & NETLOGON_NT_VERSION_5EX)) { + DEBUG(SSSDBG_OP_FAILURE, "Wrong version returned [%x]\n", + response.ntver); + ret = EBADMSG; + goto done; + } + + /* get flat domain name */ + if (_flat_name != NULL) { + if (response.data.nt5_ex.domain_name != NULL && + *response.data.nt5_ex.domain_name != '\0') { + *_flat_name = talloc_strdup(mem_ctx, + response.data.nt5_ex.domain_name); + if (*_flat_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "No netlogon flat domain name data available.\n"); + *_flat_name = NULL; + } + } + + + /* get forest */ + if (_forest != NULL) { + if (response.data.nt5_ex.forest != NULL && + *response.data.nt5_ex.forest != '\0') { + *_forest = talloc_strdup(mem_ctx, response.data.nt5_ex.forest); + if (*_forest == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "No netlogon forest data available.\n"); + *_forest = NULL; + } + } + + /* get site name */ + if (_site != NULL) { + if (response.data.nt5_ex.client_site != NULL + && response.data.nt5_ex.client_site[0] != '\0') { + *_site = talloc_strdup(mem_ctx, response.data.nt5_ex.client_site); + if (*_site == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "No netlogon site name data available.\n"); + *_site = NULL; + + if (check_next_nearest_site_as_well) { + if (response.data.nt5_ex.next_closest_site != NULL + && response.data.nt5_ex.next_closest_site[0] != '\0') { + *_site = talloc_strdup(mem_ctx, + response.data.nt5_ex.next_closest_site); + if (*_site == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "No netlogon next closest site name data " + "available.\n"); + } + } + } + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +struct ad_domain_info_state { + struct tevent_context *ev; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *id_op; + struct sdap_id_ctx *id_ctx; + struct sdap_options *opts; + struct sdap_domain *sdom; + + const char *dom_name; + int base_iter; + + char *flat; + char *site; + char *forest; + char *sid; +}; + +static errno_t ad_domain_info_next(struct tevent_req *req); +static void ad_domain_info_next_done(struct tevent_req *subreq); +static void ad_domain_info_netlogon_done(struct tevent_req *req); + +struct tevent_req * +ad_domain_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_conn_ctx *conn, + struct sdap_id_op *op, + const char *dom_name) +{ + errno_t ret; + struct tevent_req *req; + struct ad_domain_info_state *state; + + req = tevent_req_create(mem_ctx, &state, struct ad_domain_info_state); + if (!req) return NULL; + + state->ev = ev; + state->id_op = op; + state->conn = conn; + state->id_ctx = conn->id_ctx; + state->opts = conn->id_ctx->opts; + state->dom_name = dom_name; + state->sdom = sdap_domain_get_by_name(state->opts, state->dom_name); + /* The first domain in the list is the domain configured in sssd.conf and + * here it might be possible that the domain name from the config file and + * the DNS domain name do not match. All other sub-domains are discovered + * at runtime with the help of DNS lookups so it is expected that the + * names matches. Hence it makes sense to fall back to the first entry in + * the list if no matching domain was found since it is most probably + * related to the configured domain. */ + if (state->sdom == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No internal domain data found for [%s], " + "falling back to first domain.\n", + state->dom_name); + state->sdom = state->opts->sdom; + } + if (state->sdom == NULL || state->sdom->search_bases == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing internal domain data for domain [%s].\n", + state->dom_name); + ret = EINVAL; + goto immediate; + } + + + ret = ad_domain_info_next(req); + if (ret != EOK && ret != EAGAIN) { + goto immediate; + } + + return req; + +immediate: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t +ad_domain_info_next(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_search_base *base; + const char *master_sid_attrs[] = {AD_AT_OBJECT_SID, NULL}; + + struct ad_domain_info_state *state = + tevent_req_data(req, struct ad_domain_info_state); + + base = state->sdom->search_bases[state->base_iter]; + if (base == NULL) { + return EOK; + } + + subreq = sdap_get_generic_send(state, state->ev, + state->id_ctx->opts, + sdap_id_op_handle(state->id_op), + base->basedn, LDAP_SCOPE_BASE, + MASTER_DOMAIN_SID_FILTER, master_sid_attrs, + NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ad_domain_info_next_done, req); + + return EAGAIN; +} + +static void +ad_domain_info_next_done(struct tevent_req *subreq) +{ + errno_t ret; + size_t reply_count; + struct sysdb_attrs **reply = NULL; + struct ldb_message_element *el; + char *sid_str; + enum idmap_error_code err; + static const char *attrs[] = {AD_AT_NETLOGON, NULL}; + char *filter; + char *ntver; + + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_domain_info_state *state = + tevent_req_data(req, struct ad_domain_info_state); + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send request failed.\n"); + goto done; + } + + if (reply_count == 0) { + state->base_iter++; + ret = ad_domain_info_next(req); + if (ret == EAGAIN) { + /* Async request will get us back here again */ + return; + } else if (ret != EOK) { + goto done; + } + + /* EOK */ + tevent_req_done(req); + return; + } else if (reply_count == 1) { + ret = sysdb_attrs_get_el(reply[0], AD_AT_OBJECT_SID, &el); + if (ret != EOK || el->num_values != 1) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_attrs_get_el failed.\n"); + goto done; + } + + err = sss_idmap_bin_sid_to_sid(state->opts->idmap_ctx->map, + el->values[0].data, + el->values[0].length, + &sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert SID: [%s].\n", idmap_error_string(err)); + ret = EFAULT; + goto done; + } + + state->sid = talloc_steal(state, sid_str); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "More than one result for domain SID found.\n"); + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Found SID [%s].\n", state->sid); + + ntver = sss_ldap_encode_ndr_uint32(state, NETLOGON_NT_VERSION_5EX | + NETLOGON_NT_VERSION_WITH_CLOSEST_SITE); + if (ntver == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_ldap_encode_ndr_uint32 failed.\n"); + ret = ENOMEM; + goto done; + } + + filter = talloc_asprintf(state, "(&(%s=%s)(%s=%s))", + AD_AT_DNS_DOMAIN, state->dom_name, + AD_AT_NT_VERSION, ntver); + if (filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_get_generic_send(state, state->ev, + state->id_ctx->opts, + sdap_id_op_handle(state->id_op), + "", LDAP_SCOPE_BASE, filter, attrs, NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ad_domain_info_netlogon_done, req); + return; + +done: + tevent_req_error(req, ret); +} + +static void +ad_domain_info_netlogon_done(struct tevent_req *subreq) +{ + int ret; + size_t reply_count; + struct sysdb_attrs **reply = NULL; + + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_domain_info_state *state = + tevent_req_data(req, struct ad_domain_info_state); + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send request failed.\n"); + tevent_req_error(req, ret); + return; + } + + /* Failure to get the flat name is not fatal. Just quit. */ + if (reply_count == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "No netlogon data available. Flat name " \ + "might not be usable\n"); + goto done; + } else if (reply_count > 1) { + DEBUG(SSSDBG_MINOR_FAILURE, + "More than one netlogon info returned.\n"); + goto done; + } + + /* Exactly one flat name. Carry on */ + + ret = netlogon_get_domain_info(state, reply[0], false, &state->flat, + &state->site, &state->forest); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not get the flat name or forest: %d:[%s]\n", + ret, sss_strerror(ret)); + /* Not fatal. Just quit. */ + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Found flat name [%s].\n", state->flat); + DEBUG(SSSDBG_TRACE_FUNC, "Found site [%s].\n", state->site); + DEBUG(SSSDBG_TRACE_FUNC, "Found forest [%s].\n", state->forest); + +done: + tevent_req_done(req); + return; +} + +errno_t +ad_domain_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **_flat, + char **_id, + char **_site, + char **_forest) +{ + struct ad_domain_info_state *state = tevent_req_data(req, + struct ad_domain_info_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_flat) { + *_flat = talloc_steal(mem_ctx, state->flat); + } + + if (_site) { + *_site = talloc_steal(mem_ctx, state->site); + } + + if (_forest) { + *_forest = talloc_steal(mem_ctx, state->forest); + } + + if (_id) { + *_id = talloc_steal(mem_ctx, state->sid); + } + + return EOK; +} diff --git a/src/providers/ad/ad_domain_info.h b/src/providers/ad/ad_domain_info.h new file mode 100644 index 0000000..cf601cf --- /dev/null +++ b/src/providers/ad/ad_domain_info.h @@ -0,0 +1,42 @@ +/* + SSSD + + AD Master Domain Module + + Authors: + Sumit Bose + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _AD_DOMAIN_INFO_H_ +#define _AD_DOMAIN_INFO_H_ + +struct tevent_req * +ad_domain_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_conn_ctx *conn, + struct sdap_id_op *op, + const char *dom_name); + +errno_t +ad_domain_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **_flat, + char **_id, + char **_site, + char **_forest); +#endif /* _AD_DOMAIN_INFO_H_ */ diff --git a/src/providers/ad/ad_dyndns.c b/src/providers/ad/ad_dyndns.c new file mode 100644 index 0000000..1a91e4b --- /dev/null +++ b/src/providers/ad/ad_dyndns.c @@ -0,0 +1,295 @@ +/* + SSSD + + ad_dyndns.c + + Authors: + Jakub Hrozek + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include "util/util.h" +#include "providers/ldap/sdap_dyndns.h" +#include "providers/data_provider.h" +#include "providers/be_dyndns.h" +#include "providers/ad/ad_common.h" + +struct ad_dyndns_update_state { + struct ad_options *ad_ctx; + struct sdap_id_op *sdap_op; +}; + +static void +ad_dyndns_sdap_update_done(struct tevent_req *subreq); + +static struct tevent_req * +ad_dyndns_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); + +static errno_t +ad_dyndns_update_recv(struct tevent_req *req); + +static void +ad_dyndns_update_connect_done(struct tevent_req *subreq); + +errno_t ad_dyndns_init(struct be_ctx *be_ctx, + struct ad_options *ad_opts) +{ + errno_t ret; + const time_t ptask_first_delay = 10; + int period; + int offset; + uint32_t extraflags = 0; + + /* nsupdate is available. Dynamic updates + * are supported + */ + ret = ad_get_dyndns_options(be_ctx, ad_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not get AD dyndns options\n"); + return ret; + } + + if (dp_opt_get_bool(ad_opts->dyndns_ctx->opts, + DP_OPT_DYNDNS_UPDATE) == false) { + DEBUG(SSSDBG_CONF_SETTINGS, "Dynamic DNS updates are off.\n"); + return EOK; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Dynamic DNS updates are on. Checking for nsupdate..\n"); + ret = be_nsupdate_check(); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "DNS updates requested but nsupdate not available\n"); + return EOK; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not check for nsupdate\n"); + return ret; + } + + ad_opts->be_res = be_ctx->be_res; + if (ad_opts->be_res == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Resolver must be initialized in order " + "to use the AD dynamic DNS updates\n"); + return EINVAL; + } + + period = dp_opt_get_int(ad_opts->dyndns_ctx->opts, DP_OPT_DYNDNS_REFRESH_INTERVAL); + if (period == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "DNS will not be updated periodically, " + "dyndns_refresh_interval is 0\n"); + extraflags |= BE_PTASK_NO_PERIODIC; + offset = 0; + } else { + offset = dp_opt_get_int(ad_opts->dyndns_ctx->opts, DP_OPT_DYNDNS_REFRESH_OFFSET); + } + + ret = be_ptask_create(ad_opts, be_ctx, period, ptask_first_delay, 0, offset, + period, 0, + ad_dyndns_update_send, ad_dyndns_update_recv, ad_opts, + "Dyndns update", + extraflags | + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_SCHEDULE_FROM_LAST, + NULL); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup ptask " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + return ret; +} + +static struct tevent_req * +ad_dyndns_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + int ret; + struct ad_options *ctx; + struct ad_dyndns_update_state *state; + struct tevent_req *req, *subreq; + struct sdap_id_ctx *sdap_ctx; + + DEBUG(SSSDBG_TRACE_FUNC, "Performing update\n"); + + ctx = talloc_get_type(pvt, struct ad_options); + sdap_ctx = ctx->id_ctx->sdap_id_ctx; + + req = tevent_req_create(ctx, &state, struct ad_dyndns_update_state); + if (req == NULL) { + return NULL; + } + state->ad_ctx = ctx; + + if (ctx->dyndns_ctx->last_refresh + 60 > time(NULL) || + ctx->dyndns_ctx->timer_in_progress) { + DEBUG(SSSDBG_FUNC_DATA, "Last periodic update ran recently or timer " + "in progress, not scheduling another update\n"); + tevent_req_done(req); + tevent_req_post(req, sdap_ctx->be->ev); + return req; + } + state->ad_ctx->dyndns_ctx->last_refresh = time(NULL); + + /* Make sure to have a valid LDAP connection */ + state->sdap_op = sdap_id_op_create(state, sdap_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret)); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ad_dyndns_update_connect_done, req); + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, sdap_ctx->be->ev); + } + return req; +} + +static void ad_dyndns_update_connect_done(struct tevent_req *subreq) +{ + int dp_error; + int ret; + struct tevent_req *req; + struct ad_dyndns_update_state *state; + struct sdap_id_ctx *sdap_ctx; + struct ad_options *ctx; + LDAPURLDesc *lud; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_dyndns_update_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, "No server is available, " + "dynamic DNS update is skipped in offline mode.\n"); + tevent_req_error(req, ERR_DYNDNS_OFFLINE); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to LDAP server: [%d](%s)\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ERR_NETWORK_IO); + } + return; + } + + ctx = state->ad_ctx; + sdap_ctx = ctx->id_ctx->sdap_id_ctx; + + ret = ldap_url_parse(ctx->service->sdap->uri, &lud); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse ldap URI '%s': %d\n", + ctx->service->sdap->uri, ret); + ret = EINVAL; + goto done; + } + + if (lud->lud_scheme != NULL && + strcasecmp(lud->lud_scheme, "ldapi") == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "The LDAP scheme is ldapi://, cannot proceed with update\n"); + ldap_free_urldesc(lud); + ret = EINVAL; + goto done; + } + + if (lud->lud_host == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "The LDAP URI (%s) did not contain a host name\n", + ctx->service->sdap->uri); + ldap_free_urldesc(lud); + ret = EINVAL; + goto done; + } + + subreq = sdap_dyndns_update_send(state, sdap_ctx->be->ev, + sdap_ctx->be, + ctx->dyndns_ctx->opts, + sdap_ctx, + ctx->dyndns_ctx->auth_type, + ctx->dyndns_ctx->auth_ptr_type, + dp_opt_get_string(ctx->dyndns_ctx->opts, + DP_OPT_DYNDNS_IFACE), + dp_opt_get_string(ctx->basic, + AD_HOSTNAME), + dp_opt_get_string(ctx->basic, + AD_KRB5_REALM), + dp_opt_get_int(ctx->dyndns_ctx->opts, + DP_OPT_DYNDNS_TTL), + false); + if (!subreq) { + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, + "sdap_dyndns_update_send failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + tevent_req_set_callback(subreq, ad_dyndns_sdap_update_done, req); + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, sdap_ctx->be->ev); + } +} + +static void ad_dyndns_sdap_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + errno_t ret; + + ret = sdap_dyndns_update_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Dynamic DNS update failed [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ad_dyndns_update_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ad/ad_gpo.c b/src/providers/ad/ad_gpo.c new file mode 100644 index 0000000..94959c3 --- /dev/null +++ b/src/providers/ad/ad_gpo.c @@ -0,0 +1,5105 @@ +/* + SSSD + + Authors: + Yassir Elley + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +/* + * This file implements the following pair of *public* functions (see header): + * ad_gpo_access_send/recv: provides client-side GPO processing + * + * This file also implements the following pairs of *private* functions (which + * are used by the public functions): + * ad_gpo_process_som_send/recv: populate list of gp_som objects + * ad_gpo_process_gpo_send/recv: populate list of gp_gpo objects + * ad_gpo_process_cse_send/recv: retrieve policy file data + */ + +#include +#include +#include +#include +#include +#include "util/util.h" +#include "util/strtonum.h" +#include "util/child_common.h" +#include "providers/data_provider.h" +#include "providers/backend.h" +#include "providers/ad/ad_access.h" +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_domain_info.h" +#include "providers/ad/ad_gpo.h" +#include "providers/ldap/sdap_access.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_idmap.h" +#include "util/util_sss_idmap.h" +#include "util/sss_chain_id.h" +#include +#include + +/* == gpo-ldap constants =================================================== */ + +#define AD_AT_DN "distinguishedName" +#define AD_AT_UAC "userAccountControl" +#define AD_AT_SAMACCOUNTNAME "sAMAccountName" +#define AD_AT_CONFIG_NC "configurationNamingContext" +#define AD_AT_GPLINK "gPLink" +#define AD_AT_GPOPTIONS "gpOptions" +#define AD_AT_NT_SEC_DESC "nTSecurityDescriptor" +#define AD_AT_CN "cn" +#define AD_AT_FILE_SYS_PATH "gPCFileSysPath" +#define AD_AT_MACHINE_EXT_NAMES "gPCMachineExtensionNames" +#define AD_AT_FUNC_VERSION "gPCFunctionalityVersion" +#define AD_AT_FLAGS "flags" +#define AD_AT_SID "objectSid" + +#define UAC_WORKSTATION_TRUST_ACCOUNT 0x00001000 +#define UAC_SERVER_TRUST_ACCOUNT 0x00002000 +#define AD_AGP_GUID "edacfd8f-ffb3-11d1-b41d-00a0c968f939" +#define AD_AUTHENTICATED_USERS_SID "S-1-5-11" + +/* == gpo-smb constants ==================================================== */ + +#define SMB_STANDARD_URI "smb://" +#define BUFSIZE 65536 + +#define RIGHTS_SECTION "Privilege Rights" +#define ALLOW_LOGON_INTERACTIVE "SeInteractiveLogonRight" +#define DENY_LOGON_INTERACTIVE "SeDenyInteractiveLogonRight" +#define ALLOW_LOGON_REMOTE_INTERACTIVE "SeRemoteInteractiveLogonRight" +#define DENY_LOGON_REMOTE_INTERACTIVE "SeDenyRemoteInteractiveLogonRight" +#define ALLOW_LOGON_NETWORK "SeNetworkLogonRight" +#define DENY_LOGON_NETWORK "SeDenyNetworkLogonRight" +#define ALLOW_LOGON_BATCH "SeBatchLogonRight" +#define DENY_LOGON_BATCH "SeDenyBatchLogonRight" +#define ALLOW_LOGON_SERVICE "SeServiceLogonRight" +#define DENY_LOGON_SERVICE "SeDenyServiceLogonRight" + +#define GP_EXT_GUID_SECURITY "{827D319E-6EAC-11D2-A4EA-00C04F79F83A}" +#define GP_EXT_GUID_SECURITY_SUFFIX "/Machine/Microsoft/Windows NT/SecEdit/GptTmpl.inf" + +#ifndef SSSD_LIBEXEC_PATH +#error "SSSD_LIBEXEC_PATH not defined" +#else +#define GPO_CHILD SSSD_LIBEXEC_PATH"/gpo_child" +#endif + +#define GPO_CHILD_LOG_FILE "gpo_child" + +/* If INI_PARSE_IGNORE_NON_KVP is not defined, use 0 (no effect) */ +#ifndef INI_PARSE_IGNORE_NON_KVP +#define INI_PARSE_IGNORE_NON_KVP 0 +#warning INI_PARSE_IGNORE_NON_KVP not defined. +#endif + +/* == common data structures and declarations ============================= */ + +struct gp_som { + const char *som_dn; + struct gp_gplink **gplink_list; + int num_gplinks; +}; + +struct gp_gplink { + const char *gpo_dn; + bool enforced; +}; + +struct gp_gpo { + struct security_descriptor *gpo_sd; + const char *gpo_dn; + const char *gpo_guid; + const char *smb_server; + const char *smb_share; + const char *smb_path; + const char **gpo_cse_guids; + int num_gpo_cse_guids; + int gpo_func_version; + int gpo_flags; + bool send_to_child; + const char *policy_filename; +}; + +enum ace_eval_agp_status { + AD_GPO_ACE_DENIED, + AD_GPO_ACE_ALLOWED, + AD_GPO_ACE_NEUTRAL +}; + +struct tevent_req *ad_gpo_process_som_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_conn_ctx *conn, + struct ldb_context *ldb_ctx, + struct sdap_id_op *sdap_op, + struct sdap_options *opts, + struct dp_option *ad_options, + int timeout, + const char *target_dn, + const char *domain_name); +int ad_gpo_process_som_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct gp_som ***som_list); + +struct tevent_req * +ad_gpo_process_gpo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_op *sdap_op, + struct sdap_options *opts, + char *server_hostname, + struct sss_domain_info *host_domain, + struct ad_access_ctx *access_ctx, + int timeout, + struct gp_som **som_list); +int ad_gpo_process_gpo_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct gp_gpo ***candidate_gpos, + int *num_candidate_gpos); + +struct tevent_req *ad_gpo_process_cse_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + bool send_to_child, + struct sss_domain_info *domain, + const char *gpo_guid, + const char *smb_server, + const char *smb_share, + const char *smb_path, + const char *smb_cse_suffix, + int cached_gpt_version, + int gpo_timeout_option); + +int ad_gpo_process_cse_recv(struct tevent_req *req); + +/* == ad_gpo_parse_map_options and helpers ==================================*/ + +#define GPO_LOGIN "login" +#define GPO_SU "su" +#define GPO_SU_L "su-l" +#define GPO_GDM_FINGERPRINT "gdm-fingerprint" +#define GPO_GDM_PASSWORD "gdm-password" +#define GPO_GDM_SMARTCARD "gdm-smartcard" +#define GPO_KDM "kdm" +#define GPO_LIGHTDM "lightdm" +#define GPO_LXDM "lxdm" +#define GPO_SDDM "sddm" +#define GPO_UNITY "unity" +#define GPO_XDM "xdm" +#define GPO_SSHD "sshd" +#define GPO_FTP "ftp" +#define GPO_SAMBA "samba" +#ifdef HAVE_DEBIAN +#define GPO_CROND "cron" +#else +#define GPO_CROND "crond" +#endif +#define GPO_POLKIT "polkit-1" +#define GPO_SUDO "sudo" +#define GPO_SUDO_I "sudo-i" +#define GPO_SYSTEMD_USER "systemd-user" +#define GPO_COCKPIT "cockpit" + +struct gpo_map_option_entry { + enum gpo_map_type gpo_map_type; + enum ad_basic_opt ad_basic_opt; + const char **gpo_map_defaults; + const char *allow_key; + const char *deny_key; +}; + +const char *gpo_map_interactive_defaults[] = + {GPO_LOGIN, GPO_SU, GPO_SU_L, + GPO_GDM_FINGERPRINT, GPO_GDM_PASSWORD, GPO_GDM_SMARTCARD, GPO_KDM, + GPO_LIGHTDM, GPO_LXDM, GPO_SDDM, GPO_UNITY, GPO_XDM, NULL}; +const char *gpo_map_remote_interactive_defaults[] = {GPO_SSHD, GPO_COCKPIT, + NULL}; +const char *gpo_map_network_defaults[] = {GPO_FTP, GPO_SAMBA, NULL}; +const char *gpo_map_batch_defaults[] = {GPO_CROND, NULL}; +const char *gpo_map_service_defaults[] = {NULL}; +const char *gpo_map_permit_defaults[] = {GPO_POLKIT, + GPO_SUDO, GPO_SUDO_I, + GPO_SYSTEMD_USER, NULL}; +const char *gpo_map_deny_defaults[] = {NULL}; + +struct gpo_map_option_entry gpo_map_option_entries[] = { + {GPO_MAP_INTERACTIVE, AD_GPO_MAP_INTERACTIVE, gpo_map_interactive_defaults, + ALLOW_LOGON_INTERACTIVE, DENY_LOGON_INTERACTIVE}, + {GPO_MAP_REMOTE_INTERACTIVE, AD_GPO_MAP_REMOTE_INTERACTIVE, + gpo_map_remote_interactive_defaults, + ALLOW_LOGON_REMOTE_INTERACTIVE, DENY_LOGON_REMOTE_INTERACTIVE}, + {GPO_MAP_NETWORK, AD_GPO_MAP_NETWORK, gpo_map_network_defaults, + ALLOW_LOGON_NETWORK, DENY_LOGON_NETWORK}, + {GPO_MAP_BATCH, AD_GPO_MAP_BATCH, gpo_map_batch_defaults, + ALLOW_LOGON_BATCH, DENY_LOGON_BATCH}, + {GPO_MAP_SERVICE, AD_GPO_MAP_SERVICE, gpo_map_service_defaults, + ALLOW_LOGON_SERVICE, DENY_LOGON_SERVICE}, + {GPO_MAP_PERMIT, AD_GPO_MAP_PERMIT, gpo_map_permit_defaults, NULL, NULL}, + {GPO_MAP_DENY, AD_GPO_MAP_DENY, gpo_map_deny_defaults, NULL, NULL}, +}; + +static const char* gpo_map_type_string(int gpo_map_type) +{ + switch(gpo_map_type) { + case GPO_MAP_INTERACTIVE: return "Interactive"; + case GPO_MAP_REMOTE_INTERACTIVE: return "Remote Interactive"; + case GPO_MAP_NETWORK: return "Network"; + case GPO_MAP_BATCH: return "Batch"; + case GPO_MAP_SERVICE: return "Service"; + case GPO_MAP_PERMIT: return "Permitted"; + case GPO_MAP_DENY: return "Denied"; + } + return "-unknown-"; /* this helper is only used in logs */ +} + +static inline bool +ad_gpo_service_in_list(char **list, size_t nlist, const char *str) +{ + size_t i; + + for (i = 0; i < nlist; i++) { + if (strcasecmp(list[i], str) == 0) { + break; + } + } + + return (i < nlist) ? true : false; +} + +errno_t +ad_gpo_parse_map_option_helper(enum gpo_map_type gpo_map_type, + hash_key_t key, + hash_table_t *options_table) +{ + hash_value_t val; + int hret; + int ret; + + hret = hash_lookup(options_table, &key, &val); + if (hret != HASH_SUCCESS && hret != HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_OP_FAILURE, "Error checking hash table: [%s]\n", + hash_error_string(hret)); + ret = EINVAL; + goto done; + } else if (hret == HASH_SUCCESS) { + /* handle unexpected case where mapping for key already exists */ + if (val.i == gpo_map_type) { + /* mapping for key exists for same map type; no error */ + DEBUG(SSSDBG_TRACE_FUNC, + "PAM service %s maps to %s multiple times\n", key.str, + gpo_map_type_string(gpo_map_type)); + ret = EOK; + } else { + /* mapping for key exists for different map type; error! */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Configuration error: PAM service %s maps to both %s and " + "%s. If you are changing the default mappings of Group " + "Policy rules to PAM services using one of the ad_gpo_map_*" + " options make sure that the PAM service you add to one map " + "using the '+service' syntax is not already present in " + "another map by default (if it is then remove it from the " + "other map by using the '-service' syntax. Check manual " + "pages 'man sssd-ad' for details).\n", key.str, + gpo_map_type_string(val.i), gpo_map_type_string(gpo_map_type)); + sss_log(SSS_LOG_ERR, + "Configuration error: PAM service %s maps to both %s and " + "%s. If you are changing the default mappings of Group " + "Policy rules to PAM services using one of the ad_gpo_map_*" + " options make sure that the PAM service you add to one map " + "using the '+service' syntax is not already present in " + "another map by default (if it is then remove it from the " + "other map by using the '-service' syntax. Check manual " + "pages 'man sssd-ad' for details).\n", key.str, + gpo_map_type_string(val.i), gpo_map_type_string(gpo_map_type)); + ret = EINVAL; + } + goto done; + } else { + /* handle expected case where mapping for key doesn't already exist */ + val.type = HASH_VALUE_INT; + val.i = gpo_map_type; + + hret = hash_enter(options_table, &key, &val); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "Error checking hash table: [%s]\n", + hash_error_string(hret)); + ret = EIO; + goto done; + } + ret = EOK; + } + +done: + return ret; +} + +errno_t +ad_gpo_parse_map_option(TALLOC_CTX *mem_ctx, + enum gpo_map_type gpo_map_type, + hash_table_t *options_table, + char *conf_str, + const char **defaults) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char **conf_list = NULL; + int conf_list_size = 0; + char **add_list = NULL; + char **remove_list = NULL; + int ai = 0, ri = 0; + int i; + hash_key_t key; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "gpo_map_type: %s\n", + gpo_map_type_string(gpo_map_type)); + + if (conf_str) { + ret = split_on_separator(tmp_ctx, conf_str, ',', true, true, + &conf_list, &conf_list_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot parse list of service names %s: %d\n", conf_str, ret); + ret = EINVAL; + goto done; + } + + add_list = talloc_zero_array(tmp_ctx, char *, conf_list_size); + remove_list = talloc_zero_array(tmp_ctx, char *, conf_list_size); + if (add_list == NULL || remove_list == NULL) { + ret = ENOMEM; + goto done; + } + } + + for (i = 0; i < conf_list_size; i++) { + switch (conf_list[i][0]) { + case '+': + add_list[ai] = conf_list[i] + 1; + ai++; + continue; + case '-': + remove_list[ri] = conf_list[i] + 1; + ri++; + continue; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "ad_gpo_map values must start with" + "either '+' (for adding service) or '-' (for removing service), " + "got '%s'\n", + conf_list[i]); + ret = EINVAL; + goto done; + } + } + + /* Start by adding explicitly added services ('+') to hashtable */ + for (i = 0; i < ai; i++) { + /* if the service is explicitly configured to be removed, skip it */ + if (ad_gpo_service_in_list(remove_list, ri, add_list[i])) { + continue; + } + + key.type = HASH_KEY_STRING; + key.str = (char *)add_list[i]; + + ret = ad_gpo_parse_map_option_helper(gpo_map_type, key, options_table); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid configuration: %d\n", ret); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Explicitly added service: %s\n", key.str); + } + + /* Add defaults to hashtable */ + for (i = 0; defaults[i]; i++) { + /* if the service is explicitly configured to be removed, skip it */ + if (ad_gpo_service_in_list(remove_list, ri, defaults[i])) { + continue; + } + + key.type = HASH_KEY_STRING; + key.str = talloc_strdup(mem_ctx, defaults[i]); + + ret = ad_gpo_parse_map_option_helper(gpo_map_type, key, options_table); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid configuration: %d\n", ret); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Default service (not explicitly removed): %s\n", + key.str); + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ad_gpo_parse_map_options(struct ad_access_ctx *access_ctx) +{ + char *gpo_default_right_config; + enum gpo_map_type gpo_default_right; + errno_t ret; + int i; + + for (i = 0; i < GPO_MAP_NUM_OPTS; i++) { + + struct gpo_map_option_entry entry = gpo_map_option_entries[i]; + + char *entry_config = dp_opt_get_string(access_ctx->ad_options, + entry.ad_basic_opt); + + ret = ad_gpo_parse_map_option(access_ctx, entry.gpo_map_type, + access_ctx->gpo_map_options_table, + entry_config, entry.gpo_map_defaults); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid configuration: %d\n", ret); + ret = EINVAL; + goto fail; + } + } + + /* default right (applicable for services without any mapping) */ + gpo_default_right_config = + dp_opt_get_string(access_ctx->ad_options, AD_GPO_DEFAULT_RIGHT); + + DEBUG(SSSDBG_TRACE_ALL, "gpo_default_right_config: %s\n", + gpo_default_right_config); + + /* if default right not set in config, set them to DENY */ + if (gpo_default_right_config == NULL) { + gpo_default_right = GPO_MAP_DENY; + } else if (strncasecmp(gpo_default_right_config, "interactive", + strlen("interactive")) == 0) { + gpo_default_right = GPO_MAP_INTERACTIVE; + } else if (strncasecmp(gpo_default_right_config, "remote_interactive", + strlen("remote_interactive")) == 0) { + gpo_default_right = GPO_MAP_REMOTE_INTERACTIVE; + } else if (strncasecmp(gpo_default_right_config, "network", + strlen("network")) == 0) { + gpo_default_right = GPO_MAP_NETWORK; + } else if (strncasecmp(gpo_default_right_config, "batch", + strlen("batch")) == 0) { + gpo_default_right = GPO_MAP_BATCH; + } else if (strncasecmp(gpo_default_right_config, "service", + strlen("service")) == 0) { + gpo_default_right = GPO_MAP_SERVICE; + } else if (strncasecmp(gpo_default_right_config, "permit", + strlen("permit")) == 0) { + gpo_default_right = GPO_MAP_PERMIT; + } else if (strncasecmp(gpo_default_right_config, "deny", + strlen("deny")) == 0) { + gpo_default_right = GPO_MAP_DENY; + } else { + ret = EINVAL; + goto fail; + } + + DEBUG(SSSDBG_TRACE_ALL, "gpo_default_right: %d\n", gpo_default_right); + access_ctx->gpo_default_right = gpo_default_right; + +fail: + return ret; +} + +/* == ad_gpo_access_send/recv helpers =======================================*/ + +static bool +ad_gpo_dom_sid_equal(const struct dom_sid *sid1, const struct dom_sid *sid2) +{ + int i; + + if (sid1 == sid2) { + return true; + } + + if (!sid1 || !sid2) { + return false; + } + + if (sid1->sid_rev_num != sid2->sid_rev_num) { + return false; + } + + for (i = 0; i < 6; i++) { + if (sid1->id_auth[i] != sid2->id_auth[i]) { + return false; + } + } + + if (sid1->num_auths != sid2->num_auths) { + return false; + } + + for (i = 0; i < sid1->num_auths; i++) { + if (sid1->sub_auths[i] != sid2->sub_auths[i]) { + return false; + } + } + + return true; +} + +/* + * This function retrieves the SID of the group with given gid. + */ +static char * +ad_gpo_get_primary_group_sid(TALLOC_CTX *mem_ctx, + gid_t gid, + struct sss_domain_info *domain, + struct sss_idmap_ctx *idmap_ctx) +{ + char *idmap_sid = NULL; + const char *cache_sid; + char *result; + const char *attrs[] = { + SYSDB_SID_STR, + NULL + }; + struct ldb_message *msg; + int ret; + + if (gid == 0) { + return NULL; + } + + ret = sss_idmap_unix_to_sid(idmap_ctx, gid, &idmap_sid); + if (ret == EOK) { + result = talloc_strdup(mem_ctx, idmap_sid); + sss_idmap_free_sid(idmap_ctx, idmap_sid); + if (result == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Out of memory while getting SID of the group\n"); + } + return result; + } + + if (ret == IDMAP_EXTERNAL) { + /* no ID mapping in this domain, search for the group object and get sid there */ + ret = sysdb_search_group_by_gid(mem_ctx, domain, gid, attrs, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Search for group '%"SPRIgid"' failded with error '%d'\n", gid, ret); + return NULL; + } + + cache_sid = ldb_msg_find_attr_as_string(msg, SYSDB_SID_STR, NULL); + if (cache_sid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get SID attribute of the group '%"SPRIgid"'\n", gid); + return NULL; + } + + result = talloc_strdup(mem_ctx, cache_sid); + if (result == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Out of memory while getting group SID\n"); + } + return result; + } + + DEBUG(SSSDBG_OP_FAILURE, "Failed to get SID of primary the group '%"SPRIgid"'\n", gid); + return NULL; +} + +/* + * This function retrieves the SIDs corresponding to the input user and returns + * the user_sid, group_sids, and group_size in their respective output params. + * + * Note: since authentication must complete successfully before the + * gpo access checks are called, we can safely assume that the user/computer + * has been authenticated. As such, this function always adds the + * AD_AUTHENTICATED_USERS_SID to the group_sids. + */ +static errno_t +ad_gpo_get_sids(TALLOC_CTX *mem_ctx, + const char *user, + struct sss_domain_info *domain, + struct sss_idmap_ctx *idmap_ctx, + const char **_user_sid, + const char ***_group_sids, + int *_group_size) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_result *res; + int ret = 0; + int i = 0; + int num_group_sids = 0; + const char *user_sid = NULL; + const char *group_sid = NULL; + const char **group_sids = NULL; + gid_t orig_gid = 0; + char *orig_gid_sid = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + /* first result from sysdb_initgroups is user_sid; rest are group_sids */ + ret = sysdb_initgroups(tmp_ctx, domain, user, &res); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_initgroups failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + if (res->count == 0) { + ret = ENOENT; + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_initgroups returned empty result\n"); + goto done; + } + + user_sid = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_SID_STR, NULL); + + /* if there is origPrimaryGroupGidNumber, it's SID must be added to list */ + orig_gid = ldb_msg_find_attr_as_uint64(res->msgs[0], + SYSDB_PRIMARY_GROUP_GIDNUM, + 0); + orig_gid_sid = ad_gpo_get_primary_group_sid(tmp_ctx, + orig_gid, + domain, + idmap_ctx); + DEBUG(SSSDBG_TRACE_INTERNAL, "SID of the primary group with gid '%"SPRIgid"' is '%s'\n", orig_gid, orig_gid_sid); + + num_group_sids = (res->count) - 1; + + /* include space for AD_AUTHENTICATED_USERS_SID, original GID sid and NULL */ + group_sids = talloc_array(tmp_ctx, const char *, num_group_sids + 3); + if (group_sids == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_group_sids; i++) { + group_sid = ldb_msg_find_attr_as_string(res->msgs[i+1], + SYSDB_SID_STR, NULL); + if (group_sid == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing SID for cache entry [%s].\n", + ldb_dn_get_linearized(res->msgs[i+1]->dn)); + ret = EINVAL; + goto done; + } + + group_sids[i] = talloc_steal(group_sids, group_sid); + if (group_sids[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + group_sids[i++] = talloc_strdup(group_sids, AD_AUTHENTICATED_USERS_SID); + if (orig_gid_sid != NULL) { + group_sids[i++] = orig_gid_sid; + } + group_sids[i] = NULL; + + *_group_size = i; + *_group_sids = talloc_steal(mem_ctx, group_sids); + *_user_sid = talloc_steal(mem_ctx, user_sid); + ret = EOK; + + done: + talloc_free(tmp_ctx); + return ret; +} + +/* + * This function determines whether the input ace_dom_sid matches any of the + * client's SIDs. The boolean result is assigned to the _included output param. + */ +static errno_t +ad_gpo_ace_includes_client_sid(const char *user_sid, + const char *host_sid, + const char **group_sids, + int group_size, + const char **host_group_sids, + int host_group_size, + struct dom_sid ace_dom_sid, + struct sss_idmap_ctx *idmap_ctx, + bool *_included) +{ + int i = 0; + struct dom_sid *user_dom_sid; + struct dom_sid *host_dom_sid; + struct dom_sid *group_dom_sid; + enum idmap_error_code err; + bool included = false; + + err = sss_idmap_sid_to_smb_sid(idmap_ctx, user_sid, &user_dom_sid); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_idmap_sid_to_smb_sid() failed for user_sid '%s': %d\n", + user_sid, err); + return EFAULT; + } + + included = ad_gpo_dom_sid_equal(&ace_dom_sid, user_dom_sid); + sss_idmap_free_smb_sid(idmap_ctx, user_dom_sid); + if (included) { + *_included = true; + return EOK; + } + + err = sss_idmap_sid_to_smb_sid(idmap_ctx, host_sid, &host_dom_sid); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_idmap_sid_to_smb_sid() failed for host_sid '%s': %d\n", + host_sid, err); + return EFAULT; + } + + included = ad_gpo_dom_sid_equal(&ace_dom_sid, host_dom_sid); + sss_idmap_free_smb_sid(idmap_ctx, host_dom_sid); + if (included) { + *_included = true; + return EOK; + } + + for (i = 0; i < group_size; i++) { + err = sss_idmap_sid_to_smb_sid(idmap_ctx, group_sids[i], &group_dom_sid); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_idmap_sid_to_smb_sid() failed for group_sid '%s': %d\n", + group_sids[i], err); + return EFAULT; + } + included = ad_gpo_dom_sid_equal(&ace_dom_sid, group_dom_sid); + sss_idmap_free_smb_sid(idmap_ctx, group_dom_sid); + if (included) { + *_included = true; + return EOK; + } + } + + for (i = 0; i < host_group_size; i++) { + err = sss_idmap_sid_to_smb_sid(idmap_ctx, host_group_sids[i], &group_dom_sid); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_idmap_sid_to_smb_sid() failed for group_sid '%s': %d\n", + group_sids[i], err); + return EFAULT; + } + included = ad_gpo_dom_sid_equal(&ace_dom_sid, group_dom_sid); + sss_idmap_free_smb_sid(idmap_ctx, group_dom_sid); + if (included) { + *_included = true; + return EOK; + } + } + + *_included = false; + return EOK; +} + +/* + * This function determines whether use of the extended right named + * "ApplyGroupPolicy" (AGP) is allowed for the GPO, by comparing the + * specified user_sid and group_sids against the passed access control + * entry (ACE). + * This function returns ALLOWED, DENIED, or NEUTRAL depending on whether + * the ACE explicitly allows, explicitly denies, or does neither. + * + * Notes: + * (1) Abbreviation 'M' used in the evaluation algorithm stands for + * "access_mask", which represents the set of access rights associated with + * the passed ACE. The access right of interest to the GPO code is + * RIGHT_DS_CONTROL_ACCESS, which serves as a container for all control access + * rights. The specific control access right is identified by a GUID in the + * ACE's ObjectType. In our case, this is the GUID corresponding to AGP. + * (2) ACE that require an evaluation algorithm different from [MS-ADTS] + * 5.1.3.3.4, e. g. RIGHT_DS_CONTROL_ACCESS (CR) is not present in M, are + * ignored. + * + * The ACE evaluation algorithm is specified in [MS-ADTS] 5.1.3.3.4: + * Evaluate the DACL by examining each ACE in sequence, starting with the first + * ACE. Perform the following sequence of actions for each ACE in the order as + * shown: + * 1. If the "Inherit Only" (IO) flag is set in the ACE, skip the ACE. + * 2. If the SID in the ACE does not match any SID in the requester's + * security context, skip the ACE. + * 3. If the ACE type is "Object Access Allowed", the access right + * RIGHT_DS_CONTROL_ACCESS (CR) is present in M, and the ObjectType + * field in the ACE is not present, then grant the requested control + * access right. Stop any further access checks. + * 4. If the ACE type is "Object Access Allowed" the access right + * RIGHT_DS_CONTROL_ACCESS (CR) is present in M, and the ObjectType + * field in the ACE contains a GUID value equal to AGP, then grant + * the requested control access right. Stop any further access checks. + * 5. If the ACE type is "Object Access Denied", the access right + * RIGHT_DS_CONTROL_ACCESS (CR) is present in M, and the ObjectType + * field in the ACE is not present, then deny the requested control + * access right. Stop any further access checks. + * 6. If the ACE type is "Object Access Denied" the access right + * RIGHT_DS_CONTROL_ACCESS (CR) is present in M, and the ObjectType + * field in the ACE contains a GUID value equal to AGP, then deny + * the requested control access right. Stop any further access checks. + */ +static enum ace_eval_agp_status ad_gpo_evaluate_ace(struct security_ace *ace, + struct sss_idmap_ctx *idmap_ctx, + const char *user_sid, + const char *host_sid, + const char **group_sids, + int group_size, + const char **host_group_sids, + int host_group_size) +{ + bool included = false; + int ret = 0; + struct security_ace_object object; + struct GUID ext_right_agp_guid; + + if (ace->flags & SEC_ACE_FLAG_INHERIT_ONLY) { + return AD_GPO_ACE_NEUTRAL; + } + + ret = ad_gpo_ace_includes_client_sid(user_sid, host_sid, group_sids, + group_size, host_group_sids, + host_group_size, ace->trustee, + idmap_ctx, &included); + if (ret != EOK) { + return AD_GPO_ACE_DENIED; + } + + if (!included) { + return AD_GPO_ACE_NEUTRAL; + } + + if (ace->access_mask & SEC_ADS_CONTROL_ACCESS) { + object = ace->object.object; + if (object.flags & SEC_ACE_OBJECT_TYPE_PRESENT) { + GUID_from_string(AD_AGP_GUID, &ext_right_agp_guid); + if (!GUID_equal(&object.type.type, &ext_right_agp_guid)) { + return AD_GPO_ACE_NEUTRAL; + } + } + if (ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT) { + return AD_GPO_ACE_ALLOWED; + } else if (ace->type == SEC_ACE_TYPE_ACCESS_DENIED_OBJECT) { + return AD_GPO_ACE_DENIED; + } + } + + return AD_GPO_ACE_NEUTRAL; +} + +/* + * This function evaluates, which standard access rights the passed access + * control entry (ACE) allows or denies for the entire GPO. + * + * Notes: + * (1) Abbreviation 'M' used in the evaluation algorithm stands for + * "access_mask", which represents the set of access rights associated with + * the passed ACE. + * (2) Abbreviation 'G' used in the evaluation algorithm stands for + * "granted rights", which represents the set of access rights, that + * have already been granted by previously evaluated ACEs. + * (3) Abbreviation 'D' used in the evaluation algorithm stands for + * "denied rights", which represents the set of access rights, that + * have already been explicitly denied by previously evaluated ACEs. + * + * The simple ACE evaluation algorithm is specified in [MS-ADTS] 5.1.3.3.2: + * Evaluate the DACL by examining each ACE in sequence, starting with the first + * ACE. Perform the following sequence of actions for each ACE in the order as + * shown: + * 1. If the "Inherit Only" (IO) flag is set in the ACE, skip the ACE. + * 2. If the SID in the ACE does not match any SID in the requester's + * security context, skip the ACE. + * 3. If the ACE type is "Access Denied" and the access rights in M + * are not in G, then add the rights in M to D. + * 4. If the ACE type is "Access Allowed" and the access rights in M + * are not in D, then add the rights in M to G. + */ +static errno_t ad_gpo_simple_evaluate_ace(struct security_ace *ace, + struct sss_idmap_ctx *idmap_ctx, + const char *user_sid, + const char *host_sid, + const char **group_sids, + int group_size, + const char **host_group_sids, + int host_group_size, + uint32_t *_gpo_access_granted_status, + uint32_t *_gpo_access_denied_status) +{ + bool included = false; + uint32_t filtered_access_rights = 0; + int ret = 0; + + if (ace->flags & SEC_ACE_FLAG_INHERIT_ONLY) { + return EOK; + } + + ret = ad_gpo_ace_includes_client_sid(user_sid, host_sid, group_sids, group_size, + host_group_sids, host_group_size, + ace->trustee, idmap_ctx, &included); + + if (ret != EOK || !included) { + return ret; + } + + if (ace->type == SEC_ACE_TYPE_ACCESS_DENIED) { + filtered_access_rights = ace->access_mask & ~*_gpo_access_granted_status; + *_gpo_access_denied_status |= filtered_access_rights; + } else if (ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED) { + filtered_access_rights = ace->access_mask & ~*_gpo_access_denied_status; + *_gpo_access_granted_status |= filtered_access_rights; + } + + return ret; +} + + +/* + * This function extracts the GPO's DACL (discretionary access control list) + * from the GPO's specified security descriptor, and determines whether + * the GPO is applicable to the policy target, by comparing the specified + * user_sid and group_sids against each access control entry (ACE) in the DACL. + * The GPO is only applicable to the target, if the requester has been granted + * read access (RIGHT_DS_READ_PROPERTY) to the properties of the GPO and + * control access (RIGHT_DS_CONTROL_ACCESS) to apply the GPO (AGP). + * The required read and control access rights for a particular trustee are + * usually located in different ACEs, i.e. one ACE for control of read access + * and one for control access. + * If it comes to the end of the DACL, and the required access is still not + * explicitly allowed or denied, SSSD denies access to the object as specified + * in [MS-ADTS] 5.1.3.1. + */ +static errno_t ad_gpo_evaluate_dacl(struct security_acl *dacl, + struct sss_idmap_ctx *idmap_ctx, + const char *user_sid, + const char *host_sid, + const char **group_sids, + int group_size, + const char **host_group_sids, + int host_group_size, + bool *_dacl_access_allowed) +{ + uint32_t num_aces = 0; + uint32_t access_granted_status = 0; + uint32_t access_denied_status = 0; + enum ace_eval_agp_status ace_status; + struct security_ace *ace = NULL; + int i = 0; + int ret = 0; + enum idmap_error_code err; + char *trustee_dom_sid_str = NULL; + + num_aces = dacl->num_aces; + + /* + * [MS-ADTS] 5.1.3.3.2. and 5.1.3.3.4: + * If the DACL does not have any ACE, then deny the requester the + * requested control access right. + */ + if (num_aces == 0) { + *_dacl_access_allowed = false; + return EOK; + } + + /* + * [MS-GOPD] 2.4: + * To process a policy that applies to a Group Policy client, the core + * Group Policy engine must be able to read the policy data from the + * directory service so that the policy settings can be applied to the + * Group Policy client or the interactive user. + */ + for (i = 0; i < dacl->num_aces; i++) { + ace = &dacl->aces[i]; + + ret = ad_gpo_simple_evaluate_ace(ace, idmap_ctx, user_sid, host_sid, + group_sids, group_size, + host_group_sids, host_group_size, + &access_granted_status, + &access_denied_status); + + if (ret != EOK) { + err = sss_idmap_smb_sid_to_sid(idmap_ctx, &ace->trustee, + &trustee_dom_sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_idmap_smb_sid_to_sid failed.\n"); + return EFAULT; + } + + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not determine if ACE is applicable; " + " Trustee: %s\n", trustee_dom_sid_str); + sss_idmap_free_sid(idmap_ctx, trustee_dom_sid_str); + trustee_dom_sid_str = NULL; + continue; + } + } + + for (i = 0; i < dacl->num_aces; i ++) { + ace = &dacl->aces[i]; + + err = sss_idmap_smb_sid_to_sid(idmap_ctx, &ace->trustee, + &trustee_dom_sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "sss_idmap_smb_sid_to_sid failed.\n"); + return EFAULT; + } + + ace_status = ad_gpo_evaluate_ace(ace, idmap_ctx, user_sid, host_sid, + group_sids, group_size, + host_group_sids, host_group_size); + + switch (ace_status) { + case AD_GPO_ACE_NEUTRAL: + break; + case AD_GPO_ACE_ALLOWED: + if (access_granted_status & SEC_ADS_READ_PROP) { + *_dacl_access_allowed = true; + sss_idmap_free_sid(idmap_ctx, trustee_dom_sid_str); + return EOK; + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "GPO read properties access denied (security); " + " Trustee: %s\n", trustee_dom_sid_str); + break; + } + case AD_GPO_ACE_DENIED: + if (access_granted_status & SEC_ADS_READ_PROP) { + DEBUG(SSSDBG_TRACE_FUNC, + "GPO denied (security); " + " Trustee: %s\n", trustee_dom_sid_str); + sss_idmap_free_sid(idmap_ctx, trustee_dom_sid_str); + *_dacl_access_allowed = false; + return EOK; + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "GPO read properties access denied (security); " + " Trustee: %s\n", trustee_dom_sid_str); + break; + } + } + sss_idmap_free_sid(idmap_ctx, trustee_dom_sid_str); + trustee_dom_sid_str = NULL; + } + + if (access_granted_status & SEC_ADS_READ_PROP) { + DEBUG(SSSDBG_TRACE_FUNC, + "GPO apply group policy access denied (security)\n"); + } + + *_dacl_access_allowed = false; + return EOK; +} + +/* + * This function takes candidate_gpos as input, filters out any gpo that is + * not applicable to the policy target and assigns the result to the + * _dacl_filtered_gpos output parameter. The filtering algorithm is + * defined in [MS-GPOL] 3.2.5.1.6 + */ +static errno_t +ad_gpo_filter_gpos_by_dacl(TALLOC_CTX *mem_ctx, + const char *user, + const char *host_fqdn, + struct sss_domain_info *domain, + struct sss_domain_info *host_domain, + struct sss_idmap_ctx *idmap_ctx, + struct gp_gpo **candidate_gpos, + int num_candidate_gpos, + struct gp_gpo ***_dacl_filtered_gpos, + int *_num_dacl_filtered_gpos) +{ + TALLOC_CTX *tmp_ctx = NULL; + int i = 0; + int ret = 0; + struct gp_gpo *candidate_gpo = NULL; + struct security_descriptor *sd = NULL; + struct security_acl *dacl = NULL; + const char *user_sid = NULL; + const char **group_sids = NULL; + int group_size = 0; + const char *host_sid = NULL; + const char **host_group_sids = NULL; + int host_group_size = 0; + int gpo_dn_idx = 0; + bool access_allowed = false; + struct gp_gpo **dacl_filtered_gpos = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ad_gpo_get_sids(tmp_ctx, user, domain, idmap_ctx, &user_sid, + &group_sids, &group_size); + if (ret != EOK) { + ret = ERR_NO_SIDS; + DEBUG(SSSDBG_OP_FAILURE, + "Unable to retrieve SIDs: [%d](%s)\n", ret, sss_strerror(ret)); + goto done; + } + + ret = ad_gpo_get_sids(tmp_ctx, host_fqdn, host_domain, idmap_ctx, &host_sid, + &host_group_sids, &host_group_size); + if (ret != EOK) { + ret = ERR_NO_SIDS; + DEBUG(SSSDBG_OP_FAILURE, + "Unable to retrieve host SIDs: [%d](%s)\n", ret, sss_strerror(ret)); + goto done; + } + + dacl_filtered_gpos = talloc_array(tmp_ctx, + struct gp_gpo *, + num_candidate_gpos + 1); + + if (dacl_filtered_gpos == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_candidate_gpos; i++) { + + access_allowed = false; + candidate_gpo = candidate_gpos[i]; + + DEBUG(SSSDBG_TRACE_FUNC, "examining dacl candidate_gpo_guid:%s\n", + candidate_gpo->gpo_guid); + + /* gpo_func_version must be set to version 2 */ + if (candidate_gpo->gpo_func_version != 2) { + DEBUG(SSSDBG_TRACE_FUNC, + "GPO not applicable to target per security filtering: " + "gPCFunctionalityVersion is not 2\n"); + continue; + } + + sd = candidate_gpo->gpo_sd; + if (sd == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Security descriptor is missing\n"); + ret = EINVAL; + goto done; + } + + dacl = candidate_gpo->gpo_sd->dacl; + + /* gpo_flags value of 2 means that GPO's computer portion is disabled */ + if (candidate_gpo->gpo_flags == 2) { + DEBUG(SSSDBG_TRACE_FUNC, + "GPO not applicable to target per security filtering: " + "GPO's computer portion is disabled\n"); + continue; + } + + if ((sd->type & SEC_DESC_DACL_PRESENT) && (dacl != NULL)) { + ret = ad_gpo_evaluate_dacl(dacl, idmap_ctx, user_sid, host_sid, + group_sids, group_size, host_group_sids, + host_group_size, &access_allowed); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not determine if GPO is applicable\n"); + continue; + } + } else { + /* + * [MS-ADTS] 5.1.3.3.4: + * If the security descriptor has no DACL or its "DACL Present" bit + * is not set, then grant requester the requested control access right. + */ + + DEBUG(SSSDBG_TRACE_ALL, "DACL is not present\n"); + access_allowed = true; + } + + if (access_allowed) { + DEBUG(SSSDBG_TRACE_FUNC, + "GPO applicable to target per security filtering\n"); + dacl_filtered_gpos[gpo_dn_idx] = talloc_steal(dacl_filtered_gpos, + candidate_gpo); + gpo_dn_idx++; + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "GPO not applicable to target per security filtering: " + "result of DACL evaluation\n"); + continue; + } + } + + dacl_filtered_gpos[gpo_dn_idx] = NULL; + + *_dacl_filtered_gpos = talloc_steal(mem_ctx, dacl_filtered_gpos); + *_num_dacl_filtered_gpos = gpo_dn_idx; + + ret = EOK; + + done: + talloc_free(tmp_ctx); + return ret; +} + +/* + * This function determines whether the input cse_guid matches any of the input + * gpo_cse_guids. The boolean result is assigned to the _included output param. + */ +static bool +ad_gpo_includes_cse_guid(const char *cse_guid, + const char **gpo_cse_guids, + int num_gpo_cse_guids) +{ + int i = 0; + const char *gpo_cse_guid = NULL; + + for (i = 0; i < num_gpo_cse_guids; i++) { + gpo_cse_guid = gpo_cse_guids[i]; + if (strcmp(gpo_cse_guid, cse_guid) == 0) { + return true; + } + } + + return false; +} + +/* + * This function takes an input dacl_filtered_gpos list, filters out any gpo + * that does not contain the input cse_guid, and assigns the result to the + * _cse_filtered_gpos output parameter. + */ +static errno_t +ad_gpo_filter_gpos_by_cse_guid(TALLOC_CTX *mem_ctx, + const char *cse_guid, + struct gp_gpo **dacl_filtered_gpos, + int num_dacl_filtered_gpos, + struct gp_gpo ***_cse_filtered_gpos, + int *_num_cse_filtered_gpos) +{ + TALLOC_CTX *tmp_ctx = NULL; + int i = 0; + int ret = 0; + struct gp_gpo *dacl_filtered_gpo = NULL; + int gpo_dn_idx = 0; + struct gp_gpo **cse_filtered_gpos = NULL; + bool included; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cse_filtered_gpos = talloc_array(tmp_ctx, + struct gp_gpo *, + num_dacl_filtered_gpos + 1); + if (cse_filtered_gpos == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_dacl_filtered_gpos; i++) { + + dacl_filtered_gpo = dacl_filtered_gpos[i]; + + DEBUG(SSSDBG_TRACE_ALL, "examining cse candidate_gpo_guid: %s\n", + dacl_filtered_gpo->gpo_guid); + + included = ad_gpo_includes_cse_guid(cse_guid, + dacl_filtered_gpo->gpo_cse_guids, + dacl_filtered_gpo->num_gpo_cse_guids); + + if (included) { + DEBUG(SSSDBG_TRACE_ALL, + "GPO applicable to target per cse_guid filtering\n"); + cse_filtered_gpos[gpo_dn_idx] = talloc_steal(cse_filtered_gpos, + dacl_filtered_gpo); + dacl_filtered_gpos[i] = NULL; + gpo_dn_idx++; + } else { + DEBUG(SSSDBG_TRACE_ALL, + "GPO not applicable to target per cse_guid filtering\n"); + continue; + } + } + + cse_filtered_gpos[gpo_dn_idx] = NULL; + + *_cse_filtered_gpos = talloc_steal(mem_ctx, cse_filtered_gpos); + *_num_cse_filtered_gpos = gpo_dn_idx; + + ret = EOK; + + done: + talloc_free(tmp_ctx); + return ret; +} + +/* + * This cse-specific function (GP_EXT_GUID_SECURITY) returns a boolean value + * based on whether the input user_sid or any of the input group_sids appear + * in the input list of privilege_sids. + */ +static bool +check_rights(char **privilege_sids, + int privilege_size, + const char *user_sid, + const char **group_sids, + int group_size) +{ + int i, j; + + for (i = 0; i < privilege_size; i++) { + if (strcmp(user_sid, privilege_sids[i]) == 0) { + return true; + } + for (j = 0; j < group_size; j++) { + if (strcmp(group_sids[j], privilege_sids[i]) == 0) { + return true; + } + } + } + + return false; +} + +/* + * This function parses the input ini_config object (which represents + * the cse-specific filename), and returns the policy_setting_value + * corresponding to the input policy_setting_key. + */ +static errno_t +ad_gpo_extract_policy_setting(TALLOC_CTX *mem_ctx, + struct ini_cfgobj *ini_config, + const char *policy_setting_key, + char **_policy_setting_value) +{ + struct value_obj *vobj = NULL; + int ret; + const char *policy_setting_value; + + ret = ini_get_config_valueobj(RIGHTS_SECTION, policy_setting_key, ini_config, + INI_GET_FIRST_VALUE, &vobj); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ini_get_config_valueobj failed [%d][%s]\n", ret, strerror(ret)); + goto done; + } + if (vobj == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "section/name not found: [%s][%s]\n", + RIGHTS_SECTION, policy_setting_key); + ret = ENOENT; + goto done; + } + policy_setting_value = ini_get_string_config_value(vobj, &ret); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ini_get_string_config_value failed [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + if (policy_setting_value[0]) { + *_policy_setting_value = talloc_strdup(mem_ctx, policy_setting_value); + if (!*_policy_setting_value) { + ret = ENOMEM; + goto done; + } + } else { + /* This is an explicitly empty policy setting. + * We need to remove this from the LDB. + */ + *_policy_setting_value = NULL; + } + + ret = EOK; + + done: + + return ret; +} + +/* + * This function parses the cse-specific (GP_EXT_GUID_SECURITY) filename, + * and stores the allow_key and deny_key of all of the gpo_map_types present + * in the file (as part of the GPO Result object in the sysdb cache). + */ +static errno_t +ad_gpo_store_policy_settings(struct sss_domain_info *domain, + const char *filename) +{ + struct ini_cfgfile *file_ctx = NULL; + struct ini_cfgobj *ini_config = NULL; + int ret; + int i; + char *allow_value = NULL; + char *deny_value = NULL; + const char *empty_val = "NO_SID"; + const char *allow_key = NULL; + const char *deny_key = NULL; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ini_config_create(&ini_config); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ini_config_create failed [%d][%s]\n", ret, strerror(ret)); + goto done; + } + + ret = ini_config_file_open(filename, 0, &file_ctx); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ini_config_file_open failed [%d][%s]\n", ret, strerror(ret)); + goto done; + } + + ret = ini_config_parse(file_ctx, INI_STOP_ON_NONE, 0, 0, ini_config); + if (ret != 0) { + int lret; + char **errors; + + DEBUG(SSSDBG_CRIT_FAILURE, + "[%s]: ini_config_parse failed [%d][%s]\n", + filename, ret, strerror(ret)); + + /* Now get specific errors if there are any */ + lret = ini_config_get_errors(ini_config, &errors); + if (lret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get specific parse error [%d][%s]\n", lret, + strerror(lret)); + goto done; + } + + for (int a = 0; errors[a]; a++) { + DEBUG(SSSDBG_CRIT_FAILURE, "%s\n", errors[a]); + } + ini_config_free_errors(errors); + + /* Do not 'goto done' here. We will try to parse + * the GPO file again. */ + } + + if (ret != EOK) { + /* A problem occurred during parsing. Try again + * with INI_PARSE_IGNORE_NON_KVP flag */ + + ini_config_file_destroy(file_ctx); + file_ctx = NULL; + ini_config_destroy(ini_config); + ini_config = NULL; + + ret = ini_config_file_open(filename, 0, &file_ctx); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ini_config_file_open failed [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + ret = ini_config_create(&ini_config); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ini_config_create failed [%d][%s]\n", ret, strerror(ret)); + goto done; + } + + ret = ini_config_parse(file_ctx, INI_STOP_ON_NONE, 0, + INI_PARSE_IGNORE_NON_KVP, ini_config); + if (ret != 0) { + int lret; + char **errors; + + DEBUG(SSSDBG_CRIT_FAILURE, + "[%s]: ini_config_parse failed [%d][%s]\n", + filename, ret, strerror(ret)); + + /* Now get specific errors if there are any */ + lret = ini_config_get_errors(ini_config, &errors); + if (lret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get specific parse error [%d][%s]\n", lret, + strerror(lret)); + goto done; + } + + for (int a = 0; errors[a]; a++) { + DEBUG(SSSDBG_CRIT_FAILURE, "%s\n", errors[a]); + } + ini_config_free_errors(errors); + + goto done; + } + } + + for (i = 0; i < GPO_MAP_NUM_OPTS; i++) { + /* The NO_SID val is used as special SID value for the case when + * no SIDs are found in the rule, but we need to store some + * value (SID) with the key (rule name) so that it is clear + * that the rule is defined on the server. */ + struct gpo_map_option_entry entry = gpo_map_option_entries[i]; + + allow_key = entry.allow_key; + if (allow_key != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "allow_key = %s\n", allow_key); + ret = ad_gpo_extract_policy_setting(tmp_ctx, + ini_config, + allow_key, + &allow_value); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ad_gpo_extract_policy_setting failed for %s [%d][%s]\n", + allow_key, ret, sss_strerror(ret)); + goto done; + } else if (ret != ENOENT) { + const char *value = allow_value ? allow_value : empty_val; + ret = sysdb_gpo_store_gpo_result_setting(domain, + allow_key, + value); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_gpo_store_gpo_result_setting failed for key:" + "'%s' value:'%s' [%d][%s]\n", allow_key, allow_value, + ret, sss_strerror(ret)); + goto done; + } + } + } + + deny_key = entry.deny_key; + if (deny_key != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "deny_key = %s\n", deny_key); + ret = ad_gpo_extract_policy_setting(tmp_ctx, + ini_config, + deny_key, + &deny_value); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ad_gpo_extract_policy_setting failed for %s [%d][%s]\n", + deny_key, ret, sss_strerror(ret)); + goto done; + } else if (ret != ENOENT) { + const char *value = deny_value ? deny_value : empty_val; + ret = sysdb_gpo_store_gpo_result_setting(domain, + deny_key, + value); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_gpo_store_gpo_result_setting failed for key:" + "'%s' value:'%s' [%d][%s]\n", deny_key, deny_value, + ret, sss_strerror(ret)); + goto done; + } + } + } + } + + ret = EOK; + + done: + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error encountered: %d.\n", ret); + } + ini_config_file_destroy(file_ctx); + ini_config_destroy(ini_config); + talloc_free(tmp_ctx); + return ret; +} + +/* + * This cse-specific function (GP_EXT_GUID_SECURITY) performs the access + * check for determining whether logon access is granted or denied for + * the {user,domain} tuple specified in the inputs. This function returns EOK + * to indicate that access is granted. Any other return value indicates that + * access is denied. + * + * The access control algorithm first determines whether the "principal_sids" + * (i.e. user_sid or group_sids) appear in allowed_sids and denied_sids. + * + * For access to be granted, both the "allowed_sids_condition" *and* the + * "denied_sids_condition" must be met (in all other cases, access is denied). + * 1) The "allowed_sids_condition" is satisfied if any of the principal_sids + * appears in allowed_sids OR if the allowed_sids list is empty + * 2) The "denied_sids_condition" is satisfied if none of the principal_sids + * appear in denied_sids + * + * Note that a deployment that is unaware of GPO-based access-control policy + * settings is unaffected by them (b/c absence of allowed_sids grants access). + * + * Note that if a principal_sid appears in both allowed_sids and denied_sids, + * the "allowed_sids_condition" is met, but the "denied_sids_condition" is not. + * In other words, Deny takes precedence over Allow. + */ +static errno_t +ad_gpo_access_check(TALLOC_CTX *mem_ctx, + enum gpo_access_control_mode gpo_mode, + enum gpo_map_type gpo_map_type, + const char *user, + bool gpo_implicit_deny, + struct sss_domain_info *domain, + struct sss_idmap_ctx *idmap_ctx, + char **allowed_sids, + int allowed_size, + char **denied_sids, + int denied_size) +{ + const char *user_sid; + const char **group_sids; + int group_size = 0; + bool access_granted = false; + bool access_denied = false; + int ret; + int j; + + DEBUG(SSSDBG_TRACE_FUNC, "RESULTANT POLICY:\n"); + DEBUG(SSSDBG_TRACE_FUNC, "gpo_map_type: %s\n", + gpo_map_type_string(gpo_map_type)); + DEBUG(SSSDBG_TRACE_FUNC, "allowed_size = %d\n", allowed_size); + for (j= 0; j < allowed_size; j++) { + DEBUG(SSSDBG_TRACE_FUNC, "allowed_sids[%d] = %s\n", j, allowed_sids[j]); + } + + DEBUG(SSSDBG_TRACE_FUNC, "denied_size = %d\n", denied_size); + for (j= 0; j < denied_size; j++) { + DEBUG(SSSDBG_TRACE_FUNC, " denied_sids[%d] = %s\n", j, denied_sids[j]); + } + + ret = ad_gpo_get_sids(mem_ctx, user, domain, idmap_ctx, &user_sid, + &group_sids, &group_size); + if (ret != EOK) { + ret = ERR_NO_SIDS; + DEBUG(SSSDBG_OP_FAILURE, + "Unable to retrieve SIDs: [%d](%s)\n", ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "CURRENT USER:\n"); + DEBUG(SSSDBG_TRACE_FUNC, " user_sid = %s\n", user_sid); + + for (j= 0; j < group_size; j++) { + DEBUG(SSSDBG_TRACE_FUNC, " group_sids[%d] = %s\n", j, + group_sids[j]); + } + + if (allowed_size == 0 && !gpo_implicit_deny) { + access_granted = true; + } else { + access_granted = check_rights(allowed_sids, allowed_size, user_sid, + group_sids, group_size); + } + + DEBUG(SSSDBG_TRACE_FUNC, "POLICY DECISION:\n"); + + DEBUG(SSSDBG_TRACE_FUNC, " access_granted = %d\n", access_granted); + + access_denied = check_rights(denied_sids, denied_size, user_sid, + group_sids, group_size); + DEBUG(SSSDBG_TRACE_FUNC, " access_denied = %d\n", access_denied); + + if (access_granted && !access_denied) { + return EOK; + } else { + switch (gpo_mode) { + case GPO_ACCESS_CONTROL_ENFORCING: + return ERR_ACCESS_DENIED; + case GPO_ACCESS_CONTROL_PERMISSIVE: + DEBUG(SSSDBG_TRACE_FUNC, "access denied: permissive mode\n"); + sss_log_ext(SSS_LOG_WARNING, LOG_AUTHPRIV, "Warning: user would " \ + "have been denied GPO-based logon access if the " \ + "ad_gpo_access_control option were set to enforcing " \ + "mode."); + return EOK; + default: + return EINVAL; + } + } + + done: + + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error encountered: %d.\n", ret); + } + + return ret; +} + +/* + * This function retrieves the raw policy_setting_value for the input key from + * the GPO_Result object in the sysdb cache. It then parses the raw value and + * uses the results to populate the output parameters with the sids_list and + * the size of the sids_list. + */ +errno_t +parse_policy_setting_value(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *key, + char ***_sids_list, + int *_sids_list_size) +{ + int ret; + int i; + const char *value; + int sids_list_size; + char **sids_list = NULL; + + ret = sysdb_gpo_get_gpo_result_setting(mem_ctx, domain, key, &value); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "No previous GPO result\n"); + value = NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot retrieve settings from sysdb for key: '%s' [%d][%s].\n", + key, ret, sss_strerror(ret)); + goto done; + } + + if (value == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "No value for key [%s] found in gpo result\n", key); + sids_list_size = 0; + } else { + ret = split_on_separator(mem_ctx, value, ',', true, true, + &sids_list, &sids_list_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot parse list of sids %s: %d\n", value, ret); + ret = EINVAL; + goto done; + } + + for (i = 0; i < sids_list_size; i++) { + /* remove the asterisk prefix found on sids */ + sids_list[i]++; + } + } + + *_sids_list = talloc_steal(mem_ctx, sids_list); + *_sids_list_size = sids_list_size; + + ret = EOK; + + done: + return ret; +} + +/* + * This cse-specific function (GP_EXT_GUID_SECURITY) performs HBAC policy + * processing and determines whether logon access is granted or denied for + * the {user,domain} tuple specified in the inputs. This function returns EOK + * to indicate that access is granted. Any other return value indicates that + * access is denied. + * + * Internally, this function retrieves the allow_value and deny_value for the + * input gpo_map_type from the GPO Result object in the sysdb cache, parses + * the values into allow_sids and deny_sids, and executes the access control + * algorithm which compares the allow_sids and deny_sids against the user_sid + * and group_sids for the input user. + */ +static errno_t +ad_gpo_perform_hbac_processing(TALLOC_CTX *mem_ctx, + enum gpo_access_control_mode gpo_mode, + enum gpo_map_type gpo_map_type, + const char *user, + bool gpo_implicit_deny, + struct sss_domain_info *user_domain, + struct sss_domain_info *host_domain, + struct sss_idmap_ctx *idmap_ctx) +{ + int ret; + const char *allow_key = NULL; + char **allow_sids; + int allow_size ; + const char *deny_key = NULL; + char **deny_sids; + int deny_size; + + allow_key = gpo_map_option_entries[gpo_map_type].allow_key; + DEBUG(SSSDBG_TRACE_ALL, "allow_key: %s\n", allow_key); + deny_key = gpo_map_option_entries[gpo_map_type].deny_key; + DEBUG(SSSDBG_TRACE_ALL, "deny_key: %s\n", deny_key); + + ret = parse_policy_setting_value(mem_ctx, host_domain, allow_key, + &allow_sids, &allow_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "parse_policy_setting_value failed for key %s: [%d](%s)\n", + allow_key, ret, sss_strerror(ret)); + ret = EINVAL; + goto done; + } + + ret = parse_policy_setting_value(mem_ctx, host_domain, deny_key, + &deny_sids, &deny_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "parse_policy_setting_value failed for key %s: [%d](%s)\n", + deny_key, ret, sss_strerror(ret)); + ret = EINVAL; + goto done; + } + + /* perform access check with the final resultant allow_sids and deny_sids */ + ret = ad_gpo_access_check(mem_ctx, gpo_mode, gpo_map_type, user, + gpo_implicit_deny, user_domain, idmap_ctx, + allow_sids, allow_size, deny_sids, deny_size); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "GPO access check failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + done: + return ret; +} + +/* == ad_gpo_access_send/recv implementation ================================*/ + +struct ad_gpo_access_state { + struct tevent_context *ev; + struct ldb_context *ldb_ctx; + struct ad_access_ctx *access_ctx; + enum gpo_access_control_mode gpo_mode; + bool gpo_implicit_deny; + enum gpo_map_type gpo_map_type; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *sdap_op; + char *server_hostname; + struct sdap_options *opts; + int timeout; + struct sss_domain_info *user_domain; + struct sss_domain_info *host_domain; + const char *host_sam_account_name; + char *host_fqdn; + const char *user; + int gpo_timeout_option; + const char *ad_hostname; + const char *host_sid; + const char *target_dn; + struct gp_gpo **dacl_filtered_gpos; + int num_dacl_filtered_gpos; + struct gp_gpo **cse_filtered_gpos; + int num_cse_filtered_gpos; + int cse_gpo_index; + const char *ad_domain; +}; + +static void ad_gpo_connect_done(struct tevent_req *subreq); +static void ad_gpo_target_dn_retrieval_done(struct tevent_req *subreq); +static void ad_gpo_process_som_done(struct tevent_req *subreq); +static void ad_gpo_process_gpo_done(struct tevent_req *subreq); + +static errno_t ad_gpo_cse_step(struct tevent_req *req); +static void ad_gpo_cse_done(struct tevent_req *subreq); + +struct tevent_req * +ad_gpo_access_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *domain, + struct ad_access_ctx *ctx, + const char *user, + const char *service) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct ad_gpo_access_state *state; + errno_t ret; + int hret; + hash_key_t key; + hash_value_t val; + enum gpo_map_type gpo_map_type; + + req = tevent_req_create(mem_ctx, &state, struct ad_gpo_access_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + /* determine service's option_type (e.g. interactive, network, etc) */ + key.type = HASH_KEY_STRING; + key.str = talloc_strdup(state, service); + + hret = hash_lookup(ctx->gpo_map_options_table, &key, &val); + if (hret != HASH_SUCCESS && hret != HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_OP_FAILURE, "Error checking hash table: [%s]\n", + hash_error_string(hret)); + ret = EINVAL; + goto immediately; + } + + /* if service isn't mapped, map it to value of ad_gpo_default_right option */ + if (hret == HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_TRACE_FUNC, + "Configuration hint: PAM service '%s' is not mapped to any Group" + " Policy rule. If you plan to use this PAM service it is " + "recommended to use the ad_gpo_map_* family of options to map " + "this PAM service to a Group Policy rule. PAM services not " + "present in any map will fall back to value set in " + "ad_gpo_default_right, which is currently set to %s (see manual " + "pages 'man sssd-ad' for more details).\n", service, + gpo_map_type_string(ctx->gpo_default_right)); + gpo_map_type = ctx->gpo_default_right; + } else { + gpo_map_type = (enum gpo_map_type) val.i; + } + + DEBUG(SSSDBG_TRACE_FUNC, "service %s maps to %s\n", service, + gpo_map_type_string(gpo_map_type)); + + if (gpo_map_type == GPO_MAP_PERMIT) { + ret = EOK; + goto immediately; + } + + if (gpo_map_type == GPO_MAP_DENY) { + switch (ctx->gpo_access_control_mode) { + case GPO_ACCESS_CONTROL_ENFORCING: + ret = ERR_ACCESS_DENIED; + goto immediately; + case GPO_ACCESS_CONTROL_PERMISSIVE: + DEBUG(SSSDBG_TRACE_FUNC, "access denied: permissive mode\n"); + sss_log_ext(SSS_LOG_WARNING, LOG_AUTHPRIV, "Warning: user would " \ + "have been denied GPO-based logon access if the " \ + "ad_gpo_access_control option were set to enforcing " \ + "mode."); + ret = EOK; + goto immediately; + default: + ret = EINVAL; + goto immediately; + } + } + + /* GPO Operations all happen against the enrolled domain, + * not the user's domain (which may be a trusted realm) + */ + state->user_domain = domain; + state->host_domain = get_domains_head(domain); + state->ad_domain = dp_opt_get_string(ctx->ad_id_ctx->ad_options->basic, + AD_DOMAIN); + + state->gpo_map_type = gpo_map_type; + state->dacl_filtered_gpos = NULL; + state->num_dacl_filtered_gpos = 0; + state->cse_filtered_gpos = NULL; + state->num_cse_filtered_gpos = 0; + state->cse_gpo_index = 0; + state->ev = ev; + state->user = user; + state->ldb_ctx = sysdb_ctx_get_ldb(state->host_domain->sysdb); + state->gpo_mode = ctx->gpo_access_control_mode; + state->gpo_timeout_option = ctx->gpo_cache_timeout; + state->ad_hostname = dp_opt_get_string(ctx->ad_options, AD_HOSTNAME); + state->gpo_implicit_deny = dp_opt_get_bool(ctx->ad_options, + AD_GPO_IMPLICIT_DENY); + state->access_ctx = ctx; + state->opts = ctx->sdap_access_ctx->id_ctx->opts; + state->timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT); + state->conn = ad_get_dom_ldap_conn(ctx->ad_id_ctx, state->host_domain); + state->sdap_op = sdap_id_op_create(state, state->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n"); + ret = ENOMEM; + goto immediately; + } + + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto immediately; + } + tevent_req_set_callback(subreq, ad_gpo_connect_done, req); + + return req; + +immediately: + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + tevent_req_post(req, ev); + return req; +} + +static errno_t +process_offline_gpos(TALLOC_CTX *mem_ctx, + const char *user, + bool gpo_implicit_deny, + enum gpo_access_control_mode gpo_mode, + struct sss_domain_info *user_domain, + struct sss_domain_info *host_domain, + struct sss_idmap_ctx *idmap_ctx, + enum gpo_map_type gpo_map_type) + +{ + errno_t ret; + + ret = ad_gpo_perform_hbac_processing(mem_ctx, + gpo_mode, + gpo_map_type, + user, + gpo_implicit_deny, + user_domain, + host_domain, + idmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "HBAC processing failed: [%d](%s}\n", + ret, sss_strerror(ret)); + goto done; + } + + /* we have successfully processed all offline gpos */ + ret = EOK; + + done: + return ret; +} + +static void +ad_gpo_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_gpo_access_state *state; + int dp_error; + errno_t ret; + char *server_uri; + LDAPURLDesc *lud; + struct sdap_domain *sdom; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_gpo_access_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + if (dp_error != DP_ERR_OFFLINE) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to AD server: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Preparing for offline operation.\n"); + ret = process_offline_gpos(state, + state->user, + state->gpo_implicit_deny, + state->gpo_mode, + state->user_domain, + state->host_domain, + state->opts->idmap_ctx->map, + state->gpo_map_type); + + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "process_offline_gpos succeeded\n"); + tevent_req_done(req); + goto done; + } else { + DEBUG(SSSDBG_OP_FAILURE, + "process_offline_gpos failed [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + } + } + + /* extract server_hostname from server_uri */ + server_uri = state->conn->service->uri; + ret = ldap_url_parse(server_uri, &lud); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse ldap URI (%s)!\n", server_uri); + ret = EINVAL; + goto done; + } + + if (lud->lud_host == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "The LDAP URI (%s) did not contain a host name\n", server_uri); + ldap_free_urldesc(lud); + ret = EINVAL; + goto done; + } + + state->server_hostname = talloc_strdup(state, lud->lud_host); + ldap_free_urldesc(lud); + if (!state->server_hostname) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "server_hostname from uri: %s\n", + state->server_hostname); + + /* SDAP_SASL_AUTHID contains the name used for kinit and SASL bind which + * in the AD case is the NetBIOS name. */ + state->host_sam_account_name = dp_opt_get_string(state->opts->basic, + SDAP_SASL_AUTHID); + if (state->host_sam_account_name == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "sam_account_name is %s\n", + state->host_sam_account_name); + + state->host_fqdn = sss_create_internal_fqname(state, state->host_sam_account_name, + state->host_domain->name); + if (state->host_fqdn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create fully-qualified host name.\n"); + ret = ENOMEM; + goto done; + } + + /* AD handle computers the same as users */ + sdom = sdap_domain_get(state->access_ctx->ad_id_ctx->sdap_id_ctx->opts, + state->host_domain); + if (sdom == NULL) { + ret = EIO; + goto done; + } + + subreq = groups_by_user_send(state, state->ev, + state->access_ctx->ad_id_ctx->sdap_id_ctx, + sdom, state->conn, + state->host_fqdn, + BE_FILTER_NAME, + NULL, + true, + true); + tevent_req_set_callback(subreq, ad_gpo_target_dn_retrieval_done, req); + + ret = EOK; + + done: + + if (ret != EOK) { + tevent_req_error(req, ret); + } +} + +static void +ad_gpo_target_dn_retrieval_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_gpo_access_state *state; + int ret; + int dp_error; + int sdap_ret; + const char *target_dn = NULL; + uint32_t uac; + static const char *host_attrs[] = { SYSDB_ORIG_DN, SYSDB_AD_USER_ACCOUNT_CONTROL, SYSDB_SID_STR, NULL }; + struct ldb_result *res = NULL; + const char *tmp = NULL; + char *endptr; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_gpo_access_state); + ret = groups_by_user_recv(subreq, &dp_error, &sdap_ret); + talloc_zfree(subreq); + if (ret != EOK) { + if (sdap_ret == EAGAIN && dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, "Preparing for offline operation.\n"); + ret = process_offline_gpos(state, + state->user, + state->gpo_implicit_deny, + state->gpo_mode, + state->user_domain, + state->host_domain, + state->opts->idmap_ctx->map, + state->gpo_map_type); + + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "process_offline_gpos succeeded\n"); + tevent_req_done(req); + goto done; + } else { + DEBUG(SSSDBG_OP_FAILURE, + "process_offline_gpos failed [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + } + + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get policy target's DN: [%d](%s)\n", + ret, sss_strerror(ret)); + ret = ENOENT; + goto done; + } + + ret = sysdb_get_user_attr(state, state->host_domain, + state->host_fqdn, + host_attrs, &res); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to read host attributes.\n"); + goto done; + } + if (res->count != 1) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected number [%d] of results searching " + "for [%s], expected 1.\n", res->count, + state->host_sam_account_name); + ret = EINVAL; + goto done; + } + + target_dn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_ORIG_DN, NULL); + if (target_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ldb_msg_find_attr_as_string failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + state->target_dn = talloc_steal(state, target_dn); + if (state->target_dn == NULL) { + ret = ENOMEM; + goto done; + } + + tmp = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_AD_USER_ACCOUNT_CONTROL, + NULL); + if (tmp == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ldb_msg_find_attr_as_string failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + uac = strtouint32(tmp, &endptr, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, "Failed to convert UAC [%s] into uint32_t.\n", + tmp); + goto done; + } + if (*endptr != '\0') { + ret = EINVAL; + DEBUG(SSSDBG_OP_FAILURE, "UAC [%s] is not a pure numerical value.\n", + tmp); + goto done; + } + + /* we only support computer policy targets, not users */ + if (!(uac & UAC_WORKSTATION_TRUST_ACCOUNT || + uac & UAC_SERVER_TRUST_ACCOUNT)) { + DEBUG(SSSDBG_OP_FAILURE, + "Invalid userAccountControl (%x) value for machine account.\n", + uac); + ret = EINVAL; + goto done; + } + + state->host_sid = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_SID_STR, + NULL); + talloc_steal(state, state->host_sid); + + subreq = ad_gpo_process_som_send(state, + state->ev, + state->conn, + state->ldb_ctx, + state->sdap_op, + state->opts, + state->access_ctx->ad_options, + state->timeout, + state->target_dn, + state->ad_domain); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ad_gpo_process_som_done, req); + + ret = EOK; + + done: + + if (ret != EOK) { + tevent_req_error(req, ret); + } + +} + +static void +ad_gpo_process_som_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_gpo_access_state *state; + int ret; + struct gp_som **som_list; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_gpo_access_state); + ret = ad_gpo_process_som_recv(subreq, state, &som_list); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get som list: [%d](%s)\n", + ret, sss_strerror(ret)); + ret = ENOENT; + goto done; + } + + subreq = ad_gpo_process_gpo_send(state, + state->ev, + state->sdap_op, + state->opts, + state->server_hostname, + state->host_domain, + state->access_ctx, + state->timeout, + som_list); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ad_gpo_process_gpo_done, req); + + ret = EOK; + + done: + + if (ret != EOK) { + tevent_req_error(req, ret); + } +} + +/* + * This function retrieves a list of candidate_gpos and potentially reduces it + * to a list of dacl_filtered_gpos, based on each GPO's DACL. + * + * This function then takes the list of dacl_filtered_gpos and potentially + * reduces it to a list of cse_filtered_gpos, based on whether each GPO's list + * of cse_guids includes the "SecuritySettings" CSE GUID (used for HBAC). + * + * Ultimately, this function then sends each cse_filtered_gpo to the gpo_child, + * which retrieves the GPT.INI and policy files (as needed). Once all files + * have been downloaded, the ad_gpo_cse_done function performs HBAC processing. + */ +static void +ad_gpo_process_gpo_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_gpo_access_state *state; + int ret; + int dp_error; + struct gp_gpo **candidate_gpos = NULL; + int num_candidate_gpos = 0; + int i = 0; + const char **cse_filtered_gpo_guids; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_gpo_access_state); + ret = ad_gpo_process_gpo_recv(subreq, state, &candidate_gpos, + &num_candidate_gpos); + + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get GPO list from server %s: [%d](%s)\n", + state->ad_hostname ? state->ad_hostname : "NULL", ret, sss_strerror(ret)); + goto done; + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, + "No GPOs found that apply to this system.\n"); + /* + * Delete the result object list, since there are no + * GPOs to include in it. + */ + ret = sysdb_gpo_delete_gpo_result_object(state, state->host_domain); + if (ret != EOK) { + switch (ret) { + case ENOENT: + DEBUG(SSSDBG_TRACE_FUNC, "No GPO Result available in cache\n"); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not delete GPO Result from cache: [%s]\n", + sss_strerror(ret)); + goto done; + } + } + + if (state->gpo_implicit_deny == true) { + DEBUG(SSSDBG_TRACE_FUNC, + "No applicable GPOs have been found and ad_gpo_implicit_deny" + " is set to 'true'. The user will be denied access.\n"); + ret = ERR_ACCESS_DENIED; + } else { + ret = EOK; + } + + goto done; + } + + ret = ad_gpo_filter_gpos_by_dacl(state, state->user, state->host_fqdn, + state->user_domain, + state->host_domain, + state->opts->idmap_ctx->map, + candidate_gpos, num_candidate_gpos, + &state->dacl_filtered_gpos, + &state->num_dacl_filtered_gpos); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to filter GPO list by DACL: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + if (state->dacl_filtered_gpos[0] == NULL) { + /* since no applicable gpos were found, there is nothing to enforce */ + DEBUG(SSSDBG_TRACE_FUNC, + "no applicable gpos found after dacl filtering\n"); + + /* + * Delete the result object list, since there are no + * GPOs to include in it. + */ + ret = sysdb_gpo_delete_gpo_result_object(state, state->host_domain); + if (ret != EOK) { + switch (ret) { + case ENOENT: + DEBUG(SSSDBG_TRACE_FUNC, "No GPO Result available in cache\n"); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not delete GPO Result from cache: [%s]\n", + sss_strerror(ret)); + goto done; + } + } + + if (state->gpo_implicit_deny == true) { + DEBUG(SSSDBG_TRACE_FUNC, + "No applicable GPOs have been found and ad_gpo_implicit_deny" + " is set to 'true'. The user will be denied access.\n"); + ret = ERR_ACCESS_DENIED; + } else { + ret = EOK; + } + + goto done; + } + + for (i = 0; i < state->num_dacl_filtered_gpos; i++) { + DEBUG(SSSDBG_TRACE_FUNC, "dacl_filtered_gpos[%d]->gpo_guid is %s\n", i, + state->dacl_filtered_gpos[i]->gpo_guid); + } + + ret = ad_gpo_filter_gpos_by_cse_guid(state, + GP_EXT_GUID_SECURITY, + state->dacl_filtered_gpos, + state->num_dacl_filtered_gpos, + &state->cse_filtered_gpos, + &state->num_cse_filtered_gpos); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to filter GPO list by CSE_GUID: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + if (state->cse_filtered_gpos[0] == NULL) { + /* no gpos contain "SecuritySettings" cse_guid, nothing to enforce */ + DEBUG(SSSDBG_TRACE_FUNC, + "no applicable gpos found after cse_guid filtering\n"); + + if (state->gpo_implicit_deny == true) { + DEBUG(SSSDBG_TRACE_FUNC, + "No applicable GPOs have been found and ad_gpo_implicit_deny" + " is set to 'true'. The user will be denied access.\n"); + ret = ERR_ACCESS_DENIED; + } else { + ret = EOK; + } + + goto done; + } + + /* we create and populate an array of applicable gpo-guids */ + cse_filtered_gpo_guids = + talloc_array(state, const char *, state->num_cse_filtered_gpos); + if (cse_filtered_gpo_guids == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < state->num_cse_filtered_gpos; i++) { + DEBUG(SSSDBG_TRACE_FUNC, "cse_filtered_gpos[%d]->gpo_guid is %s\n", i, + state->cse_filtered_gpos[i]->gpo_guid); + cse_filtered_gpo_guids[i] = talloc_steal(cse_filtered_gpo_guids, + state->cse_filtered_gpos[i]->gpo_guid); + if (cse_filtered_gpo_guids[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "num_cse_filtered_gpos: %d\n", + state->num_cse_filtered_gpos); + + /* + * before we start processing each gpo, we delete the GPO Result object + * from the sysdb cache so that any previous policy settings are cleared; + * subsequent functions will add the GPO Result object (and populate it + * with resultant policy settings) for this policy application + */ + ret = sysdb_gpo_delete_gpo_result_object(state, state->host_domain); + if (ret != EOK) { + switch (ret) { + case ENOENT: + DEBUG(SSSDBG_TRACE_FUNC, "No GPO Result available in cache\n"); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not delete GPO Result from cache: [%s]\n", + sss_strerror(ret)); + goto done; + } + } + + ret = ad_gpo_cse_step(req); + + done: + + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static errno_t +ad_gpo_cse_step(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct ad_gpo_access_state *state; + int i = 0; + struct ldb_result *res; + errno_t ret; + bool send_to_child = true; + int cached_gpt_version = 0; + time_t policy_file_timeout = 0; + + state = tevent_req_data(req, struct ad_gpo_access_state); + + struct gp_gpo *cse_filtered_gpo = + state->cse_filtered_gpos[state->cse_gpo_index]; + + /* cse_filtered_gpo is NULL after all GPO policy files have been downloaded */ + if (cse_filtered_gpo == NULL) return EOK; + + DEBUG(SSSDBG_TRACE_FUNC, "cse filtered_gpos[%d]->gpo_guid is %s\n", + state->cse_gpo_index, cse_filtered_gpo->gpo_guid); + for (i = 0; i < cse_filtered_gpo->num_gpo_cse_guids; i++) { + DEBUG(SSSDBG_TRACE_ALL, + "cse_filtered_gpos[%d]->gpo_cse_guids[%d]->gpo_guid is %s\n", + state->cse_gpo_index, i, cse_filtered_gpo->gpo_cse_guids[i]); + } + + DEBUG(SSSDBG_TRACE_FUNC, "smb_server: %s\n", cse_filtered_gpo->smb_server); + DEBUG(SSSDBG_TRACE_FUNC, "smb_share: %s\n", cse_filtered_gpo->smb_share); + DEBUG(SSSDBG_TRACE_FUNC, "smb_path: %s\n", cse_filtered_gpo->smb_path); + DEBUG(SSSDBG_TRACE_FUNC, "gpo_guid: %s\n", cse_filtered_gpo->gpo_guid); + + cse_filtered_gpo->policy_filename = + talloc_asprintf(state, + GPO_CACHE_PATH"%s%s", + cse_filtered_gpo->smb_path, + GP_EXT_GUID_SECURITY_SUFFIX); + if (cse_filtered_gpo->policy_filename == NULL) { + return ENOMEM; + } + + /* retrieve gpo cache entry; set cached_gpt_version to -1 if unavailable */ + DEBUG(SSSDBG_TRACE_FUNC, "retrieving GPO from cache [%s]\n", + cse_filtered_gpo->gpo_guid); + ret = sysdb_gpo_get_gpo_by_guid(state, + state->host_domain, + cse_filtered_gpo->gpo_guid, + &res); + if (ret == EOK) { + /* + * Note: if the timeout is valid, then we can later avoid downloading + * the GPT.INI file, as well as any policy files (i.e. we don't need + * to interact with the gpo_child at all). However, even if the timeout + * is not valid, while we will have to interact with the gpo child to + * download the GPT.INI file, we may still be able to avoid downloading + * the policy files (if the cached_gpt_version is the same as the + * GPT.INI version). In other words, the timeout is *not* an expiration + * for the entire cache entry; the cached_gpt_version never expires. + */ + + cached_gpt_version = ldb_msg_find_attr_as_int(res->msgs[0], + SYSDB_GPO_VERSION_ATTR, + 0); + + policy_file_timeout = ldb_msg_find_attr_as_uint64 + (res->msgs[0], SYSDB_GPO_TIMEOUT_ATTR, 0); + + if (policy_file_timeout >= time(NULL)) { + send_to_child = false; + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "ENOENT\n"); + cached_gpt_version = -1; + } else { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not read GPO from cache: [%s]\n", + sss_strerror(ret)); + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, "send_to_child: %d\n", send_to_child); + DEBUG(SSSDBG_TRACE_FUNC, "cached_gpt_version: %d\n", cached_gpt_version); + + cse_filtered_gpo->send_to_child = send_to_child; + + subreq = ad_gpo_process_cse_send(state, + state->ev, + send_to_child, + state->host_domain, + cse_filtered_gpo->gpo_guid, + cse_filtered_gpo->smb_server, + cse_filtered_gpo->smb_share, + cse_filtered_gpo->smb_path, + GP_EXT_GUID_SECURITY_SUFFIX, + cached_gpt_version, + state->gpo_timeout_option); + + tevent_req_set_callback(subreq, ad_gpo_cse_done, req); + return EAGAIN; +} + +/* + * This cse-specific function (GP_EXT_GUID_SECURITY) increments the + * cse_gpo_index until the policy settings for all applicable GPOs have been + * stored as part of the GPO Result object in the sysdb cache. Once all + * GPOs have been processed, this functions performs HBAC processing by + * comparing the resultant policy setting values in the GPO Result object + * with the user_sid/group_sids of interest. + */ +static void +ad_gpo_cse_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_gpo_access_state *state; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_gpo_access_state); + + struct gp_gpo *cse_filtered_gpo = + state->cse_filtered_gpos[state->cse_gpo_index]; + + const char *gpo_guid = cse_filtered_gpo->gpo_guid; + + DEBUG(SSSDBG_TRACE_FUNC, "gpo_guid: %s\n", gpo_guid); + + ret = ad_gpo_process_cse_recv(subreq); + + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve policy data: [%d](%s}\n", + ret, sss_strerror(ret)); + goto done; + } + + /* + * now that the policy file for this gpo have been downloaded to the + * GPO CACHE, we store all of the supported keys present in the file + * (as part of the GPO Result object in the sysdb cache). + */ + ret = ad_gpo_store_policy_settings(state->host_domain, + cse_filtered_gpo->policy_filename); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "ad_gpo_store_policy_settings failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + state->cse_gpo_index++; + ret = ad_gpo_cse_step(req); + + if (ret == EOK) { + /* ret is EOK only after all GPO policy files have been downloaded */ + ret = ad_gpo_perform_hbac_processing(state, + state->gpo_mode, + state->gpo_map_type, + state->user, + state->gpo_implicit_deny, + state->user_domain, + state->host_domain, + state->opts->idmap_ctx->map); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "HBAC processing failed: [%d](%s}\n", + ret, sss_strerror(ret)); + goto done; + } + + } + + done: + + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +errno_t +ad_gpo_access_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* == ad_gpo_process_som_send/recv helpers ================================= */ + +/* + * This function returns the parent of an LDAP DN + */ +static errno_t +ad_gpo_parent_dn(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + const char *dn, + const char **_parent_dn) +{ + struct ldb_dn *ldb_dn; + struct ldb_dn *parent_ldb_dn; + const char *p; + int ret; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ldb_dn = ldb_dn_new(tmp_ctx, ldb_ctx, dn); + parent_ldb_dn = ldb_dn_get_parent(tmp_ctx, ldb_dn); + p = ldb_dn_get_linearized(parent_ldb_dn); + + *_parent_dn = talloc_steal(mem_ctx, p); + ret = EOK; + + done: + talloc_free(tmp_ctx); + return ret; +} + +/* + * This function populates the _som_list output parameter by parsing the input + * DN into a list of gp_som objects. This function essentially repeatedly + * appends the input DN's parent to the SOM List (if the parent starts with + * "OU=" or "DC="), until the first "DC=" component is reached. + * Example: if input DN is "CN=MyComputer,CN=Computers,OU=Sales,DC=FOO,DC=COM", + * then SOM List has 2 SOM entries: {[OU=Sales,DC=FOO,DC=COM], [DC=FOO, DC=COM]} + */ + +static errno_t +ad_gpo_populate_som_list(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + const char *target_dn, + int *_num_soms, + struct gp_som ***_som_list) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret; + int rdn_count = 0; + int som_idx = 0; + struct gp_som **som_list; + const char *parent_dn = NULL; + const char *tmp_dn = NULL; + struct ldb_dn *ldb_target_dn; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ldb_target_dn = ldb_dn_new(tmp_ctx, ldb_ctx, target_dn); + if (ldb_target_dn == NULL) { + ret = EINVAL; + goto done; + } + + rdn_count = ldb_dn_get_comp_num(ldb_target_dn); + if (rdn_count == -1) { + ret = EINVAL; + goto done; + } + + if (rdn_count == 0) { + *_som_list = NULL; + ret = EOK; + goto done; + } + + /* assume the worst-case, in which every parent is a SOM */ + /* include space for Site SOM and NULL: rdn_count + 1 + 1 */ + som_list = talloc_array(tmp_ctx, struct gp_som *, rdn_count + 1 + 1); + if (som_list == NULL) { + ret = ENOMEM; + goto done; + } + + /* first, populate the OU and Domain SOMs */ + tmp_dn = target_dn; + while ((ad_gpo_parent_dn(tmp_ctx, ldb_ctx, tmp_dn, &parent_dn)) == EOK) { + + if ((strncasecmp(parent_dn, "OU=", strlen("OU=")) == 0) || + (strncasecmp(parent_dn, "DC=", strlen("DC=")) == 0)) { + + som_list[som_idx] = talloc_zero(som_list, struct gp_som); + if (som_list[som_idx] == NULL) { + ret = ENOMEM; + goto done; + } + som_list[som_idx]->som_dn = talloc_steal(som_list[som_idx], + parent_dn); + if (som_list[som_idx]->som_dn == NULL) { + ret = ENOMEM; + goto done; + } + som_idx++; + } + + if (strncasecmp(parent_dn, "DC=", strlen("DC=")) == 0) { + break; + } + tmp_dn = parent_dn; + } + + som_list[som_idx] = NULL; + + *_num_soms = som_idx; + *_som_list = talloc_steal(mem_ctx, som_list); + + ret = EOK; + + done: + talloc_free(tmp_ctx); + return ret; +} + +/* + * This function populates the _gplink_list output parameter by parsing the + * input raw_gplink_value into an array of gp_gplink objects, each consisting of + * a GPO DN and bool enforced field. + * + * The raw_gplink_value is single string consisting of multiple gplink strings. + * The raw_gplink_value is in the following format: + * "[GPO_DN_1;GPLinkOptions_1]...[GPO_DN_n;GPLinkOptions_n]" + * + * Each gplink string consists of a GPO DN and a GPLinkOptions field (which + * indicates whether its associated GPO DN is ignored, unenforced, or enforced). + * If a GPO DN is flagged as ignored, it is discarded and will not be added to + * the _gplink_list. If the allow_enforced_only input is true, AND a GPO DN is + * flagged as unenforced, it will also be discarded. + * + * Example: if raw_gplink_value="[OU=Sales,DC=FOO,DC=COM;0][DC=FOO,DC=COM;2]" + * and allow_enforced_only=FALSE, then the output would consist of following: + * _gplink_list[0]: {GPO DN: "OU=Sales,DC=FOO,DC=COM", enforced: FALSE} + * _gplink_list[1]: {GPO DN: "DC=FOO,DC=COM", enforced: TRUE} + */ +static errno_t +ad_gpo_populate_gplink_list(TALLOC_CTX *mem_ctx, + const char *som_dn, + char *raw_gplink_value, + struct gp_gplink ***_gplink_list, + bool allow_enforced_only) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *ptr; + char *first; + char *last; + char *dn; + char *gplink_options; + const char delim = ']'; + struct gp_gplink **gplink_list; + int i; + int ret; + uint32_t gplink_number; + int gplink_count = 0; + int num_enabled = 0; + + if (raw_gplink_value == NULL || + *raw_gplink_value == '\0' || + _gplink_list == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "som_dn: %s\n", som_dn); + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ptr = raw_gplink_value; + + while ((ptr = strchr(ptr, delim))) { + ptr++; + gplink_count++; + } + + if (gplink_count == 0) { + ret = EOK; + goto done; + } + + gplink_list = talloc_array(tmp_ctx, struct gp_gplink *, gplink_count + 1); + if (gplink_list == NULL) { + ret = ENOMEM; + goto done; + } + + num_enabled = 0; + ptr = raw_gplink_value; + for (i = 0; i < gplink_count; i++) { + first = ptr + 1; + last = strchr(first, delim); + if (last == NULL) { + ret = EINVAL; + goto done; + } + *last = '\0'; + last++; + dn = first; + if ( strncasecmp(dn, "LDAP://", 7)== 0 ) { + dn = dn + 7; + } + gplink_options = strchr(first, ';'); + if (gplink_options == NULL) { + ret = EINVAL; + goto done; + } + *gplink_options = '\0'; + gplink_options++; + + gplink_number = strtouint32(gplink_options, NULL, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "strtouint32 failed: [%d](%s)\n", ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, + "gplink_list[%d]: [%s; %d]\n", num_enabled, dn, gplink_number); + + if ((gplink_number == 1) || (gplink_number ==3)) { + /* ignore flag is set */ + DEBUG(SSSDBG_TRACE_ALL, "ignored gpo skipped\n"); + ptr = last; + continue; + } + + if (allow_enforced_only && (gplink_number == 0)) { + /* unenforced flag is set; only enforced gpos allowed */ + DEBUG(SSSDBG_TRACE_ALL, "unenforced gpo skipped\n"); + ptr = last; + continue; + } + + gplink_list[num_enabled] = talloc_zero(gplink_list, struct gp_gplink); + if (gplink_list[num_enabled] == NULL) { + ret = ENOMEM; + goto done; + } + gplink_list[num_enabled]->gpo_dn = + talloc_strdup(gplink_list[num_enabled], dn); + + if (gplink_list[num_enabled]->gpo_dn == NULL) { + ret = ENOMEM; + goto done; + } + + if (gplink_number == 0) { + gplink_list[num_enabled]->enforced = 0; + num_enabled++; + } else if (gplink_number == 2) { + gplink_list[num_enabled]->enforced = 1; + num_enabled++; + } else { + ret = EINVAL; + goto done; + } + + ptr = last; + } + gplink_list[num_enabled] = NULL; + + *_gplink_list = talloc_steal(mem_ctx, gplink_list); + ret = EOK; + + done: + talloc_free(tmp_ctx); + return ret; +} + +/* == ad_gpo_process_som_send/recv implementation ========================== */ + +struct ad_gpo_process_som_state { + struct tevent_context *ev; + struct sdap_id_op *sdap_op; + struct sdap_options *opts; + struct dp_option *ad_options; + int timeout; + bool allow_enforced_only; + char *site_name; + char *site_dn; + struct gp_som **som_list; + int som_index; + int num_soms; +}; + +static void ad_gpo_site_name_retrieval_done(struct tevent_req *subreq); +static void ad_gpo_site_dn_retrieval_done(struct tevent_req *subreq); +static errno_t ad_gpo_get_som_attrs_step(struct tevent_req *req); +static void ad_gpo_get_som_attrs_done(struct tevent_req *subreq); + +/* + * This function uses the input target_dn and input domain_name to populate + * a list of gp_som objects. Each object in this list represents a SOM + * associated with the target (such as OU, Domain, and Site). + * + * The inputs are used to determine the DNs of each SOM associated with the + * target. In turn, the SOM object DNs are used to retrieve certain LDAP + * attributes of each SOM object, that are parsed into an array of gp_gplink + * objects, essentially representing the GPOs that have been linked to each + * SOM object. Note that it is perfectly valid for there to be *no* GPOs + * linked to a SOM object. + */ +struct tevent_req * +ad_gpo_process_som_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_conn_ctx *conn, + struct ldb_context *ldb_ctx, + struct sdap_id_op *sdap_op, + struct sdap_options *opts, + struct dp_option *ad_options, + int timeout, + const char *target_dn, + const char *domain_name) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct ad_gpo_process_som_state *state; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ad_gpo_process_som_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->sdap_op = sdap_op; + state->opts = opts; + state->ad_options = ad_options; + state->timeout = timeout; + state->som_index = 0; + state->allow_enforced_only = 0; + + ret = ad_gpo_populate_som_list(state, ldb_ctx, target_dn, + &state->num_soms, &state->som_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to retrieve SOM List : [%d](%s)\n", + ret, sss_strerror(ret)); + ret = ENOENT; + goto immediately; + } + + if (state->som_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "target dn must have at least one parent\n"); + ret = EINVAL; + goto immediately; + } + + subreq = ad_domain_info_send(state, state->ev, conn, + state->sdap_op, domain_name); + + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ad_domain_info_send failed.\n"); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ad_gpo_site_name_retrieval_done, req); + + ret = EOK; + + immediately: + + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void +ad_gpo_site_name_retrieval_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_gpo_process_som_state *state; + int ret; + char *site = NULL; + char *site_override = NULL; + const char *attrs[] = {AD_AT_CONFIG_NC, NULL}; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_gpo_process_som_state); + + /* gpo code only cares about the site name */ + ret = ad_domain_info_recv(subreq, state, NULL, NULL, &site, NULL); + talloc_zfree(subreq); + + if (ret != EOK || site == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Could not autodiscover AD site. This is not fatal if " + "ad_site option was set.\n"); + } + + site_override = dp_opt_get_string(state->ad_options, AD_SITE); + if (site_override != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Overriding autodiscovered AD site value '%s' with '%s' from " + "configuration.\n", site ? site : "none", site_override); + } + + if (site == NULL && site_override == NULL) { + sss_log(SSS_LOG_WARNING, + "Could not autodiscover AD site value using DNS and ad_site " + "option was not set in configuration. GPO will not work. " + "To work around this issue you can use ad_site option in SSSD " + "configuration."); + DEBUG(SSSDBG_OP_FAILURE, + "Could not autodiscover AD site value using DNS and ad_site " + "option was not set in configuration. GPO will not work. " + "To work around this issue you can use ad_site option in SSSD " + "configuration.\n"); + tevent_req_error(req, ENOENT); + return; + } + + state->site_name = talloc_asprintf(state, "cn=%s", + site_override ? site_override + : site); + if (state->site_name == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Using AD site '%s'.\n", state->site_name); + + /* + * note: the configNC attribute is being retrieved here from the rootDSE + * entry. In future, since we already make an LDAP query for the rootDSE + * entry when LDAP connection is made, this attribute should really be + * retrieved at that point (see https://fedorahosted.org/sssd/ticket/2276) + */ + subreq = sdap_get_generic_send(state, state->ev, state->opts, + sdap_id_op_handle(state->sdap_op), + "", LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, NULL, 0, + state->timeout, + false); + + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ad_gpo_site_dn_retrieval_done, req); +} + +static void +ad_gpo_site_dn_retrieval_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_gpo_process_som_state *state; + int ret; + int dp_error; + int i = 0; + size_t reply_count; + struct sysdb_attrs **reply; + const char *configNC; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_gpo_process_som_state); + + ret = sdap_get_generic_recv(subreq, state, + &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get configNC: [%d](%s)\n", ret, sss_strerror(ret)); + ret = ENOENT; + goto done; + } + + /* make sure there is only one non-NULL reply returned */ + + if (reply_count < 1) { + DEBUG(SSSDBG_OP_FAILURE, "No configNC retrieved\n"); + ret = ENOENT; + goto done; + } else if (reply_count > 1) { + DEBUG(SSSDBG_OP_FAILURE, "Multiple replies for configNC\n"); + ret = ERR_INTERNAL; + goto done; + } else if (reply == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "reply_count is 1, but reply is NULL\n"); + ret = ERR_INTERNAL; + goto done; + } + + /* reply[0] holds requested attributes of single reply */ + ret = sysdb_attrs_get_string(reply[0], AD_AT_CONFIG_NC, &configNC); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + state->site_dn = + talloc_asprintf(state, "%s,cn=Sites,%s", state->site_name, configNC); + if (state->site_dn == NULL) { + ret = ENOMEM; + goto done; + } + + /* note that space was allocated for site_dn when allocating som_list */ + state->som_list[state->num_soms] = + talloc_zero(state->som_list, struct gp_som); + if (state->som_list[state->num_soms] == NULL) { + ret = ENOMEM; + goto done; + } + + state->som_list[state->num_soms]->som_dn = + talloc_steal(state->som_list[state->num_soms], state->site_dn); + + if (state->som_list[state->num_soms]->som_dn == NULL) { + ret = ENOMEM; + goto done; + } + + state->num_soms++; + state->som_list[state->num_soms] = NULL; + + i = 0; + while (state->som_list[i]) { + DEBUG(SSSDBG_TRACE_FUNC, "som_list[%d]->som_dn is %s\n", i, + state->som_list[i]->som_dn); + i++; + } + + ret = ad_gpo_get_som_attrs_step(req); + + done: + + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + +} +static errno_t +ad_gpo_get_som_attrs_step(struct tevent_req *req) +{ + const char *attrs[] = {AD_AT_GPLINK, AD_AT_GPOPTIONS, NULL}; + struct tevent_req *subreq; + struct ad_gpo_process_som_state *state; + + state = tevent_req_data(req, struct ad_gpo_process_som_state); + + struct gp_som *gp_som = state->som_list[state->som_index]; + + /* gp_som is NULL only after all SOMs have been processed */ + if (gp_som == NULL) return EOK; + + const char *som_dn = gp_som->som_dn; + subreq = sdap_get_generic_send(state, state->ev, state->opts, + sdap_id_op_handle(state->sdap_op), + som_dn, LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, NULL, 0, + state->timeout, + false); + + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ad_gpo_get_som_attrs_done, req); + return EAGAIN; +} + +static void +ad_gpo_get_som_attrs_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_gpo_process_som_state *state; + int ret; + int dp_error; + size_t num_results; + struct sysdb_attrs **results; + struct ldb_message_element *el = NULL; + uint8_t *raw_gplink_value; + uint8_t *raw_gpoptions_value; + uint32_t allow_enforced_only = 0; + struct gp_som *gp_som; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_gpo_process_som_state); + ret = sdap_get_generic_recv(subreq, state, + &num_results, &results); + talloc_zfree(subreq); + + if (ret != EOK) { + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get SOM attributes: [%d](%s)\n", + ret, sss_strerror(ret)); + ret = ENOENT; + goto done; + } + if ((num_results < 1) || (results == NULL)) { + DEBUG(SSSDBG_FUNC_DATA, "no attrs found for SOM; try next SOM.\n"); + state->som_index++; + ret = ad_gpo_get_som_attrs_step(req); + goto done; + } else if (num_results > 1) { + DEBUG(SSSDBG_OP_FAILURE, "Received multiple replies\n"); + ret = ERR_INTERNAL; + goto done; + } + + /* Get the gplink value, if available */ + ret = sysdb_attrs_get_el(results[0], AD_AT_GPLINK, &el); + + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_el() failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + if ((ret == ENOENT) || (el->num_values == 0)) { + DEBUG(SSSDBG_FUNC_DATA, "gpLink attr not found or has no values\n"); + state->som_index++; + ret = ad_gpo_get_som_attrs_step(req); + goto done; + } + + raw_gplink_value = el[0].values[0].data; + + ret = sysdb_attrs_get_el(results[0], AD_AT_GPOPTIONS, &el); + + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_el() failed\n"); + goto done; + } + + if ((ret == ENOENT) || (el->num_values == 0)) { + DEBUG(SSSDBG_TRACE_ALL, + "gpoptions attr not found or has no value; defaults to 0\n"); + allow_enforced_only = 0; + } else { + raw_gpoptions_value = el[0].values[0].data; + allow_enforced_only = strtouint32((char *)raw_gpoptions_value, NULL, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "strtouint32 failed: [%d](%s)\n", ret, sss_strerror(ret)); + goto done; + } + } + + gp_som = state->som_list[state->som_index]; + ret = ad_gpo_populate_gplink_list(gp_som, + gp_som->som_dn, + (char *)raw_gplink_value, + &gp_som->gplink_list, + state->allow_enforced_only); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ad_gpo_populate_gplink_list() failed\n"); + goto done; + } + + if (allow_enforced_only) { + state->allow_enforced_only = 1; + } + + state->som_index++; + ret = ad_gpo_get_som_attrs_step(req); + + done: + + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +int +ad_gpo_process_som_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct gp_som ***som_list) +{ + + struct ad_gpo_process_som_state *state = + tevent_req_data(req, struct ad_gpo_process_som_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *som_list = talloc_steal(mem_ctx, state->som_list); + return EOK; +} + +/* == ad_gpo_process_gpo_send/recv helpers ================================= */ + +/* + * This function examines the gp_gplink objects in each gp_som object specified + * in the input som_list, and populates the _candidate_gpos output parameter's + * gpo_dn fields with prioritized list of GPO DNs. Prioritization ensures that: + * - GPOs linked to an OU will be applied after GPOs linked to a Domain, + * which will be applied after GPOs linked to a Site. + * - multiple GPOs linked to a single SOM are applied in their link order + * (i.e. 1st GPO linked to SOM is applied before 2nd GPO linked to SOM, etc). + * - enforced GPOs are applied after unenforced GPOs. + * + * As such, the _candidate_gpos output's dn fields looks like (in link order): + * [unenforced {Site, Domain, OU}; enforced {OU, Domain, Site}] + * + * Note that in the case of conflicting policy settings, GPOs appearing later + * in the list will trump GPOs appearing earlier in the list. Therefore the + * enforced GPOs are applied in revers order after the unenforced GPOs to + * make sure the enforced setting form the highest level will be applied. + * + * GPO processing details can be found e.g. at + * https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/dn581922(v%3Dws.11) + */ +static errno_t +ad_gpo_populate_candidate_gpos(TALLOC_CTX *mem_ctx, + struct gp_som **som_list, + struct gp_gpo ***_candidate_gpos, + int *_num_candidate_gpos) +{ + + TALLOC_CTX *tmp_ctx = NULL; + struct gp_som *gp_som = NULL; + struct gp_gplink *gp_gplink = NULL; + struct gp_gpo **candidate_gpos = NULL; + int num_candidate_gpos = 0; + const char **enforced_gpo_dns = NULL; + const char **unenforced_gpo_dns = NULL; + int gpo_dn_idx = 0; + int num_enforced = 0; + int enforced_idx = 0; + int num_unenforced = 0; + int unenforced_idx = 0; + int i = 0; + int j = 0; + int ret; + size_t som_count = 0; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + while (som_list[i]) { + gp_som = som_list[i]; + j = 0; + while (gp_som && gp_som->gplink_list && gp_som->gplink_list[j]) { + gp_gplink = gp_som->gplink_list[j]; + if (gp_gplink == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "unexpected null gp_gplink\n"); + ret = EINVAL; + goto done; + } + if (gp_gplink->enforced) { + num_enforced++; + } else { + num_unenforced++; + } + j++; + } + i++; + } + som_count = i; + + num_candidate_gpos = num_enforced + num_unenforced; + + if (num_candidate_gpos == 0) { + *_candidate_gpos = NULL; + *_num_candidate_gpos = 0; + ret = EOK; + goto done; + } + + enforced_gpo_dns = talloc_array(tmp_ctx, const char *, num_enforced + 1); + if (enforced_gpo_dns == NULL) { + ret = ENOMEM; + goto done; + } + + unenforced_gpo_dns = talloc_array(tmp_ctx, const char *, num_unenforced + 1); + if (unenforced_gpo_dns == NULL) { + ret = ENOMEM; + goto done; + } + + i = som_count -1 ; + while (i >= 0) { + gp_som = som_list[i]; + + /* For unenforced_gpo_dns the most specific GPOs with the highest + * priority should be the last. We start with the top-level SOM and go + * down to the most specific one and add the unenforced following the + * gplink_list where the GPO with the highest priority comes last. */ + j = 0; + while (gp_som && gp_som->gplink_list && gp_som->gplink_list[j]) { + gp_gplink = gp_som->gplink_list[j]; + + if (!gp_gplink->enforced) { + unenforced_gpo_dns[unenforced_idx] = + talloc_steal(unenforced_gpo_dns, gp_gplink->gpo_dn); + + if (unenforced_gpo_dns[unenforced_idx] == NULL) { + ret = ENOMEM; + goto done; + } + unenforced_idx++; + } + j++; + } + i--; + } + + i = 0; + while (som_list[i]) { + gp_som = som_list[i]; + + /* For enforced GPOs we start processing with the most specific SOM to + * make sur enforced GPOs from higher levels override to lower level + * ones. According to the 'Group Policy Inheritance' tab in the + * Windows 'Goup Policy Management' utility in the same SOM the link + * order is still observed and an enforced GPO with a lower link order + * value still overrides an enforced GPO with a higher link order. */ + j = 0; + while (gp_som && gp_som->gplink_list && gp_som->gplink_list[j]) { + gp_gplink = gp_som->gplink_list[j]; + if (gp_gplink == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "unexpected null gp_gplink\n"); + ret = EINVAL; + goto done; + } + + if (gp_gplink->enforced) { + enforced_gpo_dns[enforced_idx] = + talloc_steal(enforced_gpo_dns, gp_gplink->gpo_dn); + if (enforced_gpo_dns[enforced_idx] == NULL) { + ret = ENOMEM; + goto done; + } + enforced_idx++; + } + j++; + } + i++; + } + enforced_gpo_dns[num_enforced] = NULL; + unenforced_gpo_dns[num_unenforced] = NULL; + + candidate_gpos = talloc_array(tmp_ctx, + struct gp_gpo *, + num_candidate_gpos + 1); + + if (candidate_gpos == NULL) { + ret = ENOMEM; + goto done; + } + + gpo_dn_idx = 0; + for (i = 0; i < num_unenforced; i++) { + candidate_gpos[gpo_dn_idx] = talloc_zero(candidate_gpos, struct gp_gpo); + if (candidate_gpos[gpo_dn_idx] == NULL) { + ret = ENOMEM; + goto done; + } + candidate_gpos[gpo_dn_idx]->gpo_dn = + talloc_steal(candidate_gpos[gpo_dn_idx], unenforced_gpo_dns[i]); + + if (candidate_gpos[gpo_dn_idx]->gpo_dn == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, + "candidate_gpos[%d]->gpo_dn: %s\n", + gpo_dn_idx, candidate_gpos[gpo_dn_idx]->gpo_dn); + gpo_dn_idx++; + } + + for (i = 0; i < num_enforced; i++) { + + candidate_gpos[gpo_dn_idx] = talloc_zero(candidate_gpos, struct gp_gpo); + if (candidate_gpos[gpo_dn_idx] == NULL) { + ret = ENOMEM; + goto done; + } + + candidate_gpos[gpo_dn_idx]->gpo_dn = + talloc_steal(candidate_gpos[gpo_dn_idx], enforced_gpo_dns[i]); + if (candidate_gpos[gpo_dn_idx]->gpo_dn == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "candidate_gpos[%d]->gpo_dn: %s\n", + gpo_dn_idx, candidate_gpos[gpo_dn_idx]->gpo_dn); + gpo_dn_idx++; + } + + candidate_gpos[gpo_dn_idx] = NULL; + + *_candidate_gpos = talloc_steal(mem_ctx, candidate_gpos); + *_num_candidate_gpos = num_candidate_gpos; + + ret = EOK; + + done: + talloc_free(tmp_ctx); + return ret; +} + +/* + * This function parses the input_path into its components, replaces each + * back slash ('\') with a forward slash ('/'), and populates the output params. + * + * The smb_server output is constructed by concatenating the following elements: + * - SMB_STANDARD_URI ("smb://") + * - server_hostname (which replaces domain_name in input path) + * The smb_share and smb_path outputs are extracted from the input_path. + * + * Example: if input_path = "\\foo.com\SysVol\foo.com\..." and + * server_hostname = "adserver.foo.com", then + * _smb_server = "smb://adserver.foo.com" + * _smb_share = "SysVol" + * _smb_path = "/foo.com/..." + * + * Note that the input_path must have at least four forward slash separators. + * For example, input_path = "\\foo.com\SysVol" is not a valid input_path, + * because it has only three forward slash separators. + */ +static errno_t +ad_gpo_extract_smb_components(TALLOC_CTX *mem_ctx, + char *server_hostname, + char *input_path, + const char **_smb_server, + const char **_smb_share, + const char **_smb_path) +{ + char *ptr; + const char delim = '\\'; + int ret; + int num_seps = 0; + char *smb_path = NULL; + char *smb_share = NULL; + + DEBUG(SSSDBG_TRACE_ALL, "input_path: %s\n", input_path); + + if (input_path == NULL || + *input_path == '\0' || + _smb_server == NULL || + _smb_share == NULL || + _smb_path == NULL) { + ret = EINVAL; + goto done; + } + + ptr = input_path; + while ((ptr = strchr(ptr, delim))) { + num_seps++; + if (num_seps == 3) { + /* replace the slash before the share name with null string */ + + *ptr = '\0'; + ptr++; + smb_share = ptr; + continue; + } else if (num_seps == 4) { + /* replace the slash after the share name with null string */ + *ptr = '\0'; + ptr++; + smb_path = ptr; + continue; + } + *ptr = '/'; + ptr++; + } + + if (num_seps == 0) { + ret = EINVAL; + goto done; + } + + if (smb_path == NULL) { + ret = EINVAL; + goto done; + } + + *_smb_server = talloc_asprintf(mem_ctx, "%s%s", + SMB_STANDARD_URI, + server_hostname); + if (*_smb_server == NULL) { + ret = ENOMEM; + goto done; + } + + *_smb_share = talloc_asprintf(mem_ctx, "/%s", smb_share); + if (*_smb_share == NULL) { + ret = ENOMEM; + goto done; + } + + *_smb_path = talloc_asprintf(mem_ctx, "/%s", smb_path); + if (*_smb_path == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + + done: + return ret; +} + +/* + * This function populates the _cse_guid_list output parameter by parsing the + * input raw_machine_ext_names_value into an array of cse_guid strings. + * + * The raw_machine_ext_names_value is a single string in the following format: + * "[{cse_guid_1}{tool_guid1}]...[{cse_guid_n}{tool_guid_n}]" + */ +static errno_t +ad_gpo_parse_machine_ext_names(TALLOC_CTX *mem_ctx, + char *raw_machine_ext_names_value, + const char ***_gpo_cse_guids, + int *_num_gpo_cse_guids) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *ptr; + char *first; + char *last; + char *cse_guid; + char *tool_guid; + const char delim = ']'; + const char **gpo_cse_guids; + int i; + int ret; + int num_gpo_cse_guids = 0; + + if (raw_machine_ext_names_value == NULL || + *raw_machine_ext_names_value == '\0' || + _gpo_cse_guids == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ptr = raw_machine_ext_names_value; + while ((ptr = strchr(ptr, delim))) { + ptr++; + num_gpo_cse_guids++; + } + + if (num_gpo_cse_guids == 0) { + ret = EINVAL; + goto done; + } + + gpo_cse_guids = talloc_array(tmp_ctx, const char *, num_gpo_cse_guids + 1); + if (gpo_cse_guids == NULL) { + ret = ENOMEM; + goto done; + } + + ptr = raw_machine_ext_names_value; + for (i = 0; i < num_gpo_cse_guids; i++) { + first = ptr + 1; + last = strchr(first, delim); + if (last == NULL) { + break; + } + *last = '\0'; + last++; + cse_guid = first; + first ++; + tool_guid = strchr(first, '{'); + if (tool_guid == NULL) { + break; + } + *tool_guid = '\0'; + gpo_cse_guids[i] = talloc_strdup(gpo_cse_guids, cse_guid); + ptr = last; + } + gpo_cse_guids[i] = NULL; + + DEBUG(SSSDBG_TRACE_ALL, "num_gpo_cse_guids: %d\n", num_gpo_cse_guids); + + for (i = 0; i < num_gpo_cse_guids; i++) { + DEBUG(SSSDBG_TRACE_ALL, + "gpo_cse_guids[%d] is %s\n", i, gpo_cse_guids[i]); + } + + *_gpo_cse_guids = talloc_steal(mem_ctx, gpo_cse_guids); + *_num_gpo_cse_guids = num_gpo_cse_guids; + ret = EOK; + + done: + talloc_free(tmp_ctx); + return ret; +} + +enum ndr_err_code +ad_gpo_ndr_pull_security_descriptor(struct ndr_pull *ndr, int ndr_flags, + struct security_descriptor *r); + +/* + * This function parses the input data blob and assigns the resulting + * security_descriptor object to the _gpo_sd output parameter. + */ +static errno_t ad_gpo_parse_sd(TALLOC_CTX *mem_ctx, + uint8_t *data, + size_t length, + struct security_descriptor **_gpo_sd) +{ + + struct ndr_pull *ndr_pull = NULL; + struct security_descriptor sd; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + + blob.data = data; + blob.length = length; + + ndr_pull = ndr_pull_init_blob(&blob, mem_ctx); + if (ndr_pull == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ndr_pull_init_blob() failed.\n"); + return EINVAL; + } + + ndr_err = ad_gpo_ndr_pull_security_descriptor(ndr_pull, + NDR_SCALARS|NDR_BUFFERS, + &sd); + + if (ndr_err != NDR_ERR_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to pull security descriptor\n"); + return EINVAL; + } + + *_gpo_sd = talloc_memdup(mem_ctx, &sd, sizeof(struct security_descriptor)); + + return EOK; +} + +/* == ad_gpo_process_gpo_send/recv implementation ========================== */ + +struct ad_gpo_process_gpo_state { + struct ad_access_ctx *access_ctx; + struct tevent_context *ev; + struct sdap_id_op *sdap_op; + struct dp_option *ad_options; + struct sdap_options *opts; + char *server_hostname; + struct sss_domain_info *host_domain; + int timeout; + struct gp_gpo **candidate_gpos; + int num_candidate_gpos; + int gpo_index; +}; + +static errno_t ad_gpo_get_gpo_attrs_step(struct tevent_req *req); +static void ad_gpo_get_gpo_attrs_done(struct tevent_req *subreq); + +/* + * This function uses the input som_list to populate a prioritized list of + * gp_gpo objects, prioritized based on SOM type, link order, and whether the + * GPO is "enforced". This list represents the initial set of candidate GPOs + * that might be applicable to the target. This list can not be expanded, but + * it might be reduced based on subsequent filtering steps. The GPO object DNs + * are used to retrieve certain LDAP attributes of each GPO object, that are + * parsed into the various fields of the gp_gpo object. + */ +struct tevent_req * +ad_gpo_process_gpo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_op *sdap_op, + struct sdap_options *opts, + char *server_hostname, + struct sss_domain_info *host_domain, + struct ad_access_ctx *access_ctx, + int timeout, + struct gp_som **som_list) +{ + struct tevent_req *req; + struct ad_gpo_process_gpo_state *state; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ad_gpo_process_gpo_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->sdap_op = sdap_op; + state->ad_options = access_ctx->ad_options; + state->opts = opts; + state->server_hostname = server_hostname; + state->host_domain = host_domain; + state->access_ctx = access_ctx; + state->timeout = timeout; + state->gpo_index = 0; + state->candidate_gpos = NULL; + state->num_candidate_gpos = 0; + + ret = ad_gpo_populate_candidate_gpos(state, + som_list, + &state->candidate_gpos, + &state->num_candidate_gpos); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to retrieve GPO List: [%d](%s)\n", + ret, sss_strerror(ret)); + goto immediately; + } + + if (state->candidate_gpos == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "no gpos found\n"); + ret = ENOENT; + goto immediately; + } + + ret = ad_gpo_get_gpo_attrs_step(req); + +immediately: + + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t +ad_gpo_get_gpo_attrs_step(struct tevent_req *req) +{ + const char *attrs[] = AD_GPO_ATTRS; + struct tevent_req *subreq; + struct ad_gpo_process_gpo_state *state; + + state = tevent_req_data(req, struct ad_gpo_process_gpo_state); + + struct gp_gpo *gp_gpo = state->candidate_gpos[state->gpo_index]; + + /* gp_gpo is NULL only after all GPOs have been processed */ + if (gp_gpo == NULL) return EOK; + + const char *gpo_dn = gp_gpo->gpo_dn; + + subreq = sdap_sd_search_send(state, state->ev, + state->opts, sdap_id_op_handle(state->sdap_op), + gpo_dn, SECINFO_DACL, attrs, state->timeout); + + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_sd_search_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ad_gpo_get_gpo_attrs_done, req); + return EAGAIN; +} + +static errno_t +ad_gpo_sd_process_attrs(struct tevent_req *req, + char *smb_host, + struct sysdb_attrs *result); +void +ad_gpo_get_sd_referral_done(struct tevent_req *subreq); + +static struct tevent_req * +ad_gpo_get_sd_referral_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ad_access_ctx *access_ctx, + struct sdap_options *opts, + const char *referral, + struct sss_domain_info *host_domain, + int timeout); +errno_t +ad_gpo_get_sd_referral_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **_smb_host, + struct sysdb_attrs **_reply); + +static void +ad_gpo_get_gpo_attrs_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_gpo_process_gpo_state *state; + int ret; + int dp_error; + size_t num_results, refcount; + struct sysdb_attrs **results; + char **refs; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_gpo_process_gpo_state); + + ret = sdap_sd_search_recv(subreq, state, + &num_results, &results, + &refcount, &refs); + talloc_zfree(subreq); + + if (ret != EOK) { + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get GPO attributes: [%d](%s)\n", + ret, sss_strerror(ret)); + ret = ENOENT; + goto done; + } + + if ((num_results < 1) || (results == NULL)) { + if (refcount == 1) { + /* If we were redirected to a referral, process it. + * There must be a single referral result here; if we get + * more than one (or zero) it's a bug. + */ + + subreq = ad_gpo_get_sd_referral_send(state, state->ev, + state->access_ctx, + state->opts, + refs[0], + state->host_domain, + state->timeout); + if (!subreq) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ad_gpo_get_sd_referral_done, req); + ret = EAGAIN; + goto done; + + } else { + const char *gpo_dn = state->candidate_gpos[state->gpo_index]->gpo_dn; + + DEBUG(SSSDBG_OP_FAILURE, + "No attrs found for GPO [%s].\n", gpo_dn); + ret = ENOENT; + goto done; + } + } else if (num_results > 1) { + DEBUG(SSSDBG_OP_FAILURE, "Received multiple replies\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = ad_gpo_sd_process_attrs(req, state->server_hostname, results[0]); + +done: + + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +void +ad_gpo_get_sd_referral_done(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error; + struct sysdb_attrs *reply; + char *smb_host; + + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ad_gpo_process_gpo_state *state = + tevent_req_data(req, struct ad_gpo_process_gpo_state); + + ret = ad_gpo_get_sd_referral_recv(subreq, state, &smb_host, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + /* Terminate the sdap_id_op */ + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get referred GPO attributes: [%d](%s)\n", + ret, sss_strerror(ret)); + + goto done; + } + + /* Lookup succeeded. Process it */ + ret = ad_gpo_sd_process_attrs(req, smb_host, reply); + +done: + + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static bool machine_ext_names_is_blank(char *attr_value) +{ + char *ptr; + + if (attr_value == NULL) { + return true; + } + + ptr = attr_value; + for (; *ptr != '\0'; ptr++) { + if (!isspace(*ptr)) { + return false; + } + } + + return true; +} + +static errno_t +ad_gpo_missing_or_unreadable_attr(struct ad_gpo_process_gpo_state *state, + struct tevent_req *req) +{ + bool ignore_unreadable = dp_opt_get_bool(state->ad_options, + AD_GPO_IGNORE_UNREADABLE); + + if (ignore_unreadable) { + /* If admins decided to skip GPOs with unreadable + * attributes just log the SID of skipped GPO */ + DEBUG(SSSDBG_TRACE_FUNC, + "Group Policy Container with DN [%s] has unreadable or missing " + "attributes -> skipping this GPO " + "(ad_gpo_ignore_unreadable = True)\n", + state->candidate_gpos[state->gpo_index]->gpo_dn); + state->gpo_index++; + return ad_gpo_get_gpo_attrs_step(req); + } else { + /* Inform in logs and syslog that this GPO can + * not be processed due to unreadable or missing + * attributes and point to possible server side + * and client side solutions. */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Group Policy Container with DN [%s] is unreadable or has " + "unreadable or missing attributes. In order to fix this " + "make sure that this AD object has following attributes " + "readable: nTSecurityDescriptor, cn, gPCFileSysPath, " + "gPCMachineExtensionNames, gPCFunctionalityVersion, flags. " + "Alternatively if you do not have access to the server or can " + "not change permissions on this object, you can use option " + "ad_gpo_ignore_unreadable = True which will skip this GPO. " + "See ad_gpo_ignore_unreadable in 'man sssd-ad' for details.\n", + state->candidate_gpos[state->gpo_index]->gpo_dn); + sss_log(SSS_LOG_ERR, + "Group Policy Container with DN [%s] is unreadable or has " + "unreadable or missing attributes. In order to fix this " + "make sure that this AD object has following attributes " + "readable: nTSecurityDescriptor, cn, gPCFileSysPath, " + "gPCMachineExtensionNames, gPCFunctionalityVersion, flags. " + "Alternatively if you do not have access to the server or can " + "not change permissions on this object, you can use option " + "ad_gpo_ignore_unreadable = True which will skip this GPO. " + "See ad_gpo_ignore_unreadable in 'man sssd-ad' for details.\n", + state->candidate_gpos[state->gpo_index]->gpo_dn); + return EFAULT; + } +} + +static errno_t +ad_gpo_sd_process_attrs(struct tevent_req *req, + char *smb_host, + struct sysdb_attrs *result) +{ + struct ad_gpo_process_gpo_state *state; + struct gp_gpo *gp_gpo; + int ret; + struct ldb_message_element *el = NULL; + const char *gpo_guid = NULL; + const char *raw_file_sys_path = NULL; + char *file_sys_path = NULL; + uint8_t *raw_machine_ext_names = NULL; + + state = tevent_req_data(req, struct ad_gpo_process_gpo_state); + gp_gpo = state->candidate_gpos[state->gpo_index]; + + /* retrieve AD_AT_CN */ + ret = sysdb_attrs_get_string(result, AD_AT_CN, &gpo_guid); + if (ret == ENOENT) { + ret = ad_gpo_missing_or_unreadable_attr(state, req); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + gp_gpo->gpo_guid = talloc_steal(gp_gpo, gpo_guid); + if (gp_gpo->gpo_guid == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "populating attrs for gpo_guid: %s\n", + gp_gpo->gpo_guid); + + /* retrieve AD_AT_FILE_SYS_PATH */ + ret = sysdb_attrs_get_string(result, + AD_AT_FILE_SYS_PATH, + &raw_file_sys_path); + + if (ret == ENOENT) { + ret = ad_gpo_missing_or_unreadable_attr(state, req); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + file_sys_path = talloc_strdup(gp_gpo, raw_file_sys_path); + + ret = ad_gpo_extract_smb_components(gp_gpo, smb_host, + file_sys_path, &gp_gpo->smb_server, + &gp_gpo->smb_share, &gp_gpo->smb_path); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "unable to extract smb components from file_sys_path: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "smb_server: %s\n", gp_gpo->smb_server); + DEBUG(SSSDBG_TRACE_ALL, "smb_share: %s\n", gp_gpo->smb_share); + DEBUG(SSSDBG_TRACE_ALL, "smb_path: %s\n", gp_gpo->smb_path); + + /* retrieve AD_AT_FUNC_VERSION */ + ret = sysdb_attrs_get_int32_t(result, AD_AT_FUNC_VERSION, + &gp_gpo->gpo_func_version); + if (ret == ENOENT) { + /* If this attribute is missing we can skip the GPO. It will + * be filtered out according to MS-GPOL: + * https://msdn.microsoft.com/en-us/library/cc232538.aspx */ + DEBUG(SSSDBG_TRACE_ALL, "GPO with GUID %s is missing attribute " + AD_AT_FUNC_VERSION " and will be skipped.\n", gp_gpo->gpo_guid); + state->gpo_index++; + ret = ad_gpo_get_gpo_attrs_step(req); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_int32_t failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "gpo_func_version: %d\n", + gp_gpo->gpo_func_version); + + /* retrieve AD_AT_FLAGS */ + ret = sysdb_attrs_get_int32_t(result, AD_AT_FLAGS, + &gp_gpo->gpo_flags); + if (ret == ENOENT) { + ret = ad_gpo_missing_or_unreadable_attr(state, req); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_int32_t failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "gpo_flags: %d\n", gp_gpo->gpo_flags); + + /* retrieve AD_AT_NT_SEC_DESC */ + ret = sysdb_attrs_get_el(result, AD_AT_NT_SEC_DESC, &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_el() failed\n"); + goto done; + } + if ((ret == ENOENT) || (el->num_values == 0)) { + DEBUG(SSSDBG_OP_FAILURE, + "nt_sec_desc attribute not found or has no value\n"); + ret = ad_gpo_missing_or_unreadable_attr(state, req); + goto done; + } + + ret = ad_gpo_parse_sd(gp_gpo, el[0].values[0].data, el[0].values[0].length, + &gp_gpo->gpo_sd); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_gpo_parse_sd() failed\n"); + goto done; + } + + /* retrieve AD_AT_MACHINE_EXT_NAMES */ + ret = sysdb_attrs_get_el(result, AD_AT_MACHINE_EXT_NAMES, &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_el() failed\n"); + goto done; + } + + if ((ret == ENOENT) || (el->num_values == 0) + || machine_ext_names_is_blank((char *) el[0].values[0].data)) { + /* + * if gpo has no machine_ext_names (which is perfectly valid: it could + * have only user_ext_names, for example), we continue to next gpo + */ + DEBUG(SSSDBG_TRACE_ALL, + "machine_ext_names attribute not found or has no value\n"); + state->gpo_index++; + } else { + raw_machine_ext_names = el[0].values[0].data; + + ret = ad_gpo_parse_machine_ext_names(gp_gpo, + (char *)raw_machine_ext_names, + &gp_gpo->gpo_cse_guids, + &gp_gpo->num_gpo_cse_guids); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ad_gpo_parse_machine_ext_names() failed\n"); + goto done; + } + + state->gpo_index++; + } + + ret = ad_gpo_get_gpo_attrs_step(req); + + done: + + return ret; +} + +int +ad_gpo_process_gpo_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct gp_gpo ***candidate_gpos, + int *num_candidate_gpos) +{ + struct ad_gpo_process_gpo_state *state = + tevent_req_data(req, struct ad_gpo_process_gpo_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *candidate_gpos = talloc_steal(mem_ctx, state->candidate_gpos); + *num_candidate_gpos = state->num_candidate_gpos; + return EOK; +} + +/* == ad_gpo_process_cse_send/recv helpers ================================= */ +static errno_t +create_cse_send_buffer(TALLOC_CTX *mem_ctx, + const char *smb_server, + const char *smb_share, + const char *smb_path, + const char *smb_cse_suffix, + int cached_gpt_version, + struct io_buffer **io_buf) +{ + struct io_buffer *buf; + size_t rp; + int smb_server_length; + int smb_share_length; + int smb_path_length; + int smb_cse_suffix_length; + + smb_server_length = strlen(smb_server); + smb_share_length = strlen(smb_share); + smb_path_length = strlen(smb_path); + smb_cse_suffix_length = strlen(smb_cse_suffix); + + buf = talloc(mem_ctx, struct io_buffer); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + buf->size = 5 * sizeof(uint32_t); + buf->size += smb_server_length + smb_share_length + smb_path_length + + smb_cse_suffix_length; + + DEBUG(SSSDBG_TRACE_ALL, "buffer size: %zu\n", buf->size); + + buf->data = talloc_size(buf, buf->size); + if (buf->data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + talloc_free(buf); + return ENOMEM; + } + + rp = 0; + /* cached_gpt_version */ + SAFEALIGN_SET_UINT32(&buf->data[rp], cached_gpt_version, &rp); + + /* smb_server */ + SAFEALIGN_SET_UINT32(&buf->data[rp], smb_server_length, &rp); + safealign_memcpy(&buf->data[rp], smb_server, smb_server_length, &rp); + + /* smb_share */ + SAFEALIGN_SET_UINT32(&buf->data[rp], smb_share_length, &rp); + safealign_memcpy(&buf->data[rp], smb_share, smb_share_length, &rp); + + /* smb_path */ + SAFEALIGN_SET_UINT32(&buf->data[rp], smb_path_length, &rp); + safealign_memcpy(&buf->data[rp], smb_path, smb_path_length, &rp); + + /* smb_cse_suffix */ + SAFEALIGN_SET_UINT32(&buf->data[rp], smb_cse_suffix_length, &rp); + safealign_memcpy(&buf->data[rp], smb_cse_suffix, smb_cse_suffix_length, &rp); + + *io_buf = buf; + return EOK; +} + +static errno_t +ad_gpo_parse_gpo_child_response(uint8_t *buf, + ssize_t size, + uint32_t *_sysvol_gpt_version, + uint32_t *_result) +{ + + int ret; + size_t p = 0; + uint32_t sysvol_gpt_version; + uint32_t result; + + /* sysvol_gpt_version */ + SAFEALIGN_COPY_UINT32_CHECK(&sysvol_gpt_version, buf + p, size, &p); + + /* operation result code */ + SAFEALIGN_COPY_UINT32_CHECK(&result, buf + p, size, &p); + + *_sysvol_gpt_version = sysvol_gpt_version; + *_result = result; + + ret = EOK; + return ret; +} + +/* == ad_gpo_process_cse_send/recv implementation ========================== */ + +struct ad_gpo_process_cse_state { + struct tevent_context *ev; + struct sss_domain_info *domain; + int gpo_timeout_option; + const char *gpo_guid; + const char *smb_path; + const char *smb_cse_suffix; + pid_t child_pid; + uint8_t *buf; + ssize_t len; + struct child_io_fds *io; +}; + +static errno_t gpo_fork_child(struct tevent_req *req); +static void gpo_cse_step(struct tevent_req *subreq); +static void gpo_cse_done(struct tevent_req *subreq); + +/* + * This cse-specific function (GP_EXT_GUID_SECURITY) sends the input smb uri + * components and cached_gpt_version to the gpo child, which, in turn, + * will download the GPT.INI file and policy files (as needed) and store + * them in the GPO_CACHE directory. Note that if the send_to_child input is + * false, this function simply completes the request. + */ +struct tevent_req * +ad_gpo_process_cse_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + bool send_to_child, + struct sss_domain_info *domain, + const char *gpo_guid, + const char *smb_server, + const char *smb_share, + const char *smb_path, + const char *smb_cse_suffix, + int cached_gpt_version, + int gpo_timeout_option) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct ad_gpo_process_cse_state *state; + struct io_buffer *buf = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ad_gpo_process_cse_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (!send_to_child) { + /* + * if we don't need to talk to child (b/c cache timeout is still valid), + * we simply complete the request + */ + ret = EOK; + goto immediately; + } + + state->ev = ev; + state->buf = NULL; + state->len = 0; + state->domain = domain; + state->gpo_timeout_option = gpo_timeout_option; + state->gpo_guid = gpo_guid; + state->smb_path = smb_path; + state->smb_cse_suffix = smb_cse_suffix; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto immediately; + } + + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + /* prepare the data to pass to child */ + ret = create_cse_send_buffer(state, smb_server, smb_share, smb_path, + smb_cse_suffix, cached_gpt_version, &buf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "create_cse_send_buffer failed.\n"); + goto immediately; + } + + ret = gpo_fork_child(req); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "gpo_fork_child failed.\n"); + goto immediately; + } + + subreq = write_pipe_send(state, ev, buf->data, buf->size, + state->io->write_to_child_fd); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, gpo_cse_step, req); + + return req; + +immediately: + + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void gpo_cse_step(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_gpo_process_cse_state *state; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_gpo_process_cse_state); + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); + + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, gpo_cse_done, req); +} + +static void gpo_cse_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_gpo_process_cse_state *state; + uint32_t sysvol_gpt_version = -1; + uint32_t child_result; + time_t now; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_gpo_process_cse_state); + int ret; + + ret = read_pipe_recv(subreq, state, &state->buf, &state->len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->read_from_child_fd); + + ret = ad_gpo_parse_gpo_child_response(state->buf, state->len, + &sysvol_gpt_version, &child_result); + if (ret != EOK) { + if (ret == EINVAL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ad_gpo_parse_gpo_child_response failed: [%d][%s]. " + "Broken GPO data received from AD. Check AD child logs for " + "more information.\n", + ret, sss_strerror(ret)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "ad_gpo_parse_gpo_child_response failed: [%d][%s]\n", + ret, sss_strerror(ret)); + } + + tevent_req_error(req, ret); + return; + } else if (child_result != 0){ + DEBUG(SSSDBG_CRIT_FAILURE, + "Error in gpo_child: [%d][%s]\n", + child_result, strerror(child_result)); + tevent_req_error(req, child_result); + return; + } + + now = time(NULL); + DEBUG(SSSDBG_TRACE_FUNC, "sysvol_gpt_version: %d\n", sysvol_gpt_version); + ret = sysdb_gpo_store_gpo(state->domain, state->gpo_guid, sysvol_gpt_version, + state->gpo_timeout_option, now); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to store gpo cache entry: [%d](%s}\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +int ad_gpo_process_cse_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static errno_t +gpo_fork_child(struct tevent_req *req) +{ + int pipefd_to_child[2] = PIPE_INIT; + int pipefd_from_child[2] = PIPE_INIT; + pid_t pid; + errno_t ret; + const char **extra_args; + int c = 0; + struct ad_gpo_process_cse_state *state; + + state = tevent_req_data(req, struct ad_gpo_process_cse_state); + + extra_args = talloc_array(state, const char *, 2); + + extra_args[c] = talloc_asprintf(extra_args, "--chain-id=%lu", + sss_chain_id_get()); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto fail; + } + c++; + + extra_args[c] = NULL; + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe (from) failed [%d][%s].\n", errno, strerror(errno)); + goto fail; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe (to) failed [%d][%s].\n", errno, strerror(errno)); + goto fail; + } + + pid = fork(); + + if (pid == 0) { /* child */ + exec_child_ex(state, + pipefd_to_child, pipefd_from_child, + GPO_CHILD, GPO_CHILD_LOG_FILE, extra_args, false, + STDIN_FILENO, AD_GPO_CHILD_OUT_FILENO); + + /* We should never get here */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec gpo_child:\n"); + } else if (pid > 0) { /* parent */ + state->child_pid = pid; + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->read_from_child_fd); + sss_fd_nonblocking(state->io->write_to_child_fd); + + ret = child_handler_setup(state->ev, pid, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set up child signal handler\n"); + goto fail; + } + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fork failed [%d][%s].\n", errno, strerror(errno)); + goto fail; + } + + return EOK; + +fail: + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + return ret; +} + +struct ad_gpo_get_sd_referral_state { + struct tevent_context *ev; + struct ad_access_ctx *access_ctx; + struct sdap_options *opts; + struct sss_domain_info *host_domain; + struct sss_domain_info *ref_domain; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *ref_op; + int timeout; + char *gpo_dn; + char *smb_host; + + + struct sysdb_attrs *reply; +}; + +static void +ad_gpo_get_sd_referral_conn_done(struct tevent_req *subreq); + +static struct tevent_req * +ad_gpo_get_sd_referral_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ad_access_ctx *access_ctx, + struct sdap_options *opts, + const char *referral, + struct sss_domain_info *host_domain, + int timeout) +{ + errno_t ret; + struct tevent_req *req; + struct ad_gpo_get_sd_referral_state *state; + struct tevent_req *subreq; + LDAPURLDesc *lud = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct ad_gpo_get_sd_referral_state); + if (!req) return NULL; + + state->ev = ev; + state->access_ctx = access_ctx; + state->opts = opts; + state->host_domain = host_domain; + state->timeout = timeout; + + /* Parse the URL for the domain */ + ret = ldap_url_parse(referral, &lud); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse referral URI (%s)!\n", referral); + ret = EINVAL; + goto done; + } + + state->gpo_dn = talloc_strdup(state, lud->lud_dn); + if (!state->gpo_dn) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not copy referral DN (%s)!\n", lud->lud_dn); + ldap_free_urldesc(lud); + ret = ENOMEM; + goto done; + } + + /* Active Directory returns the domain name as the hostname + * in these referrals, so we can use that to look up the + * necessary connection. + */ + state->ref_domain = find_domain_by_name(state->host_domain, + lud->lud_host, true); + if (!state->ref_domain) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not find domain matching [%s]\n", + lud->lud_host); + ldap_free_urldesc(lud); + ret = EIO; + goto done; + } + + ldap_free_urldesc(lud); + lud = NULL; + + state->conn = ad_get_dom_ldap_conn(state->access_ctx->ad_id_ctx, + state->ref_domain); + if (!state->conn) { + DEBUG(SSSDBG_OP_FAILURE, + "No connection for %s\n", state->ref_domain->name); + ret = EINVAL; + goto done; + } + + /* Get the hostname we're going to connect to. + * We'll need this later for performing the samba + * connection. + */ + ret = ldap_url_parse(state->conn->service->uri, &lud); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse service URI (%s)!\n", referral); + ret = EINVAL; + goto done; + } + + state->smb_host = talloc_strdup(state, lud->lud_host); + ldap_free_urldesc(lud); + if (!state->smb_host) { + ret = ENOMEM; + goto done; + } + + /* Start an ID operation for the referral */ + state->ref_op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->ref_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Establish the sdap_id_op connection */ + subreq = sdap_id_op_connect_send(state->ref_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, sss_strerror(ret)); + goto done; + } + tevent_req_set_callback(subreq, ad_gpo_get_sd_referral_conn_done, req); + +done: + + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void +ad_gpo_get_sd_referral_search_done(struct tevent_req *subreq); + +static void +ad_gpo_get_sd_referral_conn_done(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error; + const char *attrs[] = AD_GPO_ATTRS; + + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ad_gpo_get_sd_referral_state *state = + tevent_req_data(req, struct ad_gpo_get_sd_referral_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, + "Backend is marked offline, retry later!\n"); + tevent_req_done(req); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cross-realm GPO processing failed to connect to " \ + "referred LDAP server: (%d)[%s]\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + } + return; + } + + /* Request the referred GPO data */ + subreq = sdap_sd_search_send(state, state->ev, state->opts, + sdap_id_op_handle(state->ref_op), + state->gpo_dn, + SECINFO_DACL, + attrs, + state->timeout); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_sd_search_send failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ad_gpo_get_sd_referral_search_done, req); + +} + +static void +ad_gpo_get_sd_referral_search_done(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error; + size_t num_results, num_refs; + struct sysdb_attrs **results = NULL; + char **refs; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ad_gpo_get_sd_referral_state *state = + tevent_req_data(req, struct ad_gpo_get_sd_referral_state); + + ret = sdap_sd_search_recv(subreq, NULL, + &num_results, &results, + &num_refs, &refs); + talloc_zfree(subreq); + if (ret != EOK) { + ret = sdap_id_op_done(state->ref_op, ret, &dp_error); + + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get GPO attributes: [%d](%s)\n", + ret, sss_strerror(ret)); + ret = ENOENT; + goto done; + + } + + if ((num_results < 1) || (results == NULL)) { + /* TODO: + * It's strictly possible for the referral search to return + * another referral value here, but it shouldn't actually + * happen with Active Directory. Properly handling (and + * limiting) the referral chain would be fairly complex, so + * we will do it later if it ever becomes necessary. + */ + DEBUG(SSSDBG_OP_FAILURE, + "No attrs found for referred GPO [%s].\n", state->gpo_dn); + ret = ENOENT; + goto done; + + } else if (num_results > 1) { + DEBUG(SSSDBG_OP_FAILURE, "Received multiple replies\n"); + ret = ERR_INTERNAL; + goto done; + } + + state->reply = talloc_steal(state, results[0]); + +done: + talloc_free(results); + + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +errno_t +ad_gpo_get_sd_referral_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **_smb_host, + struct sysdb_attrs **_reply) +{ + struct ad_gpo_get_sd_referral_state *state = + tevent_req_data(req, struct ad_gpo_get_sd_referral_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_smb_host = talloc_steal(mem_ctx, state->smb_host); + *_reply = talloc_steal(mem_ctx, state->reply); + + return EOK; +} diff --git a/src/providers/ad/ad_gpo.h b/src/providers/ad/ad_gpo.h new file mode 100644 index 0000000..8066511 --- /dev/null +++ b/src/providers/ad/ad_gpo.h @@ -0,0 +1,65 @@ +/* + SSSD + + Authors: + Yassir Elley + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef AD_GPO_H_ +#define AD_GPO_H_ + +#include "providers/ad/ad_access.h" + +#define AD_GPO_CHILD_OUT_FILENO 3 + +#define AD_GPO_ATTRS {AD_AT_NT_SEC_DESC, \ + AD_AT_CN, AD_AT_FILE_SYS_PATH, \ + AD_AT_MACHINE_EXT_NAMES, \ + AD_AT_FUNC_VERSION, \ + AD_AT_FLAGS, \ + NULL} + +/* + * This pair of functions provides client-side GPO processing. + * + * While a GPO can target both user and computer objects, this + * implementation only supports targeting of computer objects. + * + * A GPO overview is at https://fedorahosted.org/sssd/wiki/GpoOverview + * + * In summary, client-side processing involves: + * - determining the target's DN + * - extracting the SOM object DNs (i.e. OUs and Domain) from target's DN + * - including the target's Site as another SOM object + * - determining which GPOs apply to the target's SOMs + * - prioritizing GPOs based on SOM, link order, and whether GPO is "enforced" + * - retrieving the corresponding GPO objects + * - sending the GPO DNs to the CSE processing engine for policy application + * - policy application currently consists of HBAC-like functionality + */ +struct tevent_req * +ad_gpo_access_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *domain, + struct ad_access_ctx *ctx, + const char *user, + const char *service); + +errno_t ad_gpo_access_recv(struct tevent_req *req); + +#endif /* AD_GPO_H_ */ diff --git a/src/providers/ad/ad_gpo_child.c b/src/providers/ad/ad_gpo_child.c new file mode 100644 index 0000000..2f2807b --- /dev/null +++ b/src/providers/ad/ad_gpo_child.c @@ -0,0 +1,821 @@ +/* + SSSD + + AD GPO Backend Module -- perform SMB and CSE processing in a child process + + Authors: + Yassir Elley + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "util/child_common.h" +#include "util/sss_chain_id.h" +#include "providers/backend.h" +#include "providers/ad/ad_gpo.h" +#include "sss_cli.h" + +#define SMB_BUFFER_SIZE 65536 +#define GPT_INI "/GPT.INI" + +errno_t ad_gpo_parse_ini_file(const char *smb_path, int *_gpt_version); + +struct input_buffer { + int cached_gpt_version; + const char *smb_server; + const char *smb_share; + const char *smb_path; + const char *smb_cse_suffix; +}; + +static errno_t +unpack_buffer(uint8_t *buf, + size_t size, + struct input_buffer *ibuf) +{ + size_t p = 0; + uint32_t len; + uint32_t cached_gpt_version; + + /* cached_gpt_version */ + SAFEALIGN_COPY_UINT32_CHECK(&cached_gpt_version, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_FUNC, "cached_gpt_version: %d\n", cached_gpt_version); + ibuf->cached_gpt_version = cached_gpt_version; + + /* smb_server */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_ALL, "smb_server length: %d\n", len); + if (len == 0) { + return EINVAL; + } else { + if (len > size - p) return EINVAL; + ibuf->smb_server = talloc_strndup(ibuf, (char *)(buf + p), len); + if (ibuf->smb_server == NULL) return ENOMEM; + DEBUG(SSSDBG_TRACE_ALL, "smb_server: %s\n", ibuf->smb_server); + p += len; + } + + /* smb_share */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_ALL, "smb_share length: %d\n", len); + if (len == 0) { + return EINVAL; + } else { + if (len > size - p) return EINVAL; + ibuf->smb_share = talloc_strndup(ibuf, (char *)(buf + p), len); + if (ibuf->smb_share == NULL) return ENOMEM; + DEBUG(SSSDBG_TRACE_ALL, "smb_share: %s\n", ibuf->smb_share); + p += len; + } + + /* smb_path */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_ALL, "smb_path length: %d\n", len); + if (len == 0) { + return EINVAL; + } else { + if (len > size - p) return EINVAL; + ibuf->smb_path = talloc_strndup(ibuf, (char *)(buf + p), len); + if (ibuf->smb_path == NULL) return ENOMEM; + DEBUG(SSSDBG_TRACE_ALL, "smb_path: %s\n", ibuf->smb_path); + p += len; + } + + /* smb_cse_suffix */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_ALL, "smb_cse_suffix length: %d\n", len); + if (len == 0) { + return EINVAL; + } else { + if (len > size - p) return EINVAL; + ibuf->smb_cse_suffix = talloc_strndup(ibuf, (char *)(buf + p), len); + if (ibuf->smb_cse_suffix == NULL) return ENOMEM; + DEBUG(SSSDBG_TRACE_ALL, "smb_cse_suffix: %s\n", ibuf->smb_cse_suffix); + p += len; + } + + return EOK; +} + + +static errno_t +pack_buffer(struct response *r, + int sysvol_gpt_version, + int result) +{ + size_t p = 0; + + /* A buffer with the following structure must be created: + * uint32_t sysvol_gpt_version (required) + * uint32_t status of the request (required) + */ + r->size = 2 * sizeof(uint32_t); + + r->buf = talloc_array(r, uint8_t, r->size); + if(r->buf == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, "result [%d]\n", result); + + /* sysvol_gpt_version */ + SAFEALIGN_SET_UINT32(&r->buf[p], sysvol_gpt_version, &p); + + /* result */ + SAFEALIGN_SET_UINT32(&r->buf[p], result, &p); + + return EOK; +} + +static errno_t +prepare_response(TALLOC_CTX *mem_ctx, + int sysvol_gpt_version, + int result, + struct response **rsp) +{ + int ret; + struct response *r = NULL; + + r = talloc_zero(mem_ctx, struct response); + if (r == NULL) { + return ENOMEM; + } + + r->buf = NULL; + r->size = 0; + + ret = pack_buffer(r, sysvol_gpt_version, result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pack_buffer failed\n"); + return ret; + } + + *rsp = r; + DEBUG(SSSDBG_TRACE_ALL, "r->size: %zu\n", r->size); + return EOK; +} + +static void +sssd_krb_get_auth_data_fn(const char * pServer, + const char * pShare, + char * pWorkgroup, + int maxLenWorkgroup, + char * pUsername, + int maxLenUsername, + char * pPassword, + int maxLenPassword) +{ + /* since we are using kerberos for authentication, we simply return */ + return; +} + +/* + * This function prepares the gpo_cache by: + * - parsing the input_smb_path into its component directories + * - creating each component directory (if it doesn't already exist) + */ +static errno_t prepare_gpo_cache(TALLOC_CTX *mem_ctx, + const char *cache_dir, + const char *input_smb_path_with_suffix) +{ + char *current_dir; + char *ptr; + const char delim = '/'; + int num_dirs = 0; + int i; + char *first = NULL; + char *last = NULL; + char *smb_path_with_suffix = NULL; + errno_t ret; + mode_t old_umask; + + smb_path_with_suffix = talloc_strdup(mem_ctx, input_smb_path_with_suffix); + if (smb_path_with_suffix == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_ALL, "smb_path_with_suffix: %s\n", smb_path_with_suffix); + + current_dir = talloc_strdup(mem_ctx, cache_dir); + if (current_dir == NULL) { + return ENOMEM; + } + + ptr = smb_path_with_suffix + 1; + while ((ptr = strchr(ptr, delim))) { + ptr++; + num_dirs++; + } + + ptr = smb_path_with_suffix + 1; + + old_umask = umask(SSS_DFL_X_UMASK); + for (i = 0; i < num_dirs; i++) { + first = ptr; + last = strchr(first, delim); + if (last == NULL) { + ret = EINVAL; + goto done; + } + *last = '\0'; + last++; + + current_dir = talloc_asprintf(mem_ctx, "%s/%s", current_dir, first); + if (current_dir == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Storing GPOs in %s\n", current_dir); + + if ((mkdir(current_dir, 0700)) < 0 && errno != EEXIST) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "mkdir(%s) failed: %d\n", current_dir, ret); + goto done; + } + + ptr = last; + } + + ret = EOK; + +done: + umask(old_umask); + + return ret; +} + +/* + * This function stores the input buf to a local file, whose file path + * is constructed by concatenating: + * GPO_CACHE_PATH, + * input smb_path, + * input smb_cse_suffix + * Note that the backend will later read the file from the same file path. + */ +static errno_t gpo_cache_store_file(const char *smb_path, + const char *smb_cse_suffix, + uint8_t *buf, + int buflen) +{ + int ret; + int fret; + int fd = -1; + char *tmp_name = NULL; + ssize_t written; + char *filename = NULL; + char *smb_path_with_suffix = NULL; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + smb_path_with_suffix = + talloc_asprintf(tmp_ctx, "%s%s", smb_path, smb_cse_suffix); + if (smb_path_with_suffix == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + /* create component directories of smb_path, if needed */ + ret = prepare_gpo_cache(tmp_ctx, GPO_CACHE_PATH, smb_path_with_suffix); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "prepare_gpo_cache failed [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + filename = talloc_asprintf(tmp_ctx, GPO_CACHE_PATH"%s", smb_path_with_suffix); + if (filename == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + tmp_name = talloc_asprintf(tmp_ctx, "%sXXXXXX", filename); + if (tmp_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + fd = sss_unique_file(tmp_ctx, tmp_name, &ret); + if (fd == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_unique_file failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + errno = 0; + written = sss_atomic_write_s(fd, buf, buflen); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "write failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + if (written != buflen) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Write error, wrote [%zd] bytes, expected [%d]\n", + written, buflen); + ret = EIO; + goto done; + } + + ret = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fchmod failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + ret = rename(tmp_name, filename); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "rename failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + ret = EOK; + done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error encountered: %d.\n", ret); + } + + if (fd != -1) { + fret = close(fd); + if (fret == -1) { + fret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "close failed [%d][%s].\n", fret, strerror(fret)); + } + } + + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +gpo_cache_remove_file(const char *smb_path, + const char *smb_cse_suffix) +{ + errno_t ret = EOK; + char *filename = NULL; + + filename = talloc_asprintf(NULL, GPO_CACHE_PATH"%s%s", smb_path, + smb_cse_suffix); + if (filename == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = unlink(filename); + if (ret != 0) { + if (errno != ENOENT) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "failed to unlink %s [%d]: %s\n", + filename, ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(filename); + return ret; +} + +/* + * This function uses the input smb uri components to download a sysvol file + * (e.g. INI file, policy file, etc) and store it to the GPO_CACHE directory. + */ +static errno_t +copy_smb_file_to_gpo_cache(SMBCCTX *smbc_ctx, + const char *smb_server, + const char *smb_share, + const char *smb_path, + const char *smb_cse_suffix, + bool optional) +{ + char *smb_uri = NULL; + char *gpt_main_folder = NULL; + SMBCFILE *file = NULL; + int ret; + uint8_t *buf = NULL; + int buflen = 0; + + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + smb_uri = talloc_asprintf(tmp_ctx, "%s%s%s%s", smb_server, + smb_share, smb_path, smb_cse_suffix); + if (smb_uri == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "smb_uri: %s\n", smb_uri); + + errno = 0; + file = smbc_getFunctionOpen(smbc_ctx)(smbc_ctx, smb_uri, O_RDONLY, 0755); + if (file == NULL) { + // ENOENT: A directory component in pathname does not exist + if (errno == ENOENT) { + /* + * DCs may use upper case names for the main folder, where GPTs are + * stored. libsmbclient does not allow us to request case insensitive + * file name lookups on DCs with case sensitive file systems. + */ + gpt_main_folder = strstr(smb_uri, "/Machine/"); + if (gpt_main_folder == NULL) { + /* At this moment we do not use any GPO from user settings, + * but it can change in the future so let's keep the following + * line around to make this part of the code 'just work' also + * with the user GPO settings. */ + gpt_main_folder = strstr(smb_uri, "/User/"); + } + if (gpt_main_folder != NULL) { + ++gpt_main_folder; + while (gpt_main_folder != NULL && *gpt_main_folder != '/') { + *gpt_main_folder = toupper(*gpt_main_folder); + ++gpt_main_folder; + } + + DEBUG(SSSDBG_TRACE_FUNC, "smb_uri: %s\n", smb_uri); + + errno = 0; + file = smbc_getFunctionOpen(smbc_ctx)(smbc_ctx, smb_uri, O_RDONLY, 0755); + } + } + + if (file == NULL) { + ret = errno; + if (optional && ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, + "%s does not exist in sysvol, purging cached copy\n", + smb_uri); + /* It looks like Windows clients treat missing GPO files as + * empty. To make sure we do not use old and now invalid + * content an potentially exising old file will be removed. */ + ret = gpo_cache_remove_file(smb_path, smb_cse_suffix); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "failed to purge stale cached %s\n", smb_uri); + goto done; + } + ret = EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "smbc_getFunctionOpen failed [%d][%s]\n", + ret, strerror(ret)); + } + goto done; + } + } + + buf = talloc_array(tmp_ctx, uint8_t, SMB_BUFFER_SIZE); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + + errno = 0; + buflen = smbc_getFunctionRead(smbc_ctx)(smbc_ctx, file, buf, SMB_BUFFER_SIZE); + if (buflen < 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "smbc_getFunctionRead failed [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "smb_buflen: %d\n", buflen); + + ret = gpo_cache_store_file(smb_path, smb_cse_suffix, buf, buflen); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "gpo_cache_store_file failed [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + done: + if (file != NULL) { + smbc_getFunctionClose(smbc_ctx)(smbc_ctx, file); + } + + talloc_free(tmp_ctx); + return ret; +} + + +/* + * Using its smb_uri components and cached_gpt_version inputs, this function + * does several things: + * - it downloads the GPT_INI file to GPO_CACHE + * - it parses the sysvol_gpt_version field from the GPT_INI file + * - if the sysvol_gpt_version is greater than the cached_gpt_version + * - it downloads the policy file to GPO_CACHE + * - else + * - it doesn't retrieve the policy file + * - in this case, the backend will use the existing policy file in GPO_CACHE + * - it returns the sysvol_gpt_version in the _sysvol_gpt_version output param + * + * Note that if the cached_gpt_version sent by the backend is -1 (to indicate + * that no gpt_version has been set in the cache for the corresponding gpo_guid), + * then the parsed sysvol_gpt_version (which must be at least 0) will be greater + * than the cached_gpt_version, thereby triggering a fresh download. + * + * Note that the backend will later do the following: + * - backend will save the sysvol_gpt_version to sysdb cache + * - backend will read the policy file from the GPO_CACHE + */ +static errno_t +perform_smb_operations(int cached_gpt_version, + const char *smb_server, + const char *smb_share, + const char *smb_path, + const char *smb_cse_suffix, + int *_sysvol_gpt_version) +{ + SMBCCTX *smbc_ctx; + int ret; + int sysvol_gpt_version = -1; + char *ini_filename = NULL; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + smbc_ctx = smbc_new_context(); + if (smbc_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not allocate new smbc context\n"); + ret = ENOMEM; + goto done; + } + + smbc_setOptionDebugToStderr(smbc_ctx, 1); + smbc_setFunctionAuthData(smbc_ctx, sssd_krb_get_auth_data_fn); + smbc_setOptionUseKerberos(smbc_ctx, 1); + + /* Initialize the context using the previously specified options */ + if (smbc_init_context(smbc_ctx) == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not initialize smbc context\n"); + ret = ENOMEM; + goto done; + } + + /* download ini file */ + ret = copy_smb_file_to_gpo_cache(smbc_ctx, smb_server, smb_share, smb_path, + GPT_INI, false); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "copy_smb_file_to_gpo_cache failed [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + ini_filename = talloc_asprintf(tmp_ctx, GPO_CACHE_PATH"%s"GPT_INI, smb_path); + if (ini_filename == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = ad_gpo_parse_ini_file(ini_filename, &sysvol_gpt_version); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse ini file: [%d][%s]\n", ret, strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "sysvol_gpt_version: %d\n", sysvol_gpt_version); + + if (sysvol_gpt_version > cached_gpt_version) { + /* download policy file */ + ret = copy_smb_file_to_gpo_cache(smbc_ctx, smb_server, smb_share, + smb_path, smb_cse_suffix, true); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "copy_smb_file_to_gpo_cache failed [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + ret = EOK; + } + + *_sysvol_gpt_version = sysvol_gpt_version; + + done: + talloc_free(tmp_ctx); + smbc_free_context(smbc_ctx, 0); + return ret; +} + +int +main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + int dumpable = 1; + int debug_fd = -1; + long chain_id = 0; + const char *opt_logger = NULL; + errno_t ret; + int sysvol_gpt_version = -1; + int result; + TALLOC_CTX *main_ctx = NULL; + uint8_t *buf = NULL; + ssize_t len = 0; + struct input_buffer *ibuf = NULL; + struct response *resp = NULL; + ssize_t written; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, + _("Allow core dumps"), NULL }, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + {"chain-id", 0, POPT_ARG_LONG, &chain_id, + 0, _("Tevent chain ID used for logging purposes"), NULL}, + SSSD_LOGGER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + } + + poptFreeContext(pc); + + prctl(PR_SET_DUMPABLE, (dumpable == 0) ? 0 : 1); + + debug_prg_name = talloc_asprintf(NULL, "gpo_child[%d]", getpid()); + if (debug_prg_name == NULL) { + ERROR("talloc_asprintf failed.\n"); + goto fail; + } + + if (debug_fd != -1) { + opt_logger = sss_logger_str[FILES_LOGGER]; + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + opt_logger = sss_logger_str[STDERR_LOGGER]; + ERROR("set_debug_file_from_fd failed.\n"); + } + } + + sss_chain_id_set_format(DEBUG_CHAIN_ID_FMT_RID); + sss_chain_id_set((uint64_t)chain_id); + + DEBUG_INIT(debug_level, opt_logger); + + DEBUG(SSSDBG_TRACE_FUNC, "gpo_child started.\n"); + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + talloc_free(discard_const(debug_prg_name)); + goto fail; + } + talloc_steal(main_ctx, debug_prg_name); + + buf = talloc_size(main_ctx, sizeof(uint8_t)*IN_BUF_SIZE); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + goto fail; + } + + ibuf = talloc_zero(main_ctx, struct input_buffer); + if (ibuf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "context initialized\n"); + + errno = 0; + len = sss_atomic_read_s(STDIN_FILENO, buf, IN_BUF_SIZE); + if (len == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "read failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + close(STDIN_FILENO); + + ret = unpack_buffer(buf, len, ibuf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "unpack_buffer failed.[%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "performing smb operations\n"); + + result = perform_smb_operations(ibuf->cached_gpt_version, + ibuf->smb_server, + ibuf->smb_share, + ibuf->smb_path, + ibuf->smb_cse_suffix, + &sysvol_gpt_version); + if (result != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "perform_smb_operations failed.[%d][%s].\n", + result, strerror(result)); + goto fail; + } + + if (sysvol_gpt_version < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "get sysvol_gpt_version failed. [%d].\n", + sysvol_gpt_version); + goto fail; + } + + ret = prepare_response(main_ctx, sysvol_gpt_version, result, &resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "prepare_response failed. [%d][%s].\n", + ret, strerror(ret)); + goto fail; + } + + errno = 0; + + written = sss_atomic_write_s(AD_GPO_CHILD_OUT_FILENO, resp->buf, resp->size); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "write failed [%d][%s].\n", ret, + strerror(ret)); + goto fail; + } + + if (written != resp->size) { + DEBUG(SSSDBG_CRIT_FAILURE, "Expected to write %zu bytes, wrote %zu\n", + resp->size, written); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "gpo_child completed successfully\n"); + close(AD_GPO_CHILD_OUT_FILENO); + talloc_free(main_ctx); + return EXIT_SUCCESS; + +fail: + DEBUG(SSSDBG_CRIT_FAILURE, "gpo_child failed!\n"); + close(AD_GPO_CHILD_OUT_FILENO); + talloc_free(main_ctx); + return EXIT_FAILURE; +} diff --git a/src/providers/ad/ad_gpo_child_utils.c b/src/providers/ad/ad_gpo_child_utils.c new file mode 100644 index 0000000..676a2b3 --- /dev/null +++ b/src/providers/ad/ad_gpo_child_utils.c @@ -0,0 +1,242 @@ +/* + SSSD + + AD GPO Backend Module -- helpers for a child process + + Copyright (C) 2022 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util/util_errors.h" +#include "util/debug.h" +#include "util/atomic_io.h" + +#define INI_GENERAL_SECTION "General" +#define GPT_INI_VERSION "Version" + +static errno_t parse_ini_file_with_libini(struct ini_cfgobj *ini_config, + int *_gpt_version) +{ + int ret; + struct value_obj *vobj = NULL; + int gpt_version; + + ret = ini_get_config_valueobj(INI_GENERAL_SECTION, GPT_INI_VERSION, + ini_config, INI_GET_FIRST_VALUE, &vobj); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ini_get_config_valueobj failed [%d][%s]\n", ret, strerror(ret)); + goto done; + } + if (vobj == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "section/name not found: [%s][%s]\n", + INI_GENERAL_SECTION, GPT_INI_VERSION); + ret = EINVAL; + goto done; + } + + gpt_version = ini_get_int32_config_value(vobj, 0, -1, &ret); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ini_get_int32_config_value failed [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + *_gpt_version = gpt_version; + + ret = EOK; + + done: + + return ret; +} + +static errno_t gpo_sanitize_buffer_content(uint8_t *buf, int buflen) +{ + int i; + int line_start = 0; + int equal_pos = 0; + + if (!buf) { + return EINVAL; + } + + for (i = 0; i < buflen; ++i) { + if (buf[i] == '\n') { + line_start = i + 1; + continue; + } + if (buf[i] == '=') { + equal_pos = i; + continue; + } + if (isascii(buf[i])) { + continue; + } + + /* non-ascii */ + if (equal_pos <= line_start) { /* key */ + DEBUG(SSSDBG_OP_FAILURE, + "Key or section starting at position %d ('%.*s...') contains" + " non-ascii symbol. File is unusable!\n", + line_start, i - line_start, buf + line_start); + return EINVAL; + } + + buf[i] = '?'; + DEBUG(SSSDBG_IMPORTANT_INFO, + "Value for key '%.*s' contains non-ascii symbol." + " Replacing with '?'\n", + equal_pos - line_start, buf + line_start); + } + + return EOK; +} + +/* + * This function parses the GPT_INI file stored in the gpo_cache, and uses the + * results to populate the output parameters ... + */ +errno_t ad_gpo_parse_ini_file(const char *ini_filename, int *_gpt_version) +{ + struct ini_cfgfile *file_ctx = NULL; + struct ini_cfgobj *ini_config = NULL; + int ret; + int gpt_version = -1; + TALLOC_CTX *tmp_ctx = NULL; + struct stat st; + int fd = -1; + uint8_t *buf = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "ini_filename:%s\n", ini_filename); + + ret = ini_config_create(&ini_config); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ini_config_create failed [%d][%s]\n", ret, strerror(ret)); + goto done; + } + + fd = open(ini_filename, O_RDONLY); + if (fd == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "open() failed [%d][%s]\n", ret, strerror(ret)); + ret = EIO; + goto done; + } + ret = fstat(fd, &st); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "stat() failed [%d][%s]\n", ret, strerror(ret)); + ret = EIO; + goto done; + } + buf = talloc_size(tmp_ctx, st.st_size); + if (buf == NULL) { + ret = ENOMEM; + goto done; + } + if (sss_atomic_read_s(fd, buf, st.st_size) != st.st_size) { + ret = EIO; + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_atomic_read_s() failed\n"); + goto done; + } + + /* Windows uses ANSI (extended-ASCII) to encode the GPT.INI file. + * Practically this might mean any code page, including uncompatible + * with UTF. Since the only value read by SSSD from GPT.INI is + * 'Version=...', just get rid of any non-ascii characters to make + * content compatible with lib_iniconfig. + */ + ret = gpo_sanitize_buffer_content(buf, st.st_size); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "gpo_sanitize_buffer_content() failed\n"); + goto done; + } + + ret = ini_config_file_from_mem(buf, st.st_size, &file_ctx); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ini_config_file_from_mem() failed [%d][%s]\n", ret, strerror(ret)); + goto done; + } + + ret = ini_config_parse(file_ctx, INI_STOP_ON_NONE, 0, 0, ini_config); + if (ret != 0) { + int lret; + char **errors; + + DEBUG(SSSDBG_CRIT_FAILURE, + "[%s]: ini_config_parse failed [%d][%s]\n", + ini_filename, ret, strerror(ret)); + + /* Now get specific errors if there are any */ + lret = ini_config_get_errors(ini_config, &errors); + if (lret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get specific parse error [%d][%s]\n", lret, + strerror(lret)); + goto done; + } + + for (int i = 0; errors[i]; i++) { + DEBUG(SSSDBG_CRIT_FAILURE, "%s\n", errors[i]); + } + ini_config_free_errors(errors); + + goto done; + } + + ret = parse_ini_file_with_libini(ini_config, &gpt_version); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "parse_ini_file_with_libini failed [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + *_gpt_version = gpt_version; + + done: + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error encountered: %d.\n", ret); + } + + ini_config_file_destroy(file_ctx); + ini_config_destroy(ini_config); + if (fd != -1) close(fd); + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ad/ad_gpo_ndr.c b/src/providers/ad/ad_gpo_ndr.c new file mode 100644 index 0000000..0d4c671 --- /dev/null +++ b/src/providers/ad/ad_gpo_ndr.c @@ -0,0 +1,526 @@ +/* + SSSD + + ad_gpo_ndr.c + + Authors: + Yassir Elley + + Copyright (C) 2014 Red Hat + + 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 3 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; 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, see . +*/ + +/* + * This file contains a copy of samba's ndr_pull_* functions needed + * to parse a security_descriptor. We are copying them here so that we don't + * have to link against libsamba-security, which is a private samba library + * These functions are taken from: + * librpc/ndr/gen_ndr/ndr_security.c + * librpc/ndr/ndr_misc.c + * librpc/ndr/ndr_sec_helper.c + */ + +#include "util/util.h" +#include +#include + +static enum ndr_err_code +ndr_pull_GUID(struct ndr_pull *ndr, + int ndr_flags, + struct GUID *r) +{ + uint32_t size_clock_seq_0 = 0; + uint32_t size_node_0 = 0; + NDR_PULL_CHECK_FLAGS(ndr, ndr_flags); + if (ndr_flags & NDR_SCALARS) { + NDR_CHECK(ndr_pull_align(ndr, 4)); + NDR_CHECK(ndr_pull_uint32(ndr, NDR_SCALARS, &r->time_low)); + NDR_CHECK(ndr_pull_uint16(ndr, NDR_SCALARS, &r->time_mid)); + NDR_CHECK(ndr_pull_uint16(ndr, NDR_SCALARS, &r->time_hi_and_version)); + size_clock_seq_0 = 2; + NDR_CHECK(ndr_pull_array_uint8(ndr, + NDR_SCALARS, + r->clock_seq, + size_clock_seq_0)); + size_node_0 = 6; + NDR_CHECK(ndr_pull_array_uint8(ndr, NDR_SCALARS, r->node, size_node_0)); + NDR_CHECK(ndr_pull_trailer_align(ndr, 4)); + } + + return NDR_ERR_SUCCESS; +} + +static enum ndr_err_code +ndr_pull_security_ace_flags(struct ndr_pull *ndr, + int ndr_flags, + uint8_t *r) +{ + uint8_t v; + NDR_CHECK(ndr_pull_uint8(ndr, ndr_flags, &v)); + *r = v; + return NDR_ERR_SUCCESS; +} + + +static enum ndr_err_code +ndr_pull_security_ace_type(struct ndr_pull *ndr, + int ndr_flags, + enum security_ace_type *r) +{ + uint8_t v; + NDR_CHECK(ndr_pull_enum_uint8(ndr, ndr_flags, &v)); + *r = v; + return NDR_ERR_SUCCESS; +} + + +static enum ndr_err_code +ndr_pull_security_ace_object_flags(struct ndr_pull *ndr, + int ndr_flags, + uint32_t *r) +{ + uint32_t v; + NDR_CHECK(ndr_pull_uint32(ndr, ndr_flags, &v)); + *r = v; + return NDR_ERR_SUCCESS; +} + + +static enum ndr_err_code +ndr_pull_security_ace_object_type(struct ndr_pull *ndr, + int ndr_flags, + union security_ace_object_type *r) +{ + uint32_t level; + NDR_PULL_CHECK_FLAGS(ndr, ndr_flags); + if (ndr_flags & NDR_SCALARS) { + /* This token is not used again (except perhaps below in the NDR_BUFFERS case) */ +#ifdef SMB_HAS_NEW_NDR_PULL_STEAL_SWITCH + NDR_CHECK(ndr_pull_steal_switch_value(ndr, r, &level)); +#else + level = ndr_pull_steal_switch_value(ndr, r); +#endif + NDR_CHECK(ndr_pull_union_align(ndr, 4)); + switch (level) { + case SEC_ACE_OBJECT_TYPE_PRESENT: { + NDR_CHECK(ndr_pull_GUID(ndr, NDR_SCALARS, &r->type)); + break; } + default: { + break; } + } + } + return NDR_ERR_SUCCESS; +} + + +static enum ndr_err_code +ndr_pull_security_ace_object_inherited_type(struct ndr_pull *ndr, + int ndr_flags, + union security_ace_object_inherited_type *r) +{ + uint32_t level; + NDR_PULL_CHECK_FLAGS(ndr, ndr_flags); + if (ndr_flags & NDR_SCALARS) { + /* This token is not used again (except perhaps below in the NDR_BUFFERS case) */ +#ifdef SMB_HAS_NEW_NDR_PULL_STEAL_SWITCH + NDR_CHECK(ndr_pull_steal_switch_value(ndr, r, &level)); +#else + level = ndr_pull_steal_switch_value(ndr, r); +#endif + NDR_CHECK(ndr_pull_union_align(ndr, 4)); + switch (level) { + case SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT: { + NDR_CHECK(ndr_pull_GUID(ndr, + NDR_SCALARS, + &r->inherited_type)); + break; } + default: { + break; } + } + } + return NDR_ERR_SUCCESS; +} + +static enum ndr_err_code +ndr_pull_security_ace_object(struct ndr_pull *ndr, + int ndr_flags, + struct security_ace_object *r) +{ + NDR_PULL_CHECK_FLAGS(ndr, ndr_flags); + if (ndr_flags & NDR_SCALARS) { + NDR_CHECK(ndr_pull_align(ndr, 4)); + NDR_CHECK(ndr_pull_security_ace_object_flags + (ndr, NDR_SCALARS, &r->flags)); + NDR_CHECK(ndr_pull_set_switch_value + (ndr, &r->type, r->flags & SEC_ACE_OBJECT_TYPE_PRESENT)); + NDR_CHECK(ndr_pull_security_ace_object_type + (ndr, NDR_SCALARS, &r->type)); + NDR_CHECK(ndr_pull_set_switch_value + (ndr, + &r->inherited_type, + r->flags & SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT)); + NDR_CHECK(ndr_pull_security_ace_object_inherited_type + (ndr, NDR_SCALARS, &r->inherited_type)); + NDR_CHECK(ndr_pull_trailer_align(ndr, 4)); + } + if (ndr_flags & NDR_BUFFERS) { + NDR_CHECK(ndr_pull_set_switch_value + (ndr, + &r->type, + r->flags & SEC_ACE_OBJECT_TYPE_PRESENT)); + NDR_CHECK(ndr_pull_security_ace_object_type + (ndr, NDR_BUFFERS, &r->type)); + NDR_CHECK(ndr_pull_set_switch_value + (ndr, + &r->inherited_type, + r->flags & SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT)); + NDR_CHECK(ndr_pull_security_ace_object_inherited_type + (ndr, NDR_BUFFERS, &r->inherited_type)); + } + return NDR_ERR_SUCCESS; +} + + +static enum ndr_err_code +ndr_pull_security_ace_object_ctr(struct ndr_pull *ndr, + int ndr_flags, + union security_ace_object_ctr *r) +{ + uint32_t level = 0; + NDR_PULL_CHECK_FLAGS(ndr, ndr_flags); + if (ndr_flags & NDR_SCALARS) { + /* This token is not used again (except perhaps below in the NDR_BUFFERS case) */ +#ifdef SMB_HAS_NEW_NDR_PULL_STEAL_SWITCH + NDR_CHECK(ndr_pull_steal_switch_value(ndr, r, &level)); +#else + level = ndr_pull_steal_switch_value(ndr, r); +#endif + NDR_CHECK(ndr_pull_union_align(ndr, 4)); + switch (level) { + case SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT: { + NDR_CHECK(ndr_pull_security_ace_object + (ndr, NDR_SCALARS, &r->object)); + break; } + case SEC_ACE_TYPE_ACCESS_DENIED_OBJECT: { + NDR_CHECK(ndr_pull_security_ace_object + (ndr, NDR_SCALARS, &r->object)); + break; } + case SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT: { + NDR_CHECK(ndr_pull_security_ace_object + (ndr, NDR_SCALARS, &r->object)); + break; } + case SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT: { + NDR_CHECK(ndr_pull_security_ace_object + (ndr, NDR_SCALARS, &r->object)); + break; } + default: { + break; } + } + } + if (ndr_flags & NDR_BUFFERS) { + if (!(ndr_flags & NDR_SCALARS)) { + /* We didn't get it above, and the token is not needed after this. */ +#ifdef SMB_HAS_NEW_NDR_PULL_STEAL_SWITCH + NDR_CHECK(ndr_pull_steal_switch_value(ndr, r, &level)); +#else + level = ndr_pull_steal_switch_value(ndr, r); +#endif + } + switch (level) { + case SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT: + NDR_CHECK(ndr_pull_security_ace_object + (ndr, NDR_BUFFERS, &r->object)); + break; + case SEC_ACE_TYPE_ACCESS_DENIED_OBJECT: + NDR_CHECK(ndr_pull_security_ace_object + (ndr, NDR_BUFFERS, &r->object)); + break; + case SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT: + NDR_CHECK(ndr_pull_security_ace_object + (ndr, NDR_BUFFERS, &r->object)); + break; + case SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT: + NDR_CHECK(ndr_pull_security_ace_object + (ndr, NDR_BUFFERS, &r->object)); + break; + default: + break; + } + } + return NDR_ERR_SUCCESS; +} + +enum ndr_err_code +ndr_pull_dom_sid(struct ndr_pull *ndr, + int ndr_flags, + struct dom_sid *r) +{ + uint32_t cntr_sub_auths_0; + if (ndr_flags & NDR_SCALARS) { + NDR_CHECK(ndr_pull_align(ndr, 4)); + NDR_CHECK(ndr_pull_uint8(ndr, NDR_SCALARS, &r->sid_rev_num)); + NDR_CHECK(ndr_pull_int8(ndr, NDR_SCALARS, &r->num_auths)); + if (r->num_auths < 0 || r->num_auths > N_ELEMENTS(r->sub_auths)) { + return ndr_pull_error(ndr, NDR_ERR_RANGE, "value out of range"); + } + NDR_CHECK(ndr_pull_array_uint8(ndr, NDR_SCALARS, r->id_auth, 6)); + memset(&r->sub_auths, 0, sizeof(r->sub_auths)); + for (cntr_sub_auths_0 = 0; + cntr_sub_auths_0 < r->num_auths; + cntr_sub_auths_0++) { + NDR_CHECK(ndr_pull_uint32 + (ndr, NDR_SCALARS, &r->sub_auths[cntr_sub_auths_0])); + } + } + return NDR_ERR_SUCCESS; +} + +static enum ndr_err_code +ndr_pull_security_ace(struct ndr_pull *ndr, + int ndr_flags, + struct security_ace *r) +{ + if (ndr_flags & NDR_SCALARS) { + uint32_t start_ofs = ndr->offset; + uint32_t size = 0; + uint32_t pad = 0; + NDR_CHECK(ndr_pull_align(ndr, 4)); + NDR_CHECK(ndr_pull_security_ace_type(ndr, NDR_SCALARS, &r->type)); + NDR_CHECK(ndr_pull_security_ace_flags(ndr, NDR_SCALARS, &r->flags)); + NDR_CHECK(ndr_pull_uint16(ndr, NDR_SCALARS, &r->size)); + NDR_CHECK(ndr_pull_uint32(ndr, NDR_SCALARS, &r->access_mask)); + NDR_CHECK(ndr_pull_set_switch_value(ndr, &r->object, r->type)); + NDR_CHECK(ndr_pull_security_ace_object_ctr + (ndr, NDR_SCALARS, &r->object)); + NDR_CHECK(ndr_pull_dom_sid(ndr, NDR_SCALARS, &r->trustee)); + size = ndr->offset - start_ofs; + if (r->size < size) { + return ndr_pull_error(ndr, NDR_ERR_BUFSIZE, + "ndr_pull_security_ace: r->size %u < size %u", + (unsigned)r->size, size); + } + pad = r->size - size; + NDR_PULL_NEED_BYTES(ndr, pad); + ndr->offset += pad; + } + if (ndr_flags & NDR_BUFFERS) { + NDR_CHECK(ndr_pull_set_switch_value(ndr, &r->object, r->type)); + NDR_CHECK(ndr_pull_security_ace_object_ctr + (ndr, NDR_BUFFERS, &r->object)); + } + return NDR_ERR_SUCCESS; +} + +static enum ndr_err_code +ndr_pull_security_acl_revision(struct ndr_pull *ndr, + int ndr_flags, + enum security_acl_revision *r) +{ + uint16_t v; + NDR_CHECK(ndr_pull_enum_uint1632(ndr, ndr_flags, &v)); + *r = v; + return NDR_ERR_SUCCESS; +} + + +static enum ndr_err_code +ndr_pull_security_acl(struct ndr_pull *ndr, + int ndr_flags, + struct security_acl *r) +{ + uint32_t size_aces_0 = 0; + uint32_t cntr_aces_0; + TALLOC_CTX *_mem_save_aces_0; + NDR_PULL_CHECK_FLAGS(ndr, ndr_flags); + if (ndr_flags & NDR_SCALARS) { + NDR_CHECK(ndr_pull_align(ndr, 4)); + NDR_CHECK(ndr_pull_security_acl_revision + (ndr, NDR_SCALARS, &r->revision)); + NDR_CHECK(ndr_pull_uint16(ndr, NDR_SCALARS, &r->size)); + NDR_CHECK(ndr_pull_uint32(ndr, NDR_SCALARS, &r->num_aces)); + if (r->num_aces > 2000) { + return ndr_pull_error(ndr, NDR_ERR_RANGE, "value out of range"); + } + size_aces_0 = r->num_aces; + NDR_PULL_ALLOC_N(ndr, r->aces, size_aces_0); + _mem_save_aces_0 = NDR_PULL_GET_MEM_CTX(ndr); + NDR_PULL_SET_MEM_CTX(ndr, r->aces, 0); + for (cntr_aces_0 = 0; cntr_aces_0 < size_aces_0; cntr_aces_0++) { + NDR_CHECK(ndr_pull_security_ace + (ndr, NDR_SCALARS, &r->aces[cntr_aces_0])); + } + NDR_PULL_SET_MEM_CTX(ndr, _mem_save_aces_0, 0); + NDR_CHECK(ndr_pull_trailer_align(ndr, 4)); + } + if (ndr_flags & NDR_BUFFERS) { + size_aces_0 = r->num_aces; + _mem_save_aces_0 = NDR_PULL_GET_MEM_CTX(ndr); + NDR_PULL_SET_MEM_CTX(ndr, r->aces, 0); + for (cntr_aces_0 = 0; cntr_aces_0 < size_aces_0; cntr_aces_0++) { + NDR_CHECK(ndr_pull_security_ace + (ndr, NDR_BUFFERS, &r->aces[cntr_aces_0])); + } + NDR_PULL_SET_MEM_CTX(ndr, _mem_save_aces_0, 0); + } + return NDR_ERR_SUCCESS; +} + + +static enum ndr_err_code +ndr_pull_security_descriptor_revision(struct ndr_pull *ndr, + int ndr_flags, + enum security_descriptor_revision *r) +{ + uint8_t v; + NDR_CHECK(ndr_pull_enum_uint8(ndr, ndr_flags, &v)); + *r = v; + return NDR_ERR_SUCCESS; +} + + + +static enum ndr_err_code +ndr_pull_security_descriptor_type(struct ndr_pull *ndr, + int ndr_flags, + uint16_t *r) +{ + uint16_t v; + NDR_CHECK(ndr_pull_uint16(ndr, ndr_flags, &v)); + *r = v; + return NDR_ERR_SUCCESS; +} + + +enum ndr_err_code +ad_gpo_ndr_pull_security_descriptor(struct ndr_pull *ndr, + int ndr_flags, + struct security_descriptor *r) +{ + uint32_t _ptr_owner_sid; + TALLOC_CTX *_mem_save_owner_sid_0; + uint32_t _ptr_group_sid; + TALLOC_CTX *_mem_save_group_sid_0; + uint32_t _ptr_sacl; + TALLOC_CTX *_mem_save_sacl_0; + uint32_t _ptr_dacl; + TALLOC_CTX *_mem_save_dacl_0; + { + uint32_t _flags_save_STRUCT = ndr->flags; + ndr_set_flags(&ndr->flags, LIBNDR_FLAG_LITTLE_ENDIAN); + NDR_PULL_CHECK_FLAGS(ndr, ndr_flags); + if (ndr_flags & NDR_SCALARS) { + NDR_CHECK(ndr_pull_align(ndr, 5)); + NDR_CHECK(ndr_pull_security_descriptor_revision(ndr, + NDR_SCALARS, + &r->revision)); + NDR_CHECK(ndr_pull_security_descriptor_type(ndr, + NDR_SCALARS, + &r->type)); + NDR_CHECK(ndr_pull_generic_ptr(ndr, &_ptr_owner_sid)); + if (_ptr_owner_sid) { + NDR_PULL_ALLOC(ndr, r->owner_sid); + NDR_CHECK(ndr_pull_relative_ptr1(ndr, + r->owner_sid, + _ptr_owner_sid)); + } else { + r->owner_sid = NULL; + } + NDR_CHECK(ndr_pull_generic_ptr(ndr, &_ptr_group_sid)); + if (_ptr_group_sid) { + NDR_PULL_ALLOC(ndr, r->group_sid); + NDR_CHECK(ndr_pull_relative_ptr1(ndr, + r->group_sid, + _ptr_group_sid)); + } else { + r->group_sid = NULL; + } + NDR_CHECK(ndr_pull_generic_ptr(ndr, &_ptr_sacl)); + if (_ptr_sacl) { + NDR_PULL_ALLOC(ndr, r->sacl); + NDR_CHECK(ndr_pull_relative_ptr1(ndr, r->sacl, _ptr_sacl)); + } else { + r->sacl = NULL; + } + NDR_CHECK(ndr_pull_generic_ptr(ndr, &_ptr_dacl)); + if (_ptr_dacl) { + NDR_PULL_ALLOC(ndr, r->dacl); + NDR_CHECK(ndr_pull_relative_ptr1(ndr, r->dacl, _ptr_dacl)); + } else { + r->dacl = NULL; + } + NDR_CHECK(ndr_pull_trailer_align(ndr, 5)); + } + if (ndr_flags & NDR_BUFFERS) { + if (r->owner_sid) { + uint32_t _relative_save_offset; + _relative_save_offset = ndr->offset; + NDR_CHECK(ndr_pull_relative_ptr2(ndr, r->owner_sid)); + _mem_save_owner_sid_0 = NDR_PULL_GET_MEM_CTX(ndr); + NDR_PULL_SET_MEM_CTX(ndr, r->owner_sid, 0); + NDR_CHECK(ndr_pull_dom_sid(ndr, NDR_SCALARS, r->owner_sid)); + NDR_PULL_SET_MEM_CTX(ndr, _mem_save_owner_sid_0, 0); + if (ndr->offset > ndr->relative_highest_offset) { + ndr->relative_highest_offset = ndr->offset; + } + ndr->offset = _relative_save_offset; + } + if (r->group_sid) { + uint32_t _relative_save_offset; + _relative_save_offset = ndr->offset; + NDR_CHECK(ndr_pull_relative_ptr2(ndr, r->group_sid)); + _mem_save_group_sid_0 = NDR_PULL_GET_MEM_CTX(ndr); + NDR_PULL_SET_MEM_CTX(ndr, r->group_sid, 0); + NDR_CHECK(ndr_pull_dom_sid(ndr, NDR_SCALARS, r->group_sid)); + NDR_PULL_SET_MEM_CTX(ndr, _mem_save_group_sid_0, 0); + if (ndr->offset > ndr->relative_highest_offset) { + ndr->relative_highest_offset = ndr->offset; + } + ndr->offset = _relative_save_offset; + } + if (r->sacl) { + uint32_t _relative_save_offset; + _relative_save_offset = ndr->offset; + NDR_CHECK(ndr_pull_relative_ptr2(ndr, r->sacl)); + _mem_save_sacl_0 = NDR_PULL_GET_MEM_CTX(ndr); + NDR_PULL_SET_MEM_CTX(ndr, r->sacl, 0); + NDR_CHECK(ndr_pull_security_acl(ndr, + NDR_SCALARS|NDR_BUFFERS, + r->sacl)); + NDR_PULL_SET_MEM_CTX(ndr, _mem_save_sacl_0, 0); + if (ndr->offset > ndr->relative_highest_offset) { + ndr->relative_highest_offset = ndr->offset; + } + ndr->offset = _relative_save_offset; + } + if (r->dacl) { + uint32_t _relative_save_offset; + _relative_save_offset = ndr->offset; + NDR_CHECK(ndr_pull_relative_ptr2(ndr, r->dacl)); + _mem_save_dacl_0 = NDR_PULL_GET_MEM_CTX(ndr); + NDR_PULL_SET_MEM_CTX(ndr, r->dacl, 0); + NDR_CHECK(ndr_pull_security_acl(ndr, + NDR_SCALARS|NDR_BUFFERS, + r->dacl)); + NDR_PULL_SET_MEM_CTX(ndr, _mem_save_dacl_0, 0); + if (ndr->offset > ndr->relative_highest_offset) { + ndr->relative_highest_offset = ndr->offset; + } + ndr->offset = _relative_save_offset; + } + } + ndr->flags = _flags_save_STRUCT; + } + return NDR_ERR_SUCCESS; +} diff --git a/src/providers/ad/ad_id.c b/src/providers/ad/ad_id.c new file mode 100644 index 0000000..09280f7 --- /dev/null +++ b/src/providers/ad/ad_id.c @@ -0,0 +1,1547 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ +#include "util/util.h" +#include "util/strtonum.h" +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_id.h" +#include "providers/ad/ad_domain_info.h" +#include "providers/ad/ad_pac.h" +#include "providers/ldap/sdap_async_enum.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_async.h" + +static bool ad_account_can_shortcut(struct sdap_idmap_ctx *idmap_ctx, + struct sss_domain_info *domain, + int filter_type, + const char *filter_value) +{ + struct sss_domain_info *dom_head = NULL; + struct sss_domain_info *sid_dom = NULL; + enum idmap_error_code err; + char *sid = NULL; + const char *csid = NULL; + uint32_t id; + bool shortcut = false; + errno_t ret; + char *endptr; + + if (!sdap_idmap_domain_has_algorithmic_mapping(idmap_ctx, domain->name, + domain->domain_id)) { + goto done; + } + + switch (filter_type) { + case BE_FILTER_IDNUM: + /* convert value to ID */ + id = strtouint32(filter_value, &endptr, 10); + if ((errno != 0) || *endptr || (filter_value == endptr)) { + ret = errno ? errno : EINVAL; + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to convert filter value to " + "number [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + /* convert the ID to its SID equivalent */ + err = sss_idmap_unix_to_sid(idmap_ctx->map, id, &sid); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "Mapping ID [%s] to SID failed: " + "[%s]\n", filter_value, idmap_error_string(err)); + /* assume id is from a different domain */ + shortcut = true; + goto done; + } + /* fall through */ + SSS_ATTRIBUTE_FALLTHROUGH; + case BE_FILTER_SECID: + csid = sid == NULL ? filter_value : sid; + + dom_head = get_domains_head(domain); + if (dom_head == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find domain head\n"); + goto done; + } + + sid_dom = find_domain_by_sid(dom_head, csid); + if (sid_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid domain for SID:%s\n", csid); + goto done; + } + + if (strcasecmp(sid_dom->name, domain->name) != 0) { + shortcut = true; + } + break; + default: + break; + } + +done: + if (sid != NULL) { + sss_idmap_free_sid(idmap_ctx->map, sid); + } + + return shortcut; +} + +struct ad_handle_acct_info_state { + struct dp_id_data *ar; + struct sdap_id_ctx *ctx; + struct sdap_id_conn_ctx **conn; + struct sdap_domain *sdom; + size_t cindex; + struct ad_options *ad_options; + bool using_pac; + + int dp_error; + const char *err; +}; + +static errno_t ad_handle_acct_info_step(struct tevent_req *req); +static void ad_handle_acct_info_done(struct tevent_req *subreq); + +struct tevent_req * +ad_handle_acct_info_send(TALLOC_CTX *mem_ctx, + struct dp_id_data *ar, + struct sdap_id_ctx *ctx, + struct ad_options *ad_options, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx **conn) +{ + struct tevent_req *req; + struct ad_handle_acct_info_state *state; + struct be_ctx *be_ctx = ctx->be; + errno_t ret; + bool shortcut; + + req = tevent_req_create(mem_ctx, &state, struct ad_handle_acct_info_state); + if (req == NULL) { + return NULL; + } + state->ar = ar; + state->ctx = ctx; + state->sdom = sdom; + state->conn = conn; + state->ad_options = ad_options; + state->cindex = 0; + + /* Try to shortcut if this is ID or SID search and it belongs to + * other domain range than is in ar->domain. */ + shortcut = ad_account_can_shortcut(ctx->opts->idmap_ctx, + sdom->dom, + ar->filter_type, + ar->filter_value); + if (shortcut) { + DEBUG(SSSDBG_TRACE_FUNC, "This ID is from different domain\n"); + ret = EOK; + goto immediate; + } + + if (sss_domain_get_state(sdom->dom) == DOM_INACTIVE) { + ret = ERR_SUBDOM_INACTIVE; + goto immediate; + } + + ret = ad_handle_acct_info_step(req); + if (ret != EAGAIN) { + goto immediate; + } + + /* Lookup in progress */ + return req; + +immediate: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, be_ctx->ev); + return req; +} + +static errno_t +ad_handle_acct_info_step(struct tevent_req *req) +{ + struct tevent_req *subreq = NULL; + struct ad_handle_acct_info_state *state = tevent_req_data(req, + struct ad_handle_acct_info_state); + bool noexist_delete = false; + struct ldb_message *msg; + int ret; + + if (state->conn[state->cindex] == NULL) { + return EOK; + } + + if (state->conn[state->cindex+1] == NULL) { + noexist_delete = true; + } + + + state->using_pac = false; + if ((state->ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_INITGROUPS) { + ret = check_if_pac_is_available(state, state->sdom->dom, + state->ar, &msg); + + if (ret == EOK) { + /* evaluate PAC */ + state->using_pac = true; + subreq = ad_handle_pac_initgr_send(state, state->ctx->be, + state->ar, state->ctx, + state->sdom, + state->conn[state->cindex], + noexist_delete, + msg); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ad_handle_pac_initgr_send failed.\n"); + return ENOMEM; + } + + } + + /* Fall through if there is no PAC or any other error */ + } + + if (subreq == NULL) { + subreq = sdap_handle_acct_req_send(state, state->ctx->be, + state->ar, state->ctx, + state->sdom, + state->conn[state->cindex], + noexist_delete); + if (subreq == NULL) { + return ENOMEM; + } + } + + tevent_req_set_callback(subreq, ad_handle_acct_info_done, req); + return EAGAIN; +} + +static void +ad_handle_acct_info_done(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error; + int sdap_err; + const char *err; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_handle_acct_info_state *state = tevent_req_data(req, + struct ad_handle_acct_info_state); + + if (state->using_pac) { + ret = ad_handle_pac_initgr_recv(subreq, &dp_error, &err, &sdap_err); + } else { + ret = sdap_handle_acct_req_recv(subreq, &dp_error, &err, &sdap_err); + } + if (dp_error == DP_ERR_OFFLINE + && state->conn[state->cindex+1] != NULL + && state->conn[state->cindex]->ignore_mark_offline) { + /* This is a special case: GC does not work. + * We need to Fall back to ldap + */ + ret = EOK; + sdap_err = ENOENT; + } + talloc_zfree(subreq); + if (ret != EOK) { + /* if GC was not used dp error should be set */ + state->dp_error = dp_error; + state->err = err; + + goto fail; + } + + if (sdap_err == EOK) { + tevent_req_done(req); + return; + } else if (sdap_err != ENOENT) { + ret = EIO; + goto fail; + } + + /* Ret is only ENOENT now. Try the next connection */ + state->cindex++; + ret = ad_handle_acct_info_step(req); + if (ret != EAGAIN) { + /* No additional search in progress. Save the last + * error status, we'll be returning it. + */ + state->dp_error = dp_error; + state->err = err; + + if (ret == EOK) { + /* No more connections */ + tevent_req_done(req); + } else { + goto fail; + } + return; + } + + /* Another lookup in progress */ + return; + +fail: + if (IS_SUBDOMAIN(state->sdom->dom)) { + /* Deactivate subdomain on lookup errors instead of going + * offline completely. + * This is a stopgap, until our failover is per-domain, + * not per-backend. Unfortunately, we can't rewrite the error + * code on some reported codes only, because sdap_id_op code + * encapsulated the failover as well.. + */ + ret = ERR_SUBDOM_INACTIVE; + } + tevent_req_error(req, ret); + return; +} + +errno_t +ad_handle_acct_info_recv(struct tevent_req *req, + int *_dp_error, const char **_err) +{ + struct ad_handle_acct_info_state *state = tevent_req_data(req, + struct ad_handle_acct_info_state); + + if (_dp_error) { + *_dp_error = state->dp_error; + } + + if (_err) { + *_err = state->err; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct sdap_id_conn_ctx ** +get_conn_list(TALLOC_CTX *mem_ctx, struct ad_id_ctx *ad_ctx, + struct sss_domain_info *dom, struct dp_id_data *ar) +{ + struct sdap_id_conn_ctx **clist; + + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: /* user */ + clist = ad_user_conn_list(mem_ctx, ad_ctx, dom); + break; + case BE_REQ_BY_SECID: /* by SID */ + case BE_REQ_USER_AND_GROUP: /* get SID */ + case BE_REQ_GROUP: /* group */ + case BE_REQ_INITGROUPS: /* init groups for user */ + clist = ad_gc_conn_list(mem_ctx, ad_ctx, dom); + break; + default: + /* Requests for other object should only contact LDAP by default */ + clist = ad_ldap_conn_list(mem_ctx, ad_ctx, dom); + break; + } + + return clist; +} + +struct ad_account_info_state { + const char *err_msg; + int dp_error; +}; + +static void ad_account_info_done(struct tevent_req *subreq); + +struct tevent_req * +ad_account_info_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx, + struct dp_id_data *data) +{ + struct sss_domain_info *domain = NULL; + struct ad_account_info_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_id_conn_ctx **clist = NULL; + struct sdap_id_ctx *sdap_id_ctx = NULL; + struct sdap_domain *sdom; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ad_account_info_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + sdap_id_ctx = id_ctx->sdap_id_ctx; + + domain = be_ctx->domain; + if (strcasecmp(data->domain, be_ctx->domain->name) != 0) { + /* Subdomain request, verify subdomain. */ + domain = find_domain_by_name(be_ctx->domain, data->domain, true); + } + + if (domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain\n"); + ret = EINVAL; + goto immediately; + } + + /* Determine whether to connect to GC, LDAP or try both. */ + clist = get_conn_list(state, id_ctx, domain, data); + if (clist == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create conn list\n"); + ret = EIO; + goto immediately; + } + + sdom = sdap_domain_get(sdap_id_ctx->opts, domain); + if (sdom == NULL) { + ret = EIO; + goto immediately; + } + + subreq = ad_handle_acct_info_send(state, data, sdap_id_ctx, + id_ctx->ad_options, sdom, clist); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, ad_account_info_done, req); + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, be_ctx->ev); + return req; +} + +static void ad_account_info_done(struct tevent_req *subreq) +{ + struct ad_account_info_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_account_info_state); + + ret = ad_handle_acct_info_recv(subreq, &state->dp_error, &state->err_msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ad_handle_acct_info_recv failed [%d]: %s\n", + ret, sss_strerror(ret)); + /* The caller wouldn't fail either, just report the error up */ + } + talloc_zfree(subreq); + tevent_req_done(req); +} + +errno_t ad_account_info_recv(struct tevent_req *req, + int *_dp_error, + const char **_err_msg) +{ + struct ad_account_info_state *state = NULL; + + state = tevent_req_data(req, struct ad_account_info_state); + + if (_err_msg != NULL) { + *_err_msg = state->err_msg; + } + + if (_dp_error) { + *_dp_error = state->dp_error; + } + + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ad_account_info_handler_state { + struct sss_domain_info *domain; + struct dp_reply_std reply; +}; + +static void ad_account_info_handler_done(struct tevent_req *subreq); + +struct tevent_req * +ad_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params) +{ + struct ad_account_info_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + + req = tevent_req_create(mem_ctx, &state, + struct ad_account_info_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sdap_is_enum_request(data)) { + DEBUG(SSSDBG_TRACE_LIBS, "Skipping enumeration on demand\n"); + ret = EOK; + goto immediately; + } + + subreq = ad_account_info_send(state, params->be_ctx, id_ctx, data); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ad_account_info_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ad_account_info_handler_done(struct tevent_req *subreq) +{ + struct ad_account_info_handler_state *state; + struct tevent_req *req; + const char *err_msg; + int dp_error = DP_ERR_FATAL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_account_info_handler_state); + + ret = ad_account_info_recv(subreq, &dp_error, &err_msg); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, err_msg); + tevent_req_done(req); +} + +errno_t ad_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct ad_account_info_handler_state *state = NULL; + + state = tevent_req_data(req, struct ad_account_info_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} + +struct ad_enumeration_state { + struct ad_id_ctx *id_ctx; + struct ldap_enum_ctx *ectx; + struct sdap_id_op *sdap_op; + struct tevent_context *ev; + + const char *realm; + struct sdap_domain *sdom; + struct sdap_domain *sditer; +}; + +static void ad_enumeration_conn_done(struct tevent_req *subreq); +static void ad_enumeration_master_done(struct tevent_req *subreq); +static errno_t ad_enum_sdom(struct tevent_req *req, struct sdap_domain *sd, + struct ad_id_ctx *id_ctx); +static void ad_enumeration_done(struct tevent_req *subreq); + +struct tevent_req * +ad_id_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct ad_enumeration_state *state; + struct ldap_enum_ctx *ectx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ad_enumeration_state); + if (req == NULL) return NULL; + + ectx = talloc_get_type(pvt, struct ldap_enum_ctx); + if (ectx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve ldap_enum_ctx!\n"); + ret = EFAULT; + goto fail; + } + + state->ectx = ectx; + state->ev = ev; + state->sdom = ectx->sdom; + state->sditer = state->sdom; + state->id_ctx = talloc_get_type(ectx->pvt, struct ad_id_ctx); + + state->realm = dp_opt_get_cstring(state->id_ctx->ad_options->basic, + AD_KRB5_REALM); + if (state->realm == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "Missing realm\n"); + ret = EINVAL; + goto fail; + } + + state->sdap_op = sdap_id_op_create(state, + state->id_ctx->ldap_ctx->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n"); + ret = ENOMEM; + goto fail; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto fail; + } + tevent_req_set_callback(subreq, ad_enumeration_conn_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +ad_enumeration_conn_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_enumeration_state *state = tevent_req_data(req, + struct ad_enumeration_state); + int ret, dp_error; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, + "Backend is marked offline, retry later!\n"); + tevent_req_done(req); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Domain enumeration failed to connect to " \ + "LDAP server: (%d)[%s]\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } + return; + } + + subreq = ad_domain_info_send(state, state->ev, + state->id_ctx->ldap_ctx, + state->sdap_op, + state->sdom->dom->name); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ad_domain_info_send failed.\n"); + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, ad_enumeration_master_done, req); +} + +static void +ad_enumeration_master_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_enumeration_state *state = tevent_req_data(req, + struct ad_enumeration_state); + char *flat_name; + char *dns_name; + char *master_sid; + char *forest; + + ret = ad_domain_info_recv(subreq, state, + &flat_name, &master_sid, NULL, &forest); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot retrieve master domain info\n"); + tevent_req_error(req, ret); + return; + } + + dns_name = dp_opt_get_string(state->id_ctx->ad_options->basic, AD_DOMAIN); + if (dns_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No domain name for AD?\n"); + ret = EIO; + tevent_req_error(req, ret); + return; + } + + ret = sysdb_master_domain_add_info(state->sdom->dom, state->realm, flat_name, + dns_name, master_sid, forest, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot save master domain info\n"); + tevent_req_error(req, ret); + return; + } + + ret = ad_enum_sdom(req, state->sdom, state->id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not enumerate domain %s\n", state->sdom->dom->name); + tevent_req_error(req, ret); + return; + } + + /* Execution will resume in ad_enumeration_done */ +} + +static errno_t +ad_enum_sdom(struct tevent_req *req, + struct sdap_domain *sd, + struct ad_id_ctx *id_ctx) +{ + struct sdap_id_conn_ctx *user_conn; + struct tevent_req *subreq; + struct ad_enumeration_state *state = tevent_req_data(req, + struct ad_enumeration_state); + + if (dp_opt_get_bool(id_ctx->ad_options->basic, AD_ENABLE_GC)) { + user_conn = id_ctx->gc_ctx; + } else { + user_conn = id_ctx->ldap_ctx; + } + + /* Groups are searched for in LDAP, users in GC. Services (if present, + * which is unlikely in AD) from LDAP as well + */ + subreq = sdap_dom_enum_ex_send(state, state->ev, + id_ctx->sdap_id_ctx, + sd, + user_conn, /* Users */ + id_ctx->ldap_ctx, /* Groups */ + id_ctx->ldap_ctx); /* Services */ + if (subreq == NULL) { + /* The ptask API will reschedule the enumeration on its own on + * failure */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to schedule enumeration, retrying later!\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ad_enumeration_done, req); + + return EOK; +} + +static errno_t ad_enum_cross_dom_members(struct sdap_options *opts, + struct sss_domain_info *dom); + +static void +ad_enumeration_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_enumeration_state *state = tevent_req_data(req, + struct ad_enumeration_state); + + ret = sdap_dom_enum_ex_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not enumerate domain %s\n", state->sditer->dom->name); + tevent_req_error(req, ret); + return; + } + + do { + state->sditer = state->sditer->next; + } while (state->sditer && + state->sditer->dom->enumerate == false); + + if (state->sditer != NULL) { + ret = ad_enum_sdom(req, state->sditer, state->sditer->pvt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not enumerate domain %s\n", + state->sditer->dom->name); + tevent_req_error(req, ret); + return; + } + + /* Execution will resume in ad_enumeration_done */ + return; + } + + /* No more subdomains to enumerate. Check if we need to fixup + * cross-domain membership + */ + if (state->sditer != state->sdom) { + /* We did enumerate at least one subdomain. Walk the subdomains + * and fixup members for each of them + */ + for (state->sditer = state->sdom; + state->sditer; + state->sditer = state->sditer->next) { + ret = ad_enum_cross_dom_members(state->id_ctx->ad_options->id, + state->sditer->dom); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not check cross-domain " + "memberships for %s, group memberships might be " + "incomplete!\n", state->sdom->dom->name); + continue; + } + } + } + + tevent_req_done(req); +} + +static errno_t ad_group_extra_members(TALLOC_CTX *mem_ctx, + const struct ldb_message *group, + struct sss_domain_info *dom, + char ***_group_only); +static errno_t ad_group_add_member(struct sdap_options *opts, + struct sss_domain_info *group_domain, + struct ldb_dn *group_dn, + const char *member); + +static errno_t +ad_enum_cross_dom_members(struct sdap_options *opts, + struct sss_domain_info *dom) +{ + errno_t ret; + errno_t sret; + char *filter; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = { + SYSDB_NAME, + SYSDB_MEMBER, + SYSDB_ORIG_MEMBER, + NULL + }; + size_t count, i, mi; + struct ldb_message **msgs; + bool in_transaction = false; + char **group_only; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + ret = sysdb_transaction_start(dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + filter = talloc_asprintf(tmp_ctx, "(%s=*)", SYSDB_NAME); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_groups(tmp_ctx, dom, filter, attrs, &count, &msgs); + if (ret != EOK) { + goto done; + } + + for (i = 0; i < count; i++) { + ret = ad_group_extra_members(tmp_ctx, msgs[i], dom, &group_only); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to check extra members\n"); + continue; + } else if (group_only == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, "No extra members\n"); + continue; + } + + /* Group has extra members */ + for (mi = 0; group_only[mi]; mi++) { + ret = ad_group_add_member(opts, dom, msgs[i]->dn, group_only[mi]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to add [%s]: %s\n", + group_only[mi], strerror(ret)); + continue; + } + } + + talloc_zfree(group_only); + } + + ret = sysdb_transaction_commit(dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(dom->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ad_group_stored_orig_members(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + struct ldb_dn *dn, char ***_odn_list); + +static errno_t +ad_group_extra_members(TALLOC_CTX *mem_ctx, const struct ldb_message *group, + struct sss_domain_info *dom, char ***_group_only) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *m, *om; + const char *name; + errno_t ret; + char **sysdb_odn_list; + const char **group_odn_list; + char **group_only = NULL; + + if (_group_only == NULL) return EINVAL; + *_group_only = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + om = ldb_msg_find_element(group, SYSDB_ORIG_MEMBER); + m = ldb_msg_find_element(group, SYSDB_MEMBER); + name = ldb_msg_find_attr_as_string(group, SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "A group with no name!\n"); + ret = EFAULT; + goto done; + } + + if (om == NULL || om->num_values == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Group %s has no original members\n", name); + ret = EOK; + goto done; + } + + if (m == NULL || (m->num_values < om->num_values)) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group %s has %d members but %d original members\n", + name, m ? m->num_values : 0, om->num_values); + + /* Get the list of originalDN attributes that are already + * linked to the group + */ + ret = ad_group_stored_orig_members(tmp_ctx, dom, group->dn, + &sysdb_odn_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not retrieve list of original members for %s\n", + name); + goto done; + } + + /* Get the list of original DN attributes the group had in AD */ + group_odn_list = sss_ldb_el_to_string_list(tmp_ctx, om); + if (group_odn_list == NULL) { + ret = EFAULT; + goto done; + } + + /* Compare the two lists */ + ret = diff_string_lists(tmp_ctx, discard_const(group_odn_list), + sysdb_odn_list, &group_only, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not compare lists of members for %s\n", name); + goto done; + } + } + + ret = EOK; + *_group_only = talloc_steal(mem_ctx, group_only); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ad_group_stored_orig_members(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + struct ldb_dn *dn, char ***_odn_list) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + size_t m_count, i; + struct ldb_message **members; + const char *attrs[] = { + SYSDB_NAME, + SYSDB_ORIG_DN, + NULL + }; + char **odn_list; + const char *odn; + size_t oi; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + /* Get all entries member element points to */ + ret = sysdb_asq_search(tmp_ctx, dom, dn, NULL, SYSDB_MEMBER, + attrs, &m_count, &members); + if (ret != EOK) { + goto done; + } + + odn_list = talloc_zero_array(tmp_ctx, char *, m_count + 1); + if (odn_list == NULL) { + ret = ENOMEM; + goto done; + } + + /* Get a list of their original DNs */ + oi = 0; + for (i = 0; i < m_count; i++) { + odn = ldb_msg_find_attr_as_string(members[i], SYSDB_ORIG_DN, NULL); + if (odn == NULL) { + continue; + } + + odn_list[oi] = talloc_strdup(odn_list, odn); + if (odn_list[oi] == NULL) { + ret = ENOMEM; + goto done; + } + oi++; + DEBUG(SSSDBG_TRACE_INTERNAL, "Member %s already in sysdb\n", odn); + } + + ret = EOK; + *_odn_list = talloc_steal(mem_ctx, odn_list); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ad_group_add_member(struct sdap_options *opts, + struct sss_domain_info *group_domain, + struct ldb_dn *group_dn, + const char *member) +{ + struct sdap_domain *sd; + struct ldb_dn *base_dn; + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *mem_filter; + size_t msgs_count; + struct ldb_message **msgs; + + /* This member would be from a different domain */ + sd = sdap_domain_get_by_dn(opts, member); + if (sd == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No matching domain for %s\n", member); + return ENOENT; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + mem_filter = talloc_asprintf(tmp_ctx, "(%s=%s)", + SYSDB_ORIG_DN, member); + if (mem_filter == NULL) { + ret = ENOMEM; + goto done; + } + + base_dn = sysdb_domain_dn(tmp_ctx, sd->dom); + if (base_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, sd->dom->sysdb, base_dn, + LDB_SCOPE_SUBTREE, mem_filter, NULL, + &msgs_count, &msgs); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "No member [%s] in sysdb\n", member); + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "[%s] found in sysdb\n", member); + + if (msgs_count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Search by orig DN returned %zd results!\n", msgs_count); + ret = EFAULT; + goto done; + } + + ret = sysdb_mod_group_member(group_domain, msgs[0]->dn, group_dn, SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add [%s] as a member of [%s]\n", + ldb_dn_get_linearized(msgs[0]->dn), + ldb_dn_get_linearized(group_dn)); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ad_id_enumeration_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static errno_t ad_get_account_domain_prepare_search(struct tevent_req *req); +static errno_t ad_get_account_domain_connect_retry(struct tevent_req *req); +static void ad_get_account_domain_connect_done(struct tevent_req *subreq); +static void ad_get_account_domain_search(struct tevent_req *req); +static void ad_get_account_domain_search_done(struct tevent_req *subreq); +static void ad_get_account_domain_evaluate(struct tevent_req *req); + +struct ad_get_account_domain_state { + struct tevent_context *ev; + struct ad_id_ctx *id_ctx; + struct sdap_id_ctx *sdap_id_ctx; + struct sdap_domain *sdom; + uint32_t entry_type; + uint32_t filter_type; + char *clean_filter; + + bool twopass; + + struct sdap_search_base **search_bases; + size_t base_iter; + const char *base_filter; + char *filter; + const char **attrs; + int dp_error; + struct dp_reply_std reply; + struct sdap_id_op *op; + struct sysdb_attrs **objects; + size_t count; + + const char *found_domain_name; +}; + +struct tevent_req * +ad_get_account_domain_send(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *id_ctx, + struct dp_get_acct_domain_data *data, + struct dp_req_params *params) +{ + struct ad_get_account_domain_state *state; + struct tevent_req *req; + errno_t ret; + bool use_id_mapping; + struct sss_domain_info *domain; + + req = tevent_req_create(mem_ctx, &state, + struct ad_get_account_domain_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + state->ev = params->ev; + state->id_ctx = id_ctx; + state->sdap_id_ctx = id_ctx->sdap_id_ctx; + state->entry_type = data->entry_type & BE_REQ_TYPE_MASK; + state->filter_type = data->filter_type; + state->attrs = talloc_array(state, const char *, 2); + if (state->attrs == NULL) { + ret = ENOMEM; + goto immediately; + } + state->attrs[0] = "objectclass"; + state->attrs[1] = NULL; + + if (sss_domain_is_mpg(params->be_ctx->domain) == true + || state->entry_type == BE_REQ_USER_AND_GROUP) { + state->twopass = true; + if (state->entry_type == BE_REQ_USER_AND_GROUP) { + state->entry_type = BE_REQ_GROUP; + } + } + + /* SID lookup does not require communication with backend */ + if (state->entry_type == BE_REQ_BY_SECID) { + domain = find_domain_by_sid(params->domain, data->filter_value); + if (domain == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "SID %s does not fit into any domain\n", data->filter_value); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ERR_NOT_FOUND, NULL); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "SID %s fits into domain %s\n", data->filter_value, domain->name); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, EOK, domain->name); + } + tevent_req_done(req); + tevent_req_post(req, params->ev); + return req; + } + + /* The get-account-domain request only works with GC */ + if (dp_opt_get_bool(id_ctx->ad_options->basic, AD_ENABLE_GC) == false) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Global catalog support is not enabled, " + "cannot locate the account domain\n"); + ret = ERR_GET_ACCT_DOM_NOT_SUPPORTED; + goto immediately; + } + + state->sdom = sdap_domain_get(id_ctx->sdap_id_ctx->opts, + params->be_ctx->domain); + if (state->sdom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find sdap_domain\n"); + ret = EIO; + goto immediately; + } + + /* Currently we only support locating the account domain + * if ID mapping is disabled. With ID mapping enabled, we can + * already shortcut the 'real' ID request + */ + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + state->sdap_id_ctx->opts->idmap_ctx, + state->sdom->dom->name, + state->sdom->dom->domain_id); + if (use_id_mapping == true) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No point in locating domain with GC if ID-mapping " + "is enabled\n"); + ret = ERR_GET_ACCT_DOM_NOT_SUPPORTED; + goto immediately; + } + + ret = sss_filter_sanitize(state, data->filter_value, &state->clean_filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot sanitize filter [%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + ret = ad_get_account_domain_prepare_search(req); + if (ret != EOK) { + goto immediately; + } + + /* FIXME - should gc_ctx always default to ignore_offline on creation + * time rather than setting the flag on first use? + */ + id_ctx->gc_ctx->ignore_mark_offline = true; + state->op = sdap_id_op_create(state, id_ctx->gc_ctx->conn_cache); + if (state->op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto immediately; + } + + ret = ad_get_account_domain_connect_retry(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Connection error"); + goto immediately; + } + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static errno_t ad_get_account_domain_prepare_search(struct tevent_req *req) +{ + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + const char *attr_name = NULL; + const char *objectclass = NULL; + + switch (state->entry_type) { + case BE_REQ_USER: + state->search_bases = state->sdom->user_search_bases; + attr_name = state->sdap_id_ctx->opts->user_map[SDAP_AT_USER_UID].name; + objectclass = state->sdap_id_ctx->opts->user_map[SDAP_OC_USER].name; + break; + case BE_REQ_GROUP: + state->search_bases = state->sdom->group_search_bases; + attr_name = state->sdap_id_ctx->opts->group_map[SDAP_AT_GROUP_GID].name; + objectclass = state->sdap_id_ctx->opts->group_map[SDAP_OC_GROUP].name; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "Unsupported request type %X\n", + state->entry_type & BE_REQ_TYPE_MASK); + return EINVAL; + } + + if (state->search_bases == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to prepare search: missing search_bases\n"); + return EINVAL; + } + + switch (state->filter_type) { + case BE_FILTER_IDNUM: + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "Unsupported filter type %X\n", state->filter_type); + return EINVAL; + } + + talloc_zfree(state->base_filter); + state->base_filter = talloc_asprintf(state, + "(&(%s=%s)(objectclass=%s))", + attr_name, + state->clean_filter, + objectclass); + if (state->base_filter == NULL) { + return ENOMEM; + } + + return EOK; +} + +static errno_t ad_get_account_domain_connect_retry(struct tevent_req *req) +{ + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + struct tevent_req *subreq; + errno_t ret; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ad_get_account_domain_connect_done, req); + return ret; +} + +static void ad_get_account_domain_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + int dp_error = DP_ERR_FATAL; + errno_t ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + ad_get_account_domain_search(req); +} + +static void ad_get_account_domain_search(struct tevent_req *req) +{ + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + struct tevent_req *subreq; + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (state->filter == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + subreq = sdap_get_generic_send(state, state->ev, state->sdap_id_ctx->opts, + sdap_id_op_handle(state->op), + "", + LDAP_SCOPE_SUBTREE, + state->filter, + state->attrs, NULL, 0, + dp_opt_get_int(state->sdap_id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n"); + tevent_req_error(req, EIO); + return; + } + + tevent_req_set_callback(subreq, ad_get_account_domain_search_done, req); +} + +static void ad_get_account_domain_search_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + size_t count; + struct sysdb_attrs **objects; + errno_t ret; + + ret = sdap_get_generic_recv(subreq, state, + &count, &objects); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search returned %zu results.\n", count); + + if (count > 0) { + size_t copied; + + state->objects = + talloc_realloc(state, + state->objects, + struct sysdb_attrs *, + state->count + count + 1); + if (!state->objects) { + tevent_req_error(req, ENOMEM); + return; + } + + copied = sdap_steal_objects_in_dom(state->sdap_id_ctx->opts, + state->objects, + state->count, + NULL, + objects, count, + false); + + state->count += copied; + state->objects[state->count] = NULL; + } + + /* Even though we search with an empty search base (=across all domains) + * the reason we iterate over search bases is that the search bases can + * also contain a filter which might restrict the IDs we find + */ + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ad_get_account_domain_search(req); + return; + } + + /* No more searches, evaluate results */ + ad_get_account_domain_evaluate(req); +} + +static void ad_get_account_domain_evaluate(struct tevent_req *req) +{ + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + struct sss_domain_info *obj_dom; + errno_t ret; + + if (state->count == 0) { + if (state->twopass + && state->entry_type != BE_REQ_USER) { + DEBUG(SSSDBG_TRACE_FUNC, "Retrying search\n"); + + state->entry_type = BE_REQ_USER; + state->base_iter = 0; + ret = ad_get_account_domain_prepare_search(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot retry search\n"); + tevent_req_error(req, ret); + return; + } + + ad_get_account_domain_search(req); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Not found\n"); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ERR_NOT_FOUND, NULL); + tevent_req_done(req); + return; + } else if (state->count > 1) { + /* FIXME: If more than one entry was found, return error for now + * as the account requsts have no way of returning multiple + * messages back until we switch to the rdp_* requests + * from the responder side + */ + DEBUG(SSSDBG_OP_FAILURE, "Multiple entries found, error!\n"); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ERANGE, NULL); + tevent_req_done(req); + return; + } + + /* Exactly one entry was found */ + obj_dom = sdap_get_object_domain(state->sdap_id_ctx->opts, + state->objects[0], + state->sdom->dom); + if (obj_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not match entry with domain!\n"); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ERR_NOT_FOUND, NULL); + tevent_req_done(req); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Found object in domain %s\n", obj_dom->name); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, EOK, obj_dom->name); + tevent_req_done(req); +} + +errno_t ad_get_account_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct ad_get_account_domain_state *state = NULL; + + state = tevent_req_data(req, struct ad_get_account_domain_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/ad/ad_id.h b/src/providers/ad/ad_id.h new file mode 100644 index 0000000..d102643 --- /dev/null +++ b/src/providers/ad/ad_id.h @@ -0,0 +1,77 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef AD_ID_H_ +#define AD_ID_H_ + +struct tevent_req * +ad_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params); + +errno_t ad_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +struct tevent_req * +ad_account_info_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx, + struct dp_id_data *data); + +errno_t ad_account_info_recv(struct tevent_req *req, + int *_dp_error, + const char **_err_msg); + +struct tevent_req * +ad_handle_acct_info_send(TALLOC_CTX *mem_ctx, + struct dp_id_data *ar, + struct sdap_id_ctx *ctx, + struct ad_options *ad_options, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx **conn); +errno_t +ad_handle_acct_info_recv(struct tevent_req *req, + int *_dp_error, const char **_err); + +struct tevent_req * +ad_id_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); + +errno_t +ad_id_enumeration_recv(struct tevent_req *req); + +struct tevent_req * +ad_get_account_domain_send(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *id_ctx, + struct dp_get_acct_domain_data *data, + struct dp_req_params *params); + +errno_t ad_get_account_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +#endif /* AD_ID_H_ */ diff --git a/src/providers/ad/ad_init.c b/src/providers/ad/ad_init.c new file mode 100644 index 0000000..f452f28 --- /dev/null +++ b/src/providers/ad/ad_init.c @@ -0,0 +1,716 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + + +#include +#include +#include +#include + +#include + +#include "util/util.h" +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_access.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_access.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_init_shared.h" +#include "providers/ad/ad_id.h" +#include "providers/ad/ad_resolver.h" +#include "providers/ad/ad_srv.h" +#include "providers/be_dyndns.h" +#include "providers/ad/ad_subdomains.h" +#include "providers/ad/ad_domain_info.h" + +struct ad_init_ctx { + struct ad_options *options; + struct ad_id_ctx *id_ctx; + struct krb5_ctx *auth_ctx; + struct ad_resolver_ctx *resolver_ctx; +}; + +#define AD_COMPAT_ON "1" +static int ad_sasl_getopt(void *context, const char *plugin_name, + const char *option, + const char **result, unsigned *len) +{ + if (!plugin_name || !result) { + return SASL_FAIL; + } + if (!sdap_sasl_mech_needs_kinit(plugin_name)) { + return SASL_FAIL; + } + if (strcmp(option, "ad_compat") != 0) { + return SASL_FAIL; + } + *result = AD_COMPAT_ON; + if (len) { + *len = 2; + } + return SASL_OK; +} + +typedef int (*sss_sasl_gen_cb_fn)(void); + +static int map_sasl2sssd_log_level(int sasl_level) +{ + int sssd_level; + + switch(sasl_level) { + case SASL_LOG_ERR: /* log unusual errors (default) */ + sssd_level = SSSDBG_CRIT_FAILURE; + break; + case SASL_LOG_FAIL: /* log all authentication failures */ + sssd_level = SSSDBG_OP_FAILURE; + break; + case SASL_LOG_WARN: /* log non-fatal warnings */ + sssd_level = SSSDBG_MINOR_FAILURE; + break; + case SASL_LOG_NOTE: /* more verbose than LOG_WARN */ + case SASL_LOG_DEBUG: /* more verbose than LOG_NOTE */ + case SASL_LOG_TRACE: /* traces of internal protocols */ + case SASL_LOG_PASS: /* traces of internal protocols, including */ + sssd_level = SSSDBG_TRACE_ALL; + break; + default: + sssd_level = SSSDBG_TRACE_ALL; + break; + } + + return sssd_level; +} + +static int ad_sasl_log(void *context, int level, const char *message) +{ + int sssd_level; + + if (level == SASL_LOG_ERR || level == SASL_LOG_FAIL) { + sss_log(SSS_LOG_ERR, "%s\n", message); + } + + sssd_level = map_sasl2sssd_log_level(level); + DEBUG(sssd_level, "SASL: %s\n", message); + return SASL_OK; +} + +static const sasl_callback_t ad_sasl_callbacks[] = { + { SASL_CB_GETOPT, (sss_sasl_gen_cb_fn)(void *)ad_sasl_getopt, NULL }, + { SASL_CB_LOG, (sss_sasl_gen_cb_fn)(void *)ad_sasl_log, NULL }, + { SASL_CB_LIST_END, NULL, NULL } +}; + +/* This is quite a hack, we *try* to fool openldap libraries by initializing + * sasl first so we can pass in the SASL_CB_GETOPT callback we need to set some + * options. Should be removed as soon as openldap exposes a way to do that */ +static void ad_sasl_initialize(void) +{ + /* NOTE: this may fail if soe other library in the system happens to + * initialize and use openldap libraries or directly the cyrus-sasl + * library as this initialization function can be called only once per + * process */ + (void)sasl_client_init(ad_sasl_callbacks); +} + +static errno_t ad_init_options(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ad_options **_ad_options) +{ + struct ad_options *ad_options; + char *ad_servers = NULL; + char *ad_backup_servers = NULL; + char *ad_realm; + bool ad_use_ldaps = false; + errno_t ret; + + ad_sasl_initialize(); + + /* Get AD-specific options */ + ret = ad_get_common_options(mem_ctx, be_ctx->cdb, be_ctx->conf_path, + be_ctx->domain, &ad_options); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not parse common options " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + ad_servers = dp_opt_get_string(ad_options->basic, AD_SERVER); + ad_backup_servers = dp_opt_get_string(ad_options->basic, AD_BACKUP_SERVER); + ad_realm = dp_opt_get_string(ad_options->basic, AD_KRB5_REALM); + ad_use_ldaps = dp_opt_get_bool(ad_options->basic, AD_USE_LDAPS); + + /* Set up the failover service */ + ret = ad_failover_init(ad_options, be_ctx, ad_servers, ad_backup_servers, + ad_realm, AD_SERVICE_NAME, AD_GC_SERVICE_NAME, + dp_opt_get_string(ad_options->basic, AD_DOMAIN), + false, /* will be set in ad_get_auth_options() */ + ad_use_ldaps, + (size_t) -1, + (size_t) -1, + &ad_options->service); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to init AD failover service: " + "[%d]: %s\n", ret, sss_strerror(ret)); + talloc_free(ad_options); + return ret; + } + + *_ad_options = ad_options; + + return EOK; +} + +static errno_t ad_init_srv_plugin(struct be_ctx *be_ctx, + struct ad_options *ad_options) +{ + struct ad_srv_plugin_ctx *srv_ctx; + const char *hostname; + const char *ad_domain; + const char *ad_site_override; + bool sites_enabled; + errno_t ret; + + hostname = dp_opt_get_string(ad_options->basic, AD_HOSTNAME); + ad_domain = dp_opt_get_string(ad_options->basic, AD_DOMAIN); + ad_site_override = dp_opt_get_string(ad_options->basic, AD_SITE); + sites_enabled = dp_opt_get_bool(ad_options->basic, AD_ENABLE_DNS_SITES); + + if (!sites_enabled) { + ret = be_fo_set_dns_srv_lookup_plugin(be_ctx, hostname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set SRV lookup plugin " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + return EOK; + } + + srv_ctx = ad_srv_plugin_ctx_init(be_ctx, be_ctx, be_ctx->be_res, + default_host_dbs, ad_options->id, + ad_options, + hostname, ad_domain, + ad_site_override); + if (srv_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n"); + return ENOMEM; + } + + be_fo_set_srv_lookup_plugin(be_ctx, ad_srv_plugin_send, + ad_srv_plugin_recv, srv_ctx, "AD"); + + return EOK; +} + +static errno_t ad_init_sdap_access_ctx(struct ad_access_ctx *access_ctx) +{ + struct dp_option *options = access_ctx->ad_options; + struct sdap_id_ctx *sdap_id_ctx = access_ctx->ad_id_ctx->sdap_id_ctx; + struct sdap_access_ctx *sdap_access_ctx; + const char *filter; + + sdap_access_ctx = talloc_zero(access_ctx, struct sdap_access_ctx); + if (sdap_access_ctx == NULL) { + return ENOMEM; + } + + sdap_access_ctx->id_ctx = sdap_id_ctx; + + + /* If ad_access_filter is set, the value of ldap_acess_order is + * expire, filter, otherwise only expire. + */ + sdap_access_ctx->access_rule[0] = LDAP_ACCESS_EXPIRE; + filter = dp_opt_get_cstring(options, AD_ACCESS_FILTER); + if (filter != NULL) { + /* The processing of the extended filter is performed during the access + * check itself. + */ + sdap_access_ctx->filter = talloc_strdup(sdap_access_ctx, filter); + if (sdap_access_ctx->filter == NULL) { + talloc_free(sdap_access_ctx); + return ENOMEM; + } + + sdap_access_ctx->access_rule[1] = LDAP_ACCESS_FILTER; + sdap_access_ctx->access_rule[2] = LDAP_ACCESS_EMPTY; + } else { + sdap_access_ctx->access_rule[1] = LDAP_ACCESS_EMPTY; + } + + access_ctx->sdap_access_ctx = sdap_access_ctx; + + return EOK; +} + +errno_t ad_gpo_parse_map_options(struct ad_access_ctx *access_ctx); + +static errno_t ad_init_gpo(struct ad_access_ctx *access_ctx) +{ + struct dp_option *options; + const char *gpo_access_control_mode; + int gpo_cache_timeout; + errno_t ret; + + options = access_ctx->ad_options; + + /* GPO access control mode */ + gpo_access_control_mode = dp_opt_get_string(options, AD_GPO_ACCESS_CONTROL); + if (gpo_access_control_mode == NULL) { + return EINVAL; + } else if (strcasecmp(gpo_access_control_mode, "disabled") == 0) { + access_ctx->gpo_access_control_mode = GPO_ACCESS_CONTROL_DISABLED; + } else if (strcasecmp(gpo_access_control_mode, "permissive") == 0) { + access_ctx->gpo_access_control_mode = GPO_ACCESS_CONTROL_PERMISSIVE; + } else if (strcasecmp(gpo_access_control_mode, "enforcing") == 0) { + access_ctx->gpo_access_control_mode = GPO_ACCESS_CONTROL_ENFORCING; + } else { + DEBUG(SSSDBG_FATAL_FAILURE, "Unrecognized GPO access control mode: " + "%s\n", gpo_access_control_mode); + return EINVAL; + } + + /* GPO cache timeout */ + gpo_cache_timeout = dp_opt_get_int(options, AD_GPO_CACHE_TIMEOUT); + access_ctx->gpo_cache_timeout = gpo_cache_timeout; + + /* GPO logon maps */ + ret = sss_hash_create(access_ctx, 0, &access_ctx->gpo_map_options_table); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not create gpo_map_options " + "hash table [%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + ret = ad_gpo_parse_map_options(access_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not parse gpo_map_options " + "(invalid config) [%d]: %s\n", ret, sss_strerror(ret)); + talloc_zfree(access_ctx->gpo_map_options_table); + return ret; + } + + return EOK; +} + +static errno_t ad_init_auth_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ad_options *ad_options, + struct krb5_ctx **_auth_ctx) +{ + struct krb5_ctx *krb5_auth_ctx; + errno_t ret; + + krb5_auth_ctx = talloc_zero(mem_ctx, struct krb5_ctx); + if (krb5_auth_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + krb5_auth_ctx->config_type = K5C_GENERIC; + krb5_auth_ctx->sss_creds_password = true; + krb5_auth_ctx->service = ad_options->service->krb5_service; + + ret = ad_get_auth_options(krb5_auth_ctx, ad_options, be_ctx, + &krb5_auth_ctx->opts); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not determine Kerberos options\n"); + goto done; + } + + ret = krb5_child_init(krb5_auth_ctx, be_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not initialize krb5_child settings: " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ad_options->auth_ctx = krb5_auth_ctx; + *_auth_ctx = krb5_auth_ctx; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(krb5_auth_ctx); + } + + return ret; +} + +static errno_t ad_init_misc(struct be_ctx *be_ctx, + struct ad_options *ad_options, + struct ad_id_ctx *ad_id_ctx, + struct sdap_id_ctx *sdap_id_ctx) +{ + errno_t ret; + + ret = ad_dyndns_init(be_ctx, ad_options); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failure setting up automatic DNS update\n"); + /* Continue without DNS updates */ + } + + setup_ldap_debug(sdap_id_ctx->opts->basic); + + ret = setup_tls_config(sdap_id_ctx->opts->basic); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get TLS options [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = sdap_idmap_init(sdap_id_ctx, sdap_id_ctx, + &sdap_id_ctx->opts->idmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not initialize ID mapping. In case ID mapping properties " + "changed on the server, please remove the SSSD database\n"); + return ret; + } + + ret = sdap_id_setup_tasks(be_ctx, sdap_id_ctx, sdap_id_ctx->opts->sdom, + ad_id_enumeration_send, ad_id_enumeration_recv, + ad_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup background tasks " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + sdap_id_ctx->opts->sdom->pvt = ad_id_ctx; + + ret = ad_init_srv_plugin(be_ctx, ad_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup SRV plugin [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = ad_refresh_init(be_ctx, ad_id_ctx); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh " + "will not work [%d]: %s\n", ret, sss_strerror(ret)); + } + + ret = ad_machine_account_password_renewal_init(be_ctx, ad_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot setup task for machine account " + "password renewal.\n"); + return ret; + } + + ret = confdb_certmap_to_sysdb(be_ctx->cdb, be_ctx->domain, false); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize certificate mapping rules. " + "Authentication with certificates/Smartcards might not work " + "as expected.\n"); + /* not fatal, ignored */ + } + + ret = sdap_init_certmap(sdap_id_ctx, sdap_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialized certificate mapping.\n"); + return ret; + } + + return EOK; +} + +errno_t sssm_ad_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data) +{ + struct ad_init_ctx *init_ctx; + errno_t ret; + + init_ctx = talloc_zero(mem_ctx, struct ad_init_ctx); + if (init_ctx == NULL) { + return ENOMEM; + } + + /* Always initialize options since it is needed everywhere. */ + ret = ad_init_options(mem_ctx, be_ctx, &init_ctx->options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init AD options [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + /* Always initialize id_ctx since it is needed everywhere. */ + init_ctx->id_ctx = ad_id_ctx_init(init_ctx->options, be_ctx); + if (init_ctx->id_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize AD ID context\n"); + ret = ENOMEM; + goto done; + } + + init_ctx->options->id_ctx = init_ctx->id_ctx; + + ret = ad_get_id_options(init_ctx->options, + be_ctx->cdb, + be_ctx->conf_path, + be_ctx->provider, + &init_ctx->id_ctx->sdap_id_ctx->opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init AD id options\n"); + return ret; + } + + /* Setup miscellaneous things. */ + ret = ad_init_misc(be_ctx, init_ctx->options, init_ctx->id_ctx, + init_ctx->id_ctx->sdap_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init AD module " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Initialize auth_ctx only if one of the target is enabled. */ + if (dp_target_enabled(provider, module_name, DPT_AUTH, DPT_CHPASS)) { + ret = ad_init_auth_ctx(init_ctx, be_ctx, init_ctx->options, + &init_ctx->auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create auth context " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } + + *_module_data = init_ctx; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(init_ctx); + } + + return ret; +} + +errno_t sssm_ad_id_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ad_init_ctx *init_ctx; + struct ad_id_ctx *id_ctx; + + init_ctx = talloc_get_type(module_data, struct ad_init_ctx); + id_ctx = init_ctx->id_ctx; + + dp_set_method(dp_methods, DPM_ACCOUNT_HANDLER, + ad_account_info_handler_send, ad_account_info_handler_recv, id_ctx, + struct ad_id_ctx, struct dp_id_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_CHECK_ONLINE, + sdap_online_check_handler_send, sdap_online_check_handler_recv, id_ctx->sdap_id_ctx, + struct sdap_id_ctx, void, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_ACCT_DOMAIN_HANDLER, + ad_get_account_domain_send, ad_get_account_domain_recv, id_ctx, + struct ad_id_ctx, struct dp_get_acct_domain_data, struct dp_reply_std); + + return EOK; +} + +errno_t sssm_ad_auth_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ad_init_ctx *init_ctx; + struct krb5_ctx *auth_ctx; + + init_ctx = talloc_get_type(module_data, struct ad_init_ctx); + auth_ctx = init_ctx->auth_ctx; + + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + krb5_pam_handler_send, krb5_pam_handler_recv, auth_ctx, + struct krb5_ctx, struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_ad_chpass_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + return sssm_ad_auth_init(mem_ctx, be_ctx, module_data, dp_methods); +} + +errno_t sssm_ad_access_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ad_init_ctx *init_ctx; + struct ad_access_ctx *access_ctx; + errno_t ret; + + init_ctx = talloc_get_type(module_data, struct ad_init_ctx); + + access_ctx = talloc_zero(mem_ctx, struct ad_access_ctx); + if (access_ctx == NULL) { + return ENOMEM; + } + + access_ctx->ad_id_ctx = init_ctx->id_ctx; + + ret = dp_copy_options(access_ctx, init_ctx->options->basic, AD_OPTS_BASIC, + &access_ctx->ad_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not initialize access provider " + "options [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = ad_init_sdap_access_ctx(access_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not initialize sdap access context " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = ad_init_gpo(access_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not initialize GPO " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + dp_set_method(dp_methods, DPM_ACCESS_HANDLER, + ad_pam_access_handler_send, ad_pam_access_handler_recv, access_ctx, + struct ad_access_ctx, struct pam_data, struct pam_data *); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(access_ctx); + } + + return ret; +} + +errno_t sssm_ad_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_AUTOFS + struct ad_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing AD autofs handler\n"); + init_ctx = talloc_get_type(module_data, struct ad_init_ctx); + + return ad_autofs_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); +#else + DEBUG(SSSDBG_MINOR_FAILURE, "Autofs init handler called but SSSD is " + "built without autofs support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ad_subdomains_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ad_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing AD subdomains handler\n"); + init_ctx = talloc_get_type(module_data, struct ad_init_ctx); + + return ad_subdomains_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); +} + +errno_t sssm_ad_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_SUDO + struct ad_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing AD sudo handler\n"); + init_ctx = talloc_get_type(module_data, struct ad_init_ctx); + + return ad_sudo_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); +#else + DEBUG(SSSDBG_MINOR_FAILURE, "Sudo init handler called but SSSD is " + "built without sudo support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ad_resolver_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ad_init_ctx *init_ctx; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing AD resolver handler\n"); + init_ctx = talloc_get_type(module_data, struct ad_init_ctx); + + ret = ad_resolver_ctx_init(init_ctx, init_ctx->id_ctx, + &init_ctx->resolver_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to initialize AD resolver context\n"); + return ret; + } + + ret = ad_resolver_setup_tasks(be_ctx, init_ctx->resolver_ctx, + ad_resolver_enumeration_send, + ad_resolver_enumeration_recv); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to setup resolver background tasks [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + dp_set_method(dp_methods, DPM_RESOLVER_HOSTS_HANDLER, + sdap_iphost_handler_send, sdap_iphost_handler_recv, + init_ctx->resolver_ctx->sdap_resolver_ctx, + struct sdap_resolver_ctx, + struct dp_resolver_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_RESOLVER_IP_NETWORK_HANDLER, + sdap_ipnetwork_handler_send, sdap_ipnetwork_handler_recv, + init_ctx->resolver_ctx->sdap_resolver_ctx, + struct sdap_resolver_ctx, + struct dp_resolver_data, struct dp_reply_std); + + return EOK; +} diff --git a/src/providers/ad/ad_machine_pw_renewal.c b/src/providers/ad/ad_machine_pw_renewal.c new file mode 100644 index 0000000..56b64a2 --- /dev/null +++ b/src/providers/ad/ad_machine_pw_renewal.c @@ -0,0 +1,424 @@ +/* + SSSD + + Authors: + Sumit Bose + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + + +#include "util/util.h" +#include "util/strtonum.h" +#include "providers/be_ptask.h" +#include "providers/ad/ad_common.h" + +#ifndef RENEWAL_PROG_PATH +#define RENEWAL_PROG_PATH "/usr/sbin/adcli" +#endif + +struct renewal_data { + struct be_ctx *be_ctx; + char *prog_path; + const char **extra_args; +}; + +static errno_t get_adcli_extra_args(const char *ad_domain, + const char *ad_hostname, + const char *ad_keytab, + size_t pw_lifetime_in_days, + bool add_samba_data, + size_t period, + size_t initial_delay, + struct renewal_data *renewal_data) +{ + const char **args; + size_t c = 0; + + if (ad_domain == NULL || ad_hostname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing AD domain or hostname.\n"); + return EINVAL; + } + + renewal_data->prog_path = talloc_strdup(renewal_data, RENEWAL_PROG_PATH); + if (renewal_data->prog_path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + + args = talloc_array(renewal_data, const char *, 9); + if (args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + + /* extra_args are added in revers order */ + /* first add NULL as a placeholder for the server name which is determined + * at runtime */ + args[c++] = NULL; + args[c++] = talloc_asprintf(args, "--computer-password-lifetime=%zu", + pw_lifetime_in_days); + if (add_samba_data) { + args[c++] = talloc_strdup(args, "--add-samba-data"); + } + args[c++] = talloc_asprintf(args, "--host-fqdn=%s", ad_hostname); + if (ad_keytab != NULL) { + args[c++] = talloc_asprintf(args, "--host-keytab=%s", ad_keytab); + } + args[c++] = talloc_asprintf(args, "--domain=%s", ad_domain); + if (DEBUG_IS_SET(SSSDBG_TRACE_LIBS)) { + args[c++] = talloc_strdup(args, "--verbose"); + } + args[c++] = talloc_strdup(args, "update"); + args[c] = NULL; + + do { + if (args[--c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "talloc failed while copying arguments.\n"); + talloc_free(args); + return ENOMEM; + } + } while (c != 1); /* it is expected that the first element is NULL */ + + renewal_data->extra_args = args; + + return EOK; +} + +struct renewal_state { + int child_status; + struct sss_child_ctx_old *child_ctx; + struct tevent_timer *timeout_handler; + struct tevent_context *ev; + + struct child_io_fds *io; +}; + +static void ad_machine_account_password_renewal_done(struct tevent_req *subreq); +static void +ad_machine_account_password_renewal_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +static struct tevent_req * +ad_machine_account_password_renewal_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct renewal_data *renewal_data; + struct renewal_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + pid_t child_pid; + struct timeval tv; + int pipefd_to_child[2] = PIPE_INIT; + int pipefd_from_child[2] = PIPE_INIT; + int ret; + const char **extra_args; + const char *server_name; + + req = tevent_req_create(mem_ctx, &state, struct renewal_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + renewal_data = talloc_get_type(pvt, struct renewal_data); + + state->ev = ev; + state->child_status = EFAULT; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + server_name = be_fo_get_active_server_name(be_ctx, AD_SERVICE_NAME); + talloc_zfree(renewal_data->extra_args[0]); + if (server_name != NULL) { + renewal_data->extra_args[0] = talloc_asprintf(renewal_data->extra_args, + "--domain-controller=%s", + server_name); + /* if talloc_asprintf() fails we let adcli try to find a server */ + } + + extra_args = renewal_data->extra_args; + if (extra_args[0] == NULL) { + extra_args = &renewal_data->extra_args[1]; + } + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe (from) failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe (to) failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + renewal_data->prog_path, NULL, + extra_args, true, + STDIN_FILENO, STDERR_FILENO); + + /* We should never get here */ + DEBUG(SSSDBG_CRIT_FAILURE, "Could not exec renewal child\n"); + } else if (child_pid > 0) { /* parent */ + + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + ret = child_handler_setup(ev, child_pid, NULL, NULL, &state->child_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_RENEWAL_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = sss_tevent_timeval_current_ofs_time_t(be_ptask_get_timeout(be_ptask)); + state->timeout_handler = tevent_add_timer(ev, req, tv, + ad_machine_account_password_renewal_timeout, + req); + if(state->timeout_handler == NULL) { + ret = ERR_RENEWAL_CHILD; + goto done; + } + + subreq = read_pipe_send(state, ev, state->io->read_from_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); + ret = ERR_RENEWAL_CHILD; + goto done; + } + tevent_req_set_callback(subreq, + ad_machine_account_password_renewal_done, req); + + /* Now either wait for the timeout to fire or the child + * to finish + */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void ad_machine_account_password_renewal_done(struct tevent_req *subreq) +{ + uint8_t *buf; + ssize_t buf_len; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct renewal_state *state = tevent_req_data(req, struct renewal_state); + int ret; + + talloc_zfree(state->timeout_handler); + + ret = read_pipe_recv(subreq, state, &buf, &buf_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, "--- adcli output start---\n" + "%.*s" + "---adcli output end---\n", + (int) buf_len, buf); + + tevent_req_done(req); + return; +} + +static void +ad_machine_account_password_renewal_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct renewal_state *state = tevent_req_data(req, struct renewal_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for AD renewal child.\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_RENEWAL_CHILD); +} + +static errno_t +ad_machine_account_password_renewal_recv(struct tevent_req *req) +{ + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t ad_machine_account_password_renewal_init(struct be_ctx *be_ctx, + struct ad_options *ad_opts) +{ + int ret; + struct renewal_data *renewal_data; + int lifetime; + size_t period; + size_t offset; + size_t initial_delay; + const char *dummy; + char **opt_list; + int opt_list_size; + char *endptr; + + ret = access(RENEWAL_PROG_PATH, X_OK); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_CONF_SETTINGS, + "The helper program ["RENEWAL_PROG_PATH"] for renewal " + "doesn't exist [%d]: %s\n", ret, strerror(ret)); + return EOK; + } + + lifetime = dp_opt_get_int(ad_opts->basic, + AD_MAXIMUM_MACHINE_ACCOUNT_PASSWORD_AGE); + + if (lifetime == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "Automatic machine account renewal disabled.\n"); + return EOK; + } + + if (lifetime < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Illegal value [%d] for password lifetime.\n", lifetime); + return EINVAL; + } + + renewal_data = talloc(be_ctx, struct renewal_data); + if (renewal_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + dummy = dp_opt_get_cstring(ad_opts->basic, + AD_MACHINE_ACCOUNT_PASSWORD_RENEWAL_OPTS); + ret = split_on_separator(renewal_data, dummy, ':', true, false, + &opt_list, &opt_list_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed.\n"); + goto done; + } + + if (opt_list_size < 2 || opt_list_size > 3) { + DEBUG(SSSDBG_CRIT_FAILURE, "Wrong number of renewal options %d\n", + opt_list_size); + ret = EINVAL; + goto done; + } + + period = strtouint32(opt_list[0], &endptr, 10); + if (errno != 0 || *endptr != '\0' || opt_list[0] == endptr) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse first renewal option.\n"); + ret = EINVAL; + goto done; + } + + initial_delay = strtouint32(opt_list[1], &endptr, 10); + if (errno != 0 || *endptr != '\0' || opt_list[1] == endptr) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse second renewal option.\n"); + ret = EINVAL; + goto done; + } + + if (opt_list_size == 3) { + offset = strtouint32(opt_list[2], &endptr, 10); + if (errno != 0 || *endptr != '\0' || opt_list[2] == endptr) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse third renewal option.\n"); + ret = EINVAL; + goto done; + } + } else { + offset = 0; + } + + ret = get_adcli_extra_args(dp_opt_get_cstring(ad_opts->basic, AD_DOMAIN), + dp_opt_get_cstring(ad_opts->basic, AD_HOSTNAME), + dp_opt_get_cstring(ad_opts->id_ctx->sdap_id_ctx->opts->basic, + SDAP_KRB5_KEYTAB), + lifetime, + dp_opt_get_bool(ad_opts->basic, + AD_UPDATE_SAMBA_MACHINE_ACCOUNT_PASSWORD), + period, initial_delay, renewal_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_adcli_extra_args failed.\n"); + goto done; + } + + ret = be_ptask_create(be_ctx, be_ctx, period, initial_delay, 0, offset, + 60, 0, + ad_machine_account_password_renewal_send, + ad_machine_account_password_renewal_recv, + renewal_data, + "AD machine account password renewal", + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_SCHEDULE_FROM_LAST, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "be_ptask_create failed.\n"); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(renewal_data); + } + + return ret; +} diff --git a/src/providers/ad/ad_opts.c b/src/providers/ad/ad_opts.c new file mode 100644 index 0000000..9dd2f03 --- /dev/null +++ b/src/providers/ad/ad_opts.c @@ -0,0 +1,344 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "src/providers/data_provider.h" +#include "db/sysdb_services.h" +#include "db/sysdb_autofs.h" +#include "db/sysdb_sudo.h" +#include "db/sysdb_iphosts.h" +#include "db/sysdb_ipnetworks.h" +#include "providers/ldap/ldap_common.h" +#include "config.h" + +struct dp_option ad_basic_opts[] = { + { "ad_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_enabled_domains", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_hostname", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "ad_enable_dns_sites", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ad_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "ad_enable_gc", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ad_gpo_access_control", DP_OPT_STRING, { AD_GPO_ACCESS_MODE_DEFAULT }, NULL_STRING }, + { "ad_gpo_implicit_deny", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ad_gpo_ignore_unreadable", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ad_gpo_cache_timeout", DP_OPT_NUMBER, { .number = 5 }, NULL_NUMBER }, + { "ad_gpo_map_interactive", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_gpo_map_remote_interactive", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_gpo_map_network", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_gpo_map_batch", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_gpo_map_service", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_gpo_map_permit", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_gpo_map_deny", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_gpo_default_right", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_site", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_confd_path", DP_OPT_STRING, { KRB5_MAPPING_DIR }, NULL_STRING }, + { "ad_maximum_machine_account_password_age", DP_OPT_NUMBER, { .number = 30 }, NULL_NUMBER }, + { "ad_machine_account_password_renewal_opts", DP_OPT_STRING, { "86400:750:300" }, NULL_STRING }, + { "ad_update_samba_machine_account_password", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ad_use_ldaps", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ad_allow_remote_domain_local_groups", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + DP_OPTION_TERMINATOR +}; + +struct dp_option ad_def_ldap_opts[] = { + { "ldap_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_backup_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_authtok_type", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB }, + { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 8 }, NULL_NUMBER }, + { "ldap_tls_reqcert", DP_OPT_STRING, { "hard" }, NULL_STRING }, + { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_user_extra_attrs", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_host_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_service_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_full_refresh_interval", DP_OPT_NUMBER, { .number = 21600 }, NULL_NUMBER }, /* 360 mins */ + { "ldap_sudo_smart_refresh_interval", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, /* 15 mins */ + { "ldap_sudo_random_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, /* disabled */ + { "ldap_sudo_use_host_filter", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_sudo_hostnames", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_ip", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_include_netgroups", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_sudo_include_regexp", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_autofs_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_autofs_map_master_name", DP_OPT_STRING, { "auto.master" }, NULL_STRING }, + { "ldap_iphost_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_ipnetwork_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_schema", DP_OPT_STRING, { "ad" }, NULL_STRING }, + { "ldap_pwmodify_mode", DP_OPT_STRING, { "exop" }, NULL_STRING }, + { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER }, + { "ldap_enumeration_refresh_offset", DP_OPT_NUMBER, { .number = 30 }, NULL_NUMBER }, + { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_purge_cache_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_key", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cipher_suite", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_id_use_start_tls", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_id_mapping", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_sasl_mech", DP_OPT_STRING, { "GSS-SPNEGO" }, NULL_STRING }, + { "ldap_sasl_authid", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_minssf", DP_OPT_NUMBER, { .number = -1 }, NULL_NUMBER }, + { "ldap_sasl_maxssf", DP_OPT_NUMBER, { .number = -1 }, NULL_NUMBER }, + { "ldap_krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_krb5_init_creds", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + /* use the same parm name as the krb5 module so we set it only once */ + { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kdcinfo_lookahead", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_pwd_policy", DP_OPT_STRING, { "none" }, NULL_STRING }, + { "ldap_referrals", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "account_cache_expiration", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_dns_service_name", DP_OPT_STRING, { SSS_LDAP_SRV_NAME }, NULL_STRING }, + { "ldap_krb5_ticket_lifetime", DP_OPT_NUMBER, { .number = (24 * 60 * 60) }, NULL_NUMBER }, + { "ldap_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_netgroup_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_nesting_level", DP_OPT_NUMBER, { .number = 2 }, NULL_NUMBER }, + { "ldap_deref", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_account_expire_policy", DP_OPT_STRING, { "ad" }, NULL_STRING }, + { "ldap_access_order", DP_OPT_STRING, { "filter" }, NULL_STRING }, + { "ldap_chpass_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_backup_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_dns_service_name", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_update_last_change", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_enumeration_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + /* Do not include ldap_auth_disable_tls_never_use_in_production in the + * manpages or SSSDConfig API + */ + { "ldap_auth_disable_tls_never_use_in_production", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_page_size", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER }, + { "ldap_deref_threshold", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }, + { "ldap_ignore_unreadable_references", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_sasl_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_connection_expire_timeout", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, + { "ldap_connection_expire_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_connection_idle_timeout", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, + { "ldap_disable_paging", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_idmap_range_min", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, + { "ldap_idmap_range_max", DP_OPT_NUMBER, { .number = 2000200000LL }, NULL_NUMBER }, + { "ldap_idmap_range_size", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, + { "ldap_idmap_autorid_compat", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_idmap_default_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_idmap_default_domain_sid", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_idmap_helper_table_size", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }, + { "ldap_use_tokengroups", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE}, + { "ldap_rfc2307_fallback_to_local_users", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_disable_range_retrieval", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_min_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_max_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_pwdlockout_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "wildcard_limit", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER}, + { "ldap_library_debug_level", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + DP_OPTION_TERMINATOR +}; + +struct dp_option ad_def_krb5_opts[] = { + { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_ccachedir", DP_OPT_STRING, { DEFAULT_CCACHE_DIR }, NULL_STRING }, + { "krb5_ccname_template", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_validate", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_store_password_if_offline", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_renewable_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_renew_interval", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_use_fast", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_fast_principal", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_fast_use_anonymous_pkinit", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_use_enterprise_principal", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kdcinfo_lookahead", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_map_user", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_use_subdomain_realm", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + DP_OPTION_TERMINATOR +}; + +struct sdap_attr_map ad_2008r2_attr_map[] = { + { "ldap_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", SDAP_AD_LAST_USN, SYSDB_HIGH_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ad_2008r2_user_map[] = { + { "ldap_user_object_class", "user", SYSDB_USER_CLASS, NULL }, + { "ldap_user_name", "sAMAccountName", SYSDB_NAME, NULL }, + { "ldap_user_pwd", "unixUserPassword", SYSDB_PWD, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "unixHomeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_user_principal", "userPrincipalName", SYSDB_UPN, NULL }, + { "ldap_user_fullname", "name", SYSDB_FULLNAME, NULL }, + { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL }, + { "ldap_user_uuid", "objectGUID", SYSDB_UUID, NULL }, + { "ldap_user_objectsid", "objectSID", SYSDB_SID, NULL }, + { "ldap_user_primary_group", "primaryGroupID", SYSDB_PRIMARY_GROUP, NULL }, + { "ldap_user_modify_timestamp", "whenChanged", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL }, + { "ldap_user_shadow_last_change", NULL, SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", NULL, SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", NULL, SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", NULL, SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", NULL, SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", NULL, SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", NULL, SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", NULL, SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", NULL, SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", NULL, SYSDB_PWD_ATTRIBUTE, NULL }, + { "ldap_user_authorized_service", NULL, SYSDB_AUTHORIZED_SERVICE, NULL }, + { "ldap_user_ad_account_expires", "accountExpires", SYSDB_AD_ACCOUNT_EXPIRES, NULL}, + { "ldap_user_ad_user_account_control", "userAccountControl", SYSDB_AD_USER_ACCOUNT_CONTROL, NULL}, + { "ldap_ns_account_lock", NULL, SYSDB_NS_ACCOUNT_LOCK, NULL}, + { "ldap_user_authorized_host", NULL, SYSDB_AUTHORIZED_HOST, NULL }, + { "ldap_user_authorized_rhost", NULL, SYSDB_AUTHORIZED_RHOST, NULL }, + { "ldap_user_nds_login_disabled", NULL, SYSDB_NDS_LOGIN_DISABLED, NULL }, + { "ldap_user_nds_login_expiration_time", NULL, SYSDB_NDS_LOGIN_EXPIRATION_TIME, NULL }, + { "ldap_user_nds_login_allowed_time_map", NULL, SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP, NULL }, + { "ldap_user_ssh_public_key", NULL, SYSDB_SSH_PUBKEY, NULL }, + { "ldap_user_auth_type", NULL, SYSDB_AUTH_TYPE, NULL }, + { "ldap_user_certificate", "userCertificate;binary", SYSDB_USER_CERT, NULL }, + { "ldap_user_email", "mail", SYSDB_USER_EMAIL, NULL }, + { "ldap_user_passkey", "altSecurityIdentities", SYSDB_USER_PASSKEY, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ad_2008r2_group_map[] = { + { "ldap_group_object_class", "group", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_object_class_alt", NULL, SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_name", "sAMAccountName", SYSDB_NAME, NULL }, + { "ldap_group_pwd", NULL, SYSDB_PWD, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_group_member", "member", SYSDB_MEMBER, NULL }, + { "ldap_group_uuid", "objectGUID", SYSDB_UUID, NULL }, + { "ldap_group_objectsid", "objectSID", SYSDB_SID, NULL }, + { "ldap_group_modify_timestamp", "whenChanged", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_group_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL }, + { "ldap_group_type", "groupType", SYSDB_GROUP_TYPE, NULL }, + { "ldap_group_external_member", NULL, SYSDB_EXTERNAL_MEMBER, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ad_netgroup_map[] = { + { "ldap_netgroup_object_class", "nisNetgroup", SYSDB_NETGROUP_CLASS, NULL }, + { "ldap_netgroup_name", "cn", SYSDB_NAME, NULL }, + { "ldap_netgroup_member", "memberNisNetgroup", SYSDB_ORIG_NETGROUP_MEMBER, NULL }, + { "ldap_netgroup_triple", "nisNetgroupTriple", SYSDB_NETGROUP_TRIPLE, NULL }, + { "ldap_netgroup_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ad_service_map[] = { + { "ldap_service_object_class", "ipService", SYSDB_SVC_CLASS, NULL }, + { "ldap_service_name", "cn", SYSDB_NAME, NULL }, + { "ldap_service_port", "ipServicePort", SYSDB_SVC_PORT, NULL }, + { "ldap_service_proto", "ipServiceProtocol", SYSDB_SVC_PROTO, NULL }, + { "ldap_service_entry_usn", NULL, SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ad_autofs_mobject_map[] = { + { "ldap_autofs_map_object_class", "nisMap", SYSDB_AUTOFS_MAP_OC, NULL }, + { "ldap_autofs_map_name", "nisMapName", SYSDB_AUTOFS_MAP_NAME, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ad_autofs_entry_map[] = { + { "ldap_autofs_entry_object_class", "nisObject", SYSDB_AUTOFS_ENTRY_OC, NULL }, + { "ldap_autofs_entry_key", "cn", SYSDB_AUTOFS_ENTRY_KEY, NULL }, + { "ldap_autofs_entry_value", "nisMapEntry", SYSDB_AUTOFS_ENTRY_VALUE, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ad_iphost_map[] = { + { "ldap_iphost_object_class", "device", SYSDB_IP_HOST_CLASS, NULL }, + { "ldap_iphost_name", "cn", SYSDB_NAME, NULL }, + { "ldap_iphost_number", "ipHostNumber", SYSDB_IP_HOST_ATTR_ADDRESS, NULL }, + { "ldap_iphost_entry_usn", NULL, SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ad_ipnetwork_map[] = { + { "ldap_ipnetwork_object_class", "ipNetwork", SYSDB_IP_NETWORK_CLASS, NULL }, + { "ldap_ipnetwork_name", "cn", SYSDB_NAME, NULL }, + { "ldap_ipnetwork_number", "ipNetworkNumber", SYSDB_IP_NETWORK_ATTR_NUMBER, NULL }, + { "ldap_ipnetwork_entry_usn", NULL, SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct dp_option ad_dyndns_opts[] = { + { "dyndns_update", DP_OPT_BOOL, BOOL_TRUE, BOOL_FALSE }, + { "dyndns_update_per_family", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "dyndns_refresh_interval", DP_OPT_NUMBER, { .number = 86400 }, NULL_NUMBER }, + { "dyndns_refresh_interval_offset", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER }, + { "dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "dyndns_ttl", DP_OPT_NUMBER, { .number = 3600 }, NULL_NUMBER }, + { "dyndns_update_ptr", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "dyndns_force_tcp", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_auth", DP_OPT_STRING, { "gss-tsig" }, NULL_STRING }, + { "dyndns_auth_ptr", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "dyndns_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + DP_OPTION_TERMINATOR +}; + +struct sdap_attr_map ad_sudorule_map[] = { + { "ldap_sudorule_object_class", "sudoRole", SYSDB_SUDO_CACHE_OC, NULL }, + { "ldap_sudorule_object_class_attr", "objectCategory", SYSDB_OBJECTCATEGORY, NULL }, + { "ldap_sudorule_name", "cn", SYSDB_SUDO_CACHE_AT_CN, NULL }, + { "ldap_sudorule_command", "sudoCommand", SYSDB_SUDO_CACHE_AT_COMMAND, NULL }, + { "ldap_sudorule_host", "sudoHost", SYSDB_SUDO_CACHE_AT_HOST, NULL }, + { "ldap_sudorule_user", "sudoUser", SYSDB_SUDO_CACHE_AT_USER, NULL }, + { "ldap_sudorule_option", "sudoOption", SYSDB_SUDO_CACHE_AT_OPTION, NULL }, + { "ldap_sudorule_runas", "sudoRunAs", SYSDB_SUDO_CACHE_AT_RUNAS, NULL }, + { "ldap_sudorule_runasuser", "sudoRunAsUser", SYSDB_SUDO_CACHE_AT_RUNASUSER, NULL }, + { "ldap_sudorule_runasgroup", "sudoRunAsGroup", SYSDB_SUDO_CACHE_AT_RUNASGROUP, NULL }, + { "ldap_sudorule_notbefore", "sudoNotBefore", SYSDB_SUDO_CACHE_AT_NOTBEFORE, NULL }, + { "ldap_sudorule_notafter", "sudoNotAfter", SYSDB_SUDO_CACHE_AT_NOTAFTER, NULL }, + { "ldap_sudorule_order", "sudoOrder", SYSDB_SUDO_CACHE_AT_ORDER, NULL }, + { "ldap_sudorule_entry_usn", NULL, SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; diff --git a/src/providers/ad/ad_opts.h b/src/providers/ad/ad_opts.h new file mode 100644 index 0000000..799be13 --- /dev/null +++ b/src/providers/ad/ad_opts.h @@ -0,0 +1,57 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef AD_OPTS_H_ +#define AD_OPTS_H_ + +#include "src/providers/data_provider.h" +#include "providers/ldap/ldap_common.h" + +extern struct dp_option ad_basic_opts[]; + +extern struct dp_option ad_def_ldap_opts[]; + +extern struct dp_option ad_def_krb5_opts[]; + +extern struct sdap_attr_map ad_2008r2_attr_map[]; + +extern struct sdap_attr_map ad_2008r2_user_map[]; + +extern struct sdap_attr_map ad_2008r2_group_map[]; + +extern struct sdap_attr_map ad_netgroup_map[]; + +extern struct sdap_attr_map ad_service_map[]; + +extern struct sdap_attr_map ad_autofs_mobject_map[]; + +extern struct sdap_attr_map ad_autofs_entry_map[]; + +extern struct sdap_attr_map ad_iphost_map[]; + +extern struct sdap_attr_map ad_ipnetwork_map[]; + +extern struct dp_option ad_dyndns_opts[]; + +extern struct sdap_attr_map ad_sudorule_map[]; + +#endif /* AD_OPTS_H_ */ diff --git a/src/providers/ad/ad_pac.c b/src/providers/ad/ad_pac.c new file mode 100644 index 0000000..fd15c63 --- /dev/null +++ b/src/providers/ad/ad_pac.c @@ -0,0 +1,750 @@ +/* + SSSD + + Authors: + Sumit Bose + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/ad/ad_pac.h" +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_id.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_async_ad.h" + +static errno_t find_user_entry(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + struct dp_id_data *ar, + struct ldb_message **_msg) +{ + const char *user_attrs[] = { SYSDB_NAME, SYSDB_OBJECTCATEGORY, + SYSDB_PAC_BLOB, SYSDB_PAC_BLOB_EXPIRE, + NULL }; + struct ldb_message *msg; + struct ldb_result *res; + int ret; + TALLOC_CTX *tmp_ctx = NULL; + + if (dom == NULL || ar == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing arguments.\n"); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (ar->extra_value && strcmp(ar->extra_value, EXTRA_NAME_IS_UPN) == 0) { + ret = sysdb_search_user_by_upn(tmp_ctx, dom, false, ar->filter_value, + user_attrs, &msg); + } else { + switch (ar->filter_type) { + case BE_FILTER_SECID: + ret = sysdb_search_user_by_sid_str(tmp_ctx, dom, ar->filter_value, + user_attrs, &msg); + break; + case BE_FILTER_UUID: + ret = sysdb_search_object_by_uuid(tmp_ctx, dom, ar->filter_value, + user_attrs, &res); + + if (ret == EOK) { + if (res->count == 1) { + msg = res->msgs[0]; + } else { + talloc_free(res); + DEBUG(SSSDBG_CRIT_FAILURE, + "Search by UUID returned multiple results.\n"); + ret = EINVAL; + goto done; + } + } + break; + case BE_FILTER_NAME: + ret = sysdb_search_user_by_name(tmp_ctx, dom, ar->filter_value, + user_attrs, &msg); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unsupported filter type [%d].\n", + ar->filter_type); + ret = EINVAL; + goto done; + } + } + + if (ret != EOK) { + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "No user found with filter [%s].\n", + ar->filter_value); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Looking up user in cache with filter [%s] failed.\n", + ar->filter_value); + } + goto done; + } + + *_msg = talloc_steal(mem_ctx, msg); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t check_if_pac_is_available(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct dp_id_data *ar, + struct ldb_message **_msg) +{ + struct ldb_message *msg; + struct ldb_message_element *el; + uint64_t pac_expires; + time_t now; + int ret; + + ret = find_user_entry(mem_ctx, dom, ar, &msg); + if (ret != EOK) { + if (ret == ENOENT) { + DEBUG(SSSDBG_FUNC_DATA, "find_user_entry didn't find user entry.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, "find_user_entry failed.\n"); + } + return ret; + } + + el = ldb_msg_find_element(msg, SYSDB_PAC_BLOB); + if (el == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No PAC available.\n"); + talloc_free(msg); + return ENOENT; + } + + pac_expires = ldb_msg_find_attr_as_uint64(msg, SYSDB_PAC_BLOB_EXPIRE, 0); + now = time(NULL); + if (pac_expires < now) { + DEBUG(SSSDBG_TRACE_FUNC, "PAC available but too old.\n"); + talloc_free(msg); + return ENOENT; + } + + if (_msg != NULL) { + *_msg = msg; + } + + return EOK; +} + +static errno_t +add_sids_from_rid_array_to_hash_table(struct dom_sid *dom_sid, + struct samr_RidWithAttributeArray *groups, + struct sss_idmap_ctx *idmap_ctx, + hash_table_t *sid_table) +{ + enum idmap_error_code err; + char *dom_sid_str = NULL; + size_t dom_sid_str_len; + char *sid_str = NULL; + char *rid_start; + hash_key_t key; + hash_value_t value; + int ret; + size_t c; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + key.type = HASH_KEY_STRING; + value.type = HASH_VALUE_ULONG; + + err = sss_idmap_smb_sid_to_sid(idmap_ctx, dom_sid, &dom_sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "sss_idmap_smb_sid_to_sid failed.\n"); + ret = EFAULT; + goto done; + } + + dom_sid_str_len = strlen(dom_sid_str); + sid_str = talloc_zero_size(tmp_ctx, dom_sid_str_len + 12); + if (sid_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n"); + ret = ENOMEM; + goto done; + } + rid_start = sid_str + dom_sid_str_len; + + memcpy(sid_str, dom_sid_str, dom_sid_str_len); + + for (c = 0; c < groups->count; c++) { + memset(rid_start, '\0', 12); + ret = snprintf(rid_start, 12, "-%lu", + (unsigned long) groups->rids[c].rid); + if (ret < 0 || ret > 12) { + DEBUG(SSSDBG_OP_FAILURE, "snprintf failed.\n"); + ret = EIO; + goto done; + } + + key.str = sid_str; + value.ul = 0; + + ret = hash_enter(sid_table, &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed [%d][%s].\n", + ret, hash_error_string(ret)); + ret = EIO; + goto done; + } + + } + + ret = EOK; + +done: + sss_idmap_free_sid(idmap_ctx, dom_sid_str); + talloc_free(tmp_ctx); + + return ret; +} + +struct resource_groups { + struct dom_sid2 *domain_sid; + struct samr_RidWithAttributeArray groups; +}; + +errno_t ad_get_sids_from_pac(TALLOC_CTX *mem_ctx, + struct sss_idmap_ctx *idmap_ctx, + struct PAC_LOGON_INFO *logon_info, + char **_user_sid_str, + char **_primary_group_sid_str, + size_t *_num_sids, + char *** _sid_list) +{ + int ret; + size_t s; + struct netr_SamInfo3 *info3; + struct resource_groups resource_groups = { 0 }; + char *sid_str = NULL; + char *msid_str = NULL; + char *user_dom_sid_str = NULL; + size_t user_dom_sid_str_len; + enum idmap_error_code err; + hash_table_t *sid_table = NULL; + hash_key_t key; + hash_value_t value; + char *rid_start; + char *user_sid_str = NULL; + char *primary_group_sid_str = NULL; + size_t c; + size_t num_sids = 0; + char **sid_list = NULL; + struct hash_iter_context_t *iter = NULL; + hash_entry_t *entry; + TALLOC_CTX *tmp_ctx; + + if (idmap_ctx == NULL || logon_info == NULL + || _num_sids == NULL || _sid_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing parameter.\n"); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + info3 = &logon_info->info3; +#ifdef HAVE_STRUCT_PAC_LOGON_INFO_RESOURCE_GROUPS + resource_groups.domain_sid = logon_info->resource_groups.domain_sid; + resource_groups.groups.count = logon_info->resource_groups.groups.count; + resource_groups.groups.rids = logon_info->resource_groups.groups.rids; +#endif + + ret = sss_hash_create(tmp_ctx, + info3->sidcount + info3->base.groups.count + 2 + + resource_groups.groups.count, + &sid_table); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n"); + goto done; + } + + key.type = HASH_KEY_STRING; + value.type = HASH_VALUE_ULONG; + + err = sss_idmap_smb_sid_to_sid(idmap_ctx, info3->base.domain_sid, + &user_dom_sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "sss_idmap_smb_sid_to_sid failed.\n"); + ret = EFAULT; + goto done; + } + + user_dom_sid_str_len = strlen(user_dom_sid_str); + sid_str = talloc_zero_size(tmp_ctx, user_dom_sid_str_len + 12); + if (sid_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n"); + ret = ENOMEM; + goto done; + } + rid_start = sid_str + user_dom_sid_str_len; + + memcpy(sid_str, user_dom_sid_str, user_dom_sid_str_len); + + memset(rid_start, '\0', 12); + ret = snprintf(rid_start, 12, "-%lu", + (unsigned long) info3->base.rid); + if (ret < 0 || ret > 12) { + DEBUG(SSSDBG_OP_FAILURE, "snprintf failed.\n"); + ret = EIO; + goto done; + } + + user_sid_str = talloc_strdup(tmp_ctx, sid_str); + if (user_sid_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + key.str = sid_str; + value.ul = 0; + + memset(rid_start, '\0', 12); + ret = snprintf(rid_start, 12, "-%lu", + (unsigned long) info3->base.primary_gid); + if (ret < 0 || ret > 12) { + DEBUG(SSSDBG_OP_FAILURE, "snprintf failed.\n"); + ret = EIO; + goto done; + } + + primary_group_sid_str = talloc_strdup(tmp_ctx, sid_str); + if (primary_group_sid_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + key.str = sid_str; + value.ul = 0; + + ret = hash_enter(sid_table, &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed [%d][%s].\n", + ret, hash_error_string(ret)); + ret = EIO; + goto done; + } + + ret = add_sids_from_rid_array_to_hash_table(info3->base.domain_sid, + &info3->base.groups, + idmap_ctx, sid_table); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "add_sids_from_rid_array_to_hash_table failed.\n"); + goto done; + } + + for(s = 0; s < info3->sidcount; s++) { + err = sss_idmap_smb_sid_to_sid(idmap_ctx, info3->sids[s].sid, + &msid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "sss_idmap_smb_sid_to_sid failed.\n"); + ret = EFAULT; + goto done; + } + + key.str = msid_str; + value.ul = 0; + + ret = hash_enter(sid_table, &key, &value); + sss_idmap_free_sid(idmap_ctx, msid_str); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed [%d][%s].\n", + ret, hash_error_string(ret)); + ret = EIO; + goto done; + } + } + + if (resource_groups.domain_sid != NULL) { + ret = add_sids_from_rid_array_to_hash_table(resource_groups.domain_sid, + &resource_groups.groups, + idmap_ctx, sid_table); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "add_sids_from_rid_array_to_hash_table failed.\n"); + goto done; + } + } + + num_sids = hash_count(sid_table); + sid_list = talloc_array(tmp_ctx, char *, num_sids); + if (sid_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + + iter = new_hash_iter_context(sid_table); + if (iter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "new_hash_iter_context failed.\n"); + ret = EINVAL; + goto done; + } + + c = 0; + while ((entry = iter->next(iter)) != NULL) { + sid_list[c] = talloc_strdup(sid_list, entry->key.str); + if (sid_list[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + } + + ret = EOK; + +done: + sss_idmap_free_sid(idmap_ctx, user_dom_sid_str); + hash_destroy(sid_table); + + if (ret == EOK) { + *_sid_list = talloc_steal(mem_ctx, sid_list); + *_user_sid_str = talloc_steal(mem_ctx, user_sid_str); + *_num_sids = num_sids; + *_primary_group_sid_str = talloc_steal(mem_ctx, primary_group_sid_str); + } + + talloc_free(tmp_ctx); + + return ret; +} + +errno_t ad_get_pac_data_from_user_entry(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + struct sss_idmap_ctx *idmap_ctx, + char **_username, + char **user_sid, + char **primary_group_sid, + size_t *num_sids, + char ***group_sids) +{ + int ret; + struct ldb_message_element *el; + struct PAC_LOGON_INFO *logon_info = NULL; + const char *dummy; + TALLOC_CTX *tmp_ctx = NULL; + char *username; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + el = ldb_msg_find_element(msg, SYSDB_PAC_BLOB); + if (el == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing PAC blob.\n"); + ret = EINVAL; + goto done; + } + + if (el->num_values != 1) { + DEBUG(SSSDBG_OP_FAILURE, "Expected only one PAC blob."); + ret = EINVAL; + goto done; + } + + /* PAC was already checked when it was saved in the cache, so no + * additional check is needed here. */ + ret = ad_get_data_from_pac(tmp_ctx, 0, el->values[0].data, + el->values[0].length, + &logon_info, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_data_from_pac failed.\n"); + goto done; + } + + dummy = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (dummy == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing user name in cache entry.\n"); + ret = EINVAL; + goto done; + } + + username = talloc_strdup(tmp_ctx, dummy); + if (username == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = ad_get_sids_from_pac(mem_ctx, idmap_ctx, logon_info, + user_sid, primary_group_sid, + num_sids, group_sids); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_sids_from_pac failed.\n"); + goto done; + } + + *_username = talloc_steal(mem_ctx, username); + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +struct ad_handle_pac_initgr_state { + struct dp_id_data *ar; + const char *err; + int dp_error; + int sdap_ret; + struct sdap_options *opts; + + size_t num_missing_sids; + char **missing_sids; + size_t num_cached_groups; + char **cached_groups; + char *username; + struct sss_domain_info *user_dom; +}; + +static void ad_handle_pac_initgr_lookup_sids_done(struct tevent_req *subreq); + +struct tevent_req *ad_handle_pac_initgr_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct dp_id_data *ar, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + bool noexist_delete, + struct ldb_message *msg) +{ + int ret; + struct ad_handle_pac_initgr_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + char *user_sid; + char *primary_group_sid; + size_t num_sids; + char **group_sids; + bool use_id_mapping; + + req = tevent_req_create(mem_ctx, &state, + struct ad_handle_pac_initgr_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + state->user_dom = sdom->dom; + state->opts = id_ctx->opts; + + /* The following variables are currently unused because no sub-request + * returns any of them. But they are needed to allow the same signature as + * sdap_handle_acct_req_recv() from the alternative group-membership + * lookup path. */ + state->err = NULL; + state->dp_error = DP_ERR_OK; + state->sdap_ret = EOK; + + ret = ad_get_pac_data_from_user_entry(state, msg, + id_ctx->opts->idmap_ctx->map, + &state->username, + &user_sid, &primary_group_sid, + &num_sids, &group_sids); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_get_pac_data_from_user_entry failed.\n"); + goto done; + } + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + id_ctx->opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + if (use_id_mapping + && sdom->dom->ignore_group_members == false) { + /* In contrast to the tokenGroups based group-membership lookup the + * PAC based approach can be used for sub-domains with id-mapping as + * well because the PAC will only contain groups which are valid in + * the target domain, i.e. it will not contain domain-local groups for + * domains other than the user domain. This means the groups must not + * be looked up immediately to determine if they are domain-local or + * not. + * + * Additionally, as a temporary workaround until + * https://fedorahosted.org/sssd/ticket/2522 is fixed, we also fetch + * the group object if group members are ignored to avoid having to + * transfer and retain members when the fake tokengroups object + * without name is replaced by the full group object. + */ + + DEBUG(SSSDBG_TRACE_ALL, "Running PAC processing with id-mapping.\n"); + + ret = sdap_ad_save_group_membership_with_idmapping(state->username, + state->opts, + sdom->dom, + id_ctx->opts->idmap_ctx, + num_sids, group_sids); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_save_group_membership_with_idmapping failed.\n"); + } + + /* this path only includes cache operation, so we can finish the + * request immediately */ + goto done; + } else { + + DEBUG(SSSDBG_TRACE_ALL, "Running PAC processing with external IDs.\n"); + + ret = sdap_ad_tokengroups_get_posix_members(state, sdom->dom, + num_sids, group_sids, + &state->num_missing_sids, + &state->missing_sids, + &state->num_cached_groups, + &state->cached_groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_tokengroups_get_posix_members failed.\n"); + goto done; + } + + /* download missing SIDs */ + subreq = sdap_ad_resolve_sids_send(state, be_ctx->ev, id_ctx, + conn, + id_ctx->opts, sdom->dom, + state->missing_sids); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_ad_resolve_sids_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ad_handle_pac_initgr_lookup_sids_done, + req); + + } + + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, be_ctx->ev); + + return req; +} + +static void ad_handle_pac_initgr_lookup_sids_done(struct tevent_req *subreq) +{ + struct ad_handle_pac_initgr_state *state; + struct tevent_req *req = NULL; + errno_t ret; + char **cached_groups; + size_t num_cached_groups; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_handle_pac_initgr_state); + + ret = sdap_ad_resolve_sids_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to resolve missing SIDs " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + ret = sdap_ad_tokengroups_get_posix_members(state, state->user_dom, + state->num_missing_sids, + state->missing_sids, + NULL, NULL, + &num_cached_groups, + &cached_groups); + if (ret != EOK){ + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_ad_tokengroups_get_posix_members failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + state->cached_groups = concatenate_string_array(state, + state->cached_groups, + state->num_cached_groups, + cached_groups, + num_cached_groups); + if (state->cached_groups == NULL) { + ret = ENOMEM; + goto done; + } + + /* update membership of existing groups */ + ret = sdap_ad_tokengroups_update_members(state->username, + state->user_dom->sysdb, + state->user_dom, + state->cached_groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t ad_handle_pac_initgr_recv(struct tevent_req *req, + int *_dp_error, const char **_err, + int *sdap_ret) +{ + struct ad_handle_pac_initgr_state *state; + + state = tevent_req_data(req, struct ad_handle_pac_initgr_state); + + if (_dp_error) { + *_dp_error = state->dp_error; + } + + if (_err) { + *_err = state->err; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ad/ad_pac.h b/src/providers/ad/ad_pac.h new file mode 100644 index 0000000..405d1c3 --- /dev/null +++ b/src/providers/ad/ad_pac.h @@ -0,0 +1,87 @@ +/* + SSSD + + Authors: + Sumit Bose + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef AD_PAC_H_ +#define AD_PAC_H_ + +#include +#include +/* ldb_val is defined as datablob in the Samba header files data_blob.h which + * is included via ndr.h -> samba_util.h -> data_blob.h. + * To allow proper type checking we have to make sure to keep the original + * definition from ldb.h */ +#ifdef ldb_val +#error Please make sure to include ad_pac.h before ldb.h +#endif +#include +#include +#include +#undef ldb_val + +#include "util/util.h" +#include "providers/ldap/ldap_common.h" + +errno_t check_if_pac_is_available(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct dp_id_data *ar, + struct ldb_message **_msg); + +errno_t ad_get_data_from_pac(TALLOC_CTX *mem_ctx, const uint32_t pac_check_opts, + uint8_t *pac_blob, size_t pac_len, + struct PAC_LOGON_INFO **_logon_info, + struct PAC_UPN_DNS_INFO **_upn_dns_info); + +errno_t ad_get_sids_from_pac(TALLOC_CTX *mem_ctx, + struct sss_idmap_ctx *idmap_ctx, + struct PAC_LOGON_INFO *logon_info, + char **_user_sid_str, + char **_primary_group_sid_str, + size_t *_num_sids, + char *** _sid_list); + +errno_t ad_get_pac_data_from_user_entry(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + struct sss_idmap_ctx *idmap_ctx, + char **username, + char **user_sid, + char **primary_group_sid, + size_t *num_sids, + char ***group_sids); + +struct tevent_req *ad_handle_pac_initgr_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct dp_id_data *ar, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + bool noexist_delete, + struct ldb_message *msg); + +errno_t ad_handle_pac_initgr_recv(struct tevent_req *req, + int *_dp_error, const char **_err, + int *sdap_ret); + +errno_t check_upn_and_sid_from_user_and_pac(struct ldb_message *msg, + struct sss_idmap_ctx *ctx, + struct PAC_UPN_DNS_INFO *upn_dns_info, + const uint32_t pac_check_opts); +#endif /* AD_PAC_H_ */ diff --git a/src/providers/ad/ad_pac_common.c b/src/providers/ad/ad_pac_common.c new file mode 100644 index 0000000..fcb54cd --- /dev/null +++ b/src/providers/ad/ad_pac_common.c @@ -0,0 +1,465 @@ +/* + SSSD + + Authors: + Sumit Bose + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + + +#include "providers/ad/ad_pac.h" +#include "util/util.h" + +#ifdef HAVE_STRUCT_PAC_UPN_DNS_INFO_EX +static bool +compare_sid_with_dom_sid_and_rid(const struct dom_sid *sid, + const struct dom_sid *dom, + uint32_t rid) +{ + size_t c; + + if (sid == NULL || dom == NULL || rid == 0) { + return false; + } + + if (sid->sid_rev_num != dom->sid_rev_num) { + return false; + } + + for (c = 0; c < sizeof(sid->id_auth); c++) { + if (sid->id_auth[c] != dom->id_auth[c]) { + return false; + } + } + + if (sid->num_auths != dom->num_auths + 1) { + return false; + } + + for (c = 0; c < sid->num_auths; c++) { + if (c == dom->num_auths) { + if (sid->sub_auths[c] != rid) { + return false; + } + } else { + if (sid->sub_auths[c] != dom->sub_auths[c]) { + return false; + } + } + } + + return true; +} +#endif + +static errno_t +check_logon_info_upn_dns_info(const struct PAC_LOGON_INFO *logon_info, + const struct PAC_UPN_DNS_INFO *upn_dns_info, + const uint32_t pac_check_opts) +{ + const char *delim; + + if (logon_info == NULL) { + return ERR_CHECK_PAC_FAILED; + } + + if (logon_info->info3.base.account_name.string == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "Missing account name in PAC.\n"); + return ERR_CHECK_PAC_FAILED; + } + + /* If upn_dns_info is not available we have nothing to check. */ + if (upn_dns_info == NULL) { + if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_PRESENT) { + DEBUG(SSSDBG_FUNC_DATA, + "UPN_DNS_INFO pac buffer required, but missing.\n"); + return ERR_CHECK_PAC_FAILED; + } else { + DEBUG(SSSDBG_TRACE_ALL, + "upn_dns_info buffer not present, nothing to check.\n"); + return EOK; + } + } + + /* upn_dns_info has no information which is present in logon_info, so + * nothing to check. */ + if (upn_dns_info->flags == 0) { + DEBUG(SSSDBG_TRACE_ALL, + "upn_dns_info buffer has no extra data to check.\n"); + return EOK; + } + + /* The user object does not have userPrincipalName set explicitly and the + * upn_name is constructed from the user name (sAMAccountName) and the DNS + * domain name. Case-insensitive comparison will be used because AD handles + * names case-insensitive. */ + if ((upn_dns_info->flags & PAC_UPN_DNS_FLAG_CONSTRUCTED) + && (pac_check_opts & CHECK_PAC_CHECK_UPN)) { + if (upn_dns_info->upn_name == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "Missing UPN in PAC.\n"); + return ERR_CHECK_PAC_FAILED; + } + + if (upn_dns_info->dns_domain_name == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "Missing DNS domain name in PAC.\n"); + return ERR_CHECK_PAC_FAILED; + } + + delim = strrchr(upn_dns_info->upn_name, '@'); + if (delim == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "Missing '@' in UPN [%s] from PAC.\n", + upn_dns_info->upn_name); + return ERR_CHECK_PAC_FAILED; + } + + if (strcasecmp(delim+1, upn_dns_info->dns_domain_name) != 0) { + DEBUG(SSSDBG_FUNC_DATA, "Domain part of UPN [%s] and " + "DNS domain name [%s] do not match.\n", + upn_dns_info->upn_name, + upn_dns_info->dns_domain_name); + return ERR_CHECK_PAC_FAILED; + } + + if (strncasecmp(logon_info->info3.base.account_name.string, + upn_dns_info->upn_name, + (size_t) (delim - upn_dns_info->upn_name)) != 0) { + DEBUG(SSSDBG_FUNC_DATA, + "Name part of UPN [%s] and account name [%s] " + "do not match.\n", upn_dns_info->upn_name, + logon_info->info3.base.account_name.string); + return ERR_CHECK_PAC_FAILED; + } + } + + /* The upn_dns_info is extended with the sAMAccountName and the SID of the + * object. */ +#ifdef HAVE_STRUCT_PAC_UPN_DNS_INFO_EX + if ((upn_dns_info->flags & PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID) + && (pac_check_opts & CHECK_PAC_CHECK_UPN_DNS_INFO_EX)) { + if (strcasecmp(logon_info->info3.base.account_name.string, + upn_dns_info->ex.sam_name_and_sid.samaccountname) != 0) { + DEBUG(SSSDBG_FUNC_DATA, "Account name in LOGON_INFO [%s] and " + "UPN_DNS_INFO [%s] PAC buffers do not match.\n", + logon_info->info3.base.account_name.string, + upn_dns_info->ex.sam_name_and_sid.samaccountname); + return ERR_CHECK_PAC_FAILED; + } + + if (!compare_sid_with_dom_sid_and_rid( + upn_dns_info->ex.sam_name_and_sid.objectsid, + logon_info->info3.base.domain_sid, + logon_info->info3.base.rid)) { + DEBUG(SSSDBG_FUNC_DATA, "SID from UPN_DNS_INFO PAC buffer " + "do not match data from LOGON_INFO buffer.\n"); + return ERR_CHECK_PAC_FAILED; + } + } +#else + DEBUG(SSSDBG_TRACE_ALL, + "This SSSD build does not support the sam_name_and_sid extension of " + "the UPN_DNS_INFO pac buffer.\n"); + if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT) { + DEBUG(SSSDBG_FUNC_DATA, + "UPN_DNS_INFO pac buffer extension required, but missing.\n"); + return ERR_CHECK_PAC_FAILED; + } +#endif + + DEBUG(SSSDBG_TRACE_ALL, "PAC consistency check successful.\n"); + return EOK; +} + +errno_t check_upn_and_sid_from_user_and_pac(struct ldb_message *msg, + struct sss_idmap_ctx *idmap_ctx, + struct PAC_UPN_DNS_INFO *upn_dns_info, + const uint32_t pac_check_opts) +{ + const char *user_data; + char *pac_ext_sid_str; + enum idmap_error_code err; + int cmp_ret; + + if (upn_dns_info == NULL || upn_dns_info->upn_name == NULL) { + if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_PRESENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing UPN in PAC.\n"); + return ERR_CHECK_PAC_FAILED; + } else { + DEBUG(SSSDBG_TRACE_ALL, + "Missing UPN in PAC, but check is not required.\n"); + return EOK; + } + } else { + user_data = ldb_msg_find_attr_as_string(msg, SYSDB_UPN, NULL); + + /* If the user object doesn't have a UPN we would expect that the UPN in + * the PAC_UPN_DNS_INFO buffer is generated and + * PAC_UPN_DNS_FLAG_CONSTRUCTED is set. However, there might still be + * configurations like 'ldap_user_principal = noSuchAttr' around. So we + * just check and log a message. */ + if (user_data == NULL + && !(upn_dns_info->flags & PAC_UPN_DNS_FLAG_CONSTRUCTED)) { + DEBUG(SSSDBG_MINOR_FAILURE, "User object does not have a UPN but PAC " + "says otherwise, maybe ldap_user_principal option is set.\n"); + if (pac_check_opts & CHECK_PAC_CHECK_UPN) { + if (pac_check_opts & CHECK_PAC_CHECK_UPN_ALLOW_MISSING) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "UPN is missing but PAC UPN check required, " + "PAC validation failed. However, " + "'check_upn_allow_missing' is set and the error is " + "ignored. To make this message go away please check " + "why the UPN is not read from the server. In FreeIPA " + "environments 'ldap_user_principal' is most probably " + "set to a non-existing attribute name to avoid " + "issues with enterprise principals. This is not " + "needed anymore with recent versions of FreeIPA.\n"); + sss_log(SSS_LOG_CRIT, "PAC validation issue, please check " + "sssd_pac.log for details"); + return EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "UPN is missing but PAC UPN check required, " + "PAC validation failed.\n"); + return ERR_CHECK_PAC_FAILED; + } + } + } + + if (user_data != NULL) { + if (strcasecmp(user_data, upn_dns_info->upn_name) != 0) { + if (pac_check_opts & CHECK_PAC_CHECK_UPN) { + DEBUG(SSSDBG_CRIT_FAILURE, "UPN of user entry [%s] and " + "PAC [%s] do not match.\n", + user_data, + upn_dns_info->upn_name); + return ERR_CHECK_PAC_FAILED; + } else { + DEBUG(SSSDBG_IMPORTANT_INFO, "UPN of user entry [%s] and " + "PAC [%s] do not match, " + "ignored.\n", user_data, + upn_dns_info->upn_name); + return EOK; + } + } + } + + DEBUG(SSSDBG_TRACE_ALL, "PAC UPN check successful.\n"); + } + +#ifdef HAVE_STRUCT_PAC_UPN_DNS_INFO_EX + if (upn_dns_info == NULL + || !(upn_dns_info->flags & PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID) ) { + if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing SID in PAC extension.\n"); + return ERR_CHECK_PAC_FAILED; + } else { + DEBUG(SSSDBG_TRACE_ALL, + "Missing SID in PAC extension, but check is not required.\n"); + return EOK; + } + } else { + user_data = ldb_msg_find_attr_as_string(msg, SYSDB_SID_STR, NULL); + if (user_data == NULL) { + if (pac_check_opts & CHECK_PAC_CHECK_UPN_DNS_INFO_EX) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User has no SID stored but SID check is required.\n"); + return ERR_CHECK_PAC_FAILED; + } else { + DEBUG(SSSDBG_TRACE_ALL, + "User has no SID stored cannot check SID from PAC.\n"); + return EOK; + } + } + + err = sss_idmap_smb_sid_to_sid(idmap_ctx, + upn_dns_info->ex.sam_name_and_sid.objectsid, + &pac_ext_sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to convert SID from PAC externsion.\n"); + return EIO; + } + + cmp_ret = strcmp(user_data, pac_ext_sid_str); + err = sss_idmap_free_sid(idmap_ctx, pac_ext_sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_idmap_free_sid() failed, ignored.\n"); + } + if (cmp_ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User SID [%s] and SID from PAC externsion [%s] differ.\n", + user_data, pac_ext_sid_str); + } + } +#else + DEBUG(SSSDBG_TRACE_ALL, + "This SSSD build does not support the sam_name_and_sid extension of " + "the UPN_DNS_INFO pac buffer.\n"); + if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT) { + DEBUG(SSSDBG_FUNC_DATA, + "UPN_DNS_INFO pac buffer extension required, but missing.\n"); + return ERR_CHECK_PAC_FAILED; + } +#endif + + return EOK; +} + +errno_t ad_get_data_from_pac(TALLOC_CTX *mem_ctx, const uint32_t pac_check_opts, + uint8_t *pac_blob, size_t pac_len, + struct PAC_LOGON_INFO **_logon_info, + struct PAC_UPN_DNS_INFO **_upn_dns_info) +{ + DATA_BLOB blob; + struct ndr_pull *ndr_pull; + struct PAC_DATA *pac_data; + enum ndr_err_code ndr_err; + size_t c; + int ret; + TALLOC_CTX *tmp_ctx; + + if (_logon_info != NULL) { + *_logon_info = NULL; + } + if (_upn_dns_info != NULL) { + *_upn_dns_info = NULL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + blob.data = pac_blob; + blob.length = pac_len; + + ndr_pull = ndr_pull_init_blob(&blob, tmp_ctx); + if (ndr_pull == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ndr_pull_init_blob failed.\n"); + ret = ENOMEM; + goto done; + } + ndr_pull->flags |= LIBNDR_FLAG_REF_ALLOC; /* FIXME: is this really needed ? */ + + pac_data = talloc_zero(tmp_ctx, struct PAC_DATA); + if (pac_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + ndr_err = ndr_pull_PAC_DATA(ndr_pull, NDR_SCALARS|NDR_BUFFERS, pac_data); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(SSSDBG_OP_FAILURE, "ndr_pull_PAC_DATA failed [%d]\n", ndr_err); + ret = EBADMSG; + goto done; + } + + for(c = 0; c < pac_data->num_buffers; c++) { + switch (pac_data->buffers[c].type) { + case PAC_TYPE_SRV_CHECKSUM: + break; + case PAC_TYPE_KDC_CHECKSUM: + break; + case PAC_TYPE_LOGON_INFO: + if (_logon_info != NULL) { + *_logon_info = talloc_steal(mem_ctx, + pac_data->buffers[c].info->logon_info.info); + } + break; + case PAC_TYPE_UPN_DNS_INFO: + if (_upn_dns_info != NULL) { + *_upn_dns_info = talloc_steal(mem_ctx, + &pac_data->buffers[c].info->upn_dns_info); + } + break; + default: + DEBUG(SSSDBG_TRACE_ALL, "Unhandled PAC buffer type [%d].\n", + pac_data->buffers[c].type); + } + } + + /* The logon_info buffer is the main PAC buffer with the basic user + * information, if this is missing we consider the PAC as broken. */ + if (_logon_info != NULL && *_logon_info == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "LOGON_INFO pac buffer missing.\n"); + ret = ERR_CHECK_PAC_FAILED; + goto done; + } + + /* The upn_dns_info buffer was added with Windows 2008, so there might be + * still very old installations which might not have it. But all relevant + * Samba versions knows about it, so no ifdef-protection is needed. */ + if (_upn_dns_info != NULL && *_upn_dns_info == NULL + && ((pac_check_opts & CHECK_PAC_UPN_DNS_INFO_PRESENT) + || (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT))) { + DEBUG(SSSDBG_CRIT_FAILURE, + "UPN_DNS_INFO pac buffer required, but missing.\n"); + ret = ERR_CHECK_PAC_FAILED; + goto done; + } + +#ifdef HAVE_STRUCT_PAC_UPN_DNS_INFO_EX + if (_upn_dns_info != NULL && *_upn_dns_info != NULL + && !((*_upn_dns_info)->flags & PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID) + && (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "UPN_DNS_INFO pac buffer extension required, but missing.\n"); + ret = ERR_CHECK_PAC_FAILED; + goto done; + } +#else + DEBUG(SSSDBG_TRACE_ALL, + "This SSSD build does not support the sam_name_and_sid extension of " + "the UPN_DNS_INFO pac buffer.\n"); + if (pac_check_opts & CHECK_PAC_UPN_DNS_INFO_EX_PRESENT) { + DEBUG(SSSDBG_FUNC_DATA, + "UPN_DNS_INFO pac buffer extension required, but missing.\n"); + ret = ERR_CHECK_PAC_FAILED; + goto done; + } +#endif + + /* Make sure the content of different PAC buffers is consistent. */ + if (_logon_info != NULL && _upn_dns_info != NULL) { + ret = check_logon_info_upn_dns_info(*_logon_info, *_upn_dns_info, + pac_check_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Validating PAC data failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + if (ret != EOK) { + if (_logon_info != NULL) { + talloc_free(*_logon_info); + *_logon_info = NULL; + } + if (_upn_dns_info != NULL) { + talloc_free(*_upn_dns_info); + *_upn_dns_info = NULL; + } + } + + return ret; +} diff --git a/src/providers/ad/ad_refresh.c b/src/providers/ad/ad_refresh.c new file mode 100644 index 0000000..7aa56f3 --- /dev/null +++ b/src/providers/ad/ad_refresh.c @@ -0,0 +1,240 @@ +/* + Copyright (C) 2019 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_id.h" + +struct ad_refresh_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct dp_id_data *account_req; + struct ad_id_ctx *id_ctx; + struct sss_domain_info *domain; + char **names; + size_t index; +}; + +static errno_t ad_refresh_step(struct tevent_req *req); +static void ad_refresh_done(struct tevent_req *subreq); + +static struct tevent_req *ad_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + int entry_type, + char **names, + void *pvt) +{ + struct ad_refresh_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + uint32_t filter_type; + + req = tevent_req_create(mem_ctx, &state, + struct ad_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (names == NULL) { + ret = EOK; + goto immediately; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->domain = domain; + state->id_ctx = talloc_get_type(pvt, struct ad_id_ctx); + state->names = names; + state->index = 0; + + switch (entry_type) { + case BE_REQ_INITGROUPS: + case BE_REQ_NETGROUP: + filter_type = BE_FILTER_NAME; + break; + case BE_REQ_USER: + case BE_REQ_GROUP: + filter_type = BE_FILTER_SECID; + break; + default: + ret = EINVAL; + goto immediately; + } + + state->account_req = be_refresh_acct_req(state, entry_type, + filter_type, domain); + if (state->account_req == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = ad_refresh_step(req); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Nothing to refresh\n"); + goto immediately; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, "ad_refresh_step() failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ad_refresh_step(struct tevent_req *req) +{ + struct ad_refresh_state *state = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + state = tevent_req_data(req, struct ad_refresh_state); + + if (state->names == NULL) { + ret = EOK; + goto done; + } + + state->account_req->filter_value = state->names[state->index]; + if (state->account_req->filter_value == NULL) { + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing refresh of %s %s\n", + be_req2str(state->account_req->entry_type), + state->account_req->filter_value); + + subreq = ad_account_info_send(state, state->be_ctx, state->id_ctx, + state->account_req); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ad_refresh_done, req); + + state->index++; + ret = EAGAIN; + +done: + return ret; +} + +static void ad_refresh_done(struct tevent_req *subreq) +{ + struct ad_refresh_state *state = NULL; + struct tevent_req *req = NULL; + const char *err_msg = NULL; + errno_t dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_refresh_state); + + ret = ad_account_info_recv(subreq, &dp_error, &err_msg); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to refresh %s [dp_error: %d, " + "errno: %d]: %s\n", be_req2str(state->account_req->entry_type), + dp_error, ret, err_msg); + goto done; + } + + if (state->account_req->entry_type == BE_REQ_INITGROUPS) { + ret = sysdb_set_initgr_expire_timestamp(state->domain, + state->account_req->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to set initgroups expiration for [%s]\n", + state->account_req->filter_value); + } + } + + ret = ad_refresh_step(req); + if (ret == EAGAIN) { + return; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ad_refresh_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +REFRESH_SEND_RECV_FNS(ad_refresh_initgroups, ad_refresh, BE_REQ_INITGROUPS); +REFRESH_SEND_RECV_FNS(ad_refresh_users, ad_refresh, BE_REQ_USER); +REFRESH_SEND_RECV_FNS(ad_refresh_groups, ad_refresh, BE_REQ_GROUP); +REFRESH_SEND_RECV_FNS(ad_refresh_netgroups, ad_refresh, BE_REQ_NETGROUP); + +errno_t ad_refresh_init(struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx) +{ + errno_t ret; + struct be_refresh_cb ad_refresh_callbacks[] = { + { .send_fn = ad_refresh_initgroups_send, + .recv_fn = ad_refresh_initgroups_recv, + .pvt = id_ctx, + }, + { .send_fn = ad_refresh_users_send, + .recv_fn = ad_refresh_users_recv, + .pvt = id_ctx, + }, + { .send_fn = ad_refresh_groups_send, + .recv_fn = ad_refresh_groups_recv, + .pvt = id_ctx, + }, + { .send_fn = ad_refresh_netgroups_send, + .recv_fn = ad_refresh_netgroups_recv, + .pvt = id_ctx, + }, + }; + + ret = be_refresh_ctx_init_with_callbacks(be_ctx, + SYSDB_SID_STR, + ad_refresh_callbacks); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize background refresh\n"); + return ret; + } + + return ret; +} diff --git a/src/providers/ad/ad_resolver.c b/src/providers/ad/ad_resolver.c new file mode 100644 index 0000000..0ce4e2a --- /dev/null +++ b/src/providers/ad/ad_resolver.c @@ -0,0 +1,484 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_domain_info.h" +#include "providers/ad/ad_resolver.h" +#include "providers/ldap/sdap_async_resolver_enum.h" +#include "providers/ldap/ldap_resolver_enum.h" + +static errno_t +ad_resolver_setup_enumeration(struct be_ctx *be_ctx, + struct ad_resolver_ctx *resolver_ctx, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn) +{ + errno_t ret; + time_t first_delay; + time_t period; + time_t offset; + time_t cleanup; + bool has_enumerated; + char *name = NULL; + struct sdap_id_ctx *id_ctx = resolver_ctx->ad_id_ctx->sdap_id_ctx; + + ret = sysdb_has_enumerated(id_ctx->opts->sdom->dom, + SYSDB_HAS_ENUMERATED_RESOLVER, + &has_enumerated); + if (ret == ENOENT) { + /* default value */ + has_enumerated = false; + } else if (ret != EOK) { + return ret; + } + + if (has_enumerated) { + /* At least one enumeration has previously run, + * so clients will get cached data. We will delay + * starting to enumerate by 10s so we don't slow + * down the startup process if this is happening + * during system boot. + */ + first_delay = 10; + } else { + /* This is our first startup. Schedule the + * enumeration to start immediately once we + * enter the mainloop. + */ + first_delay = 0; + } + + cleanup = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if (cleanup == 0) { + /* We need to cleanup the cache once in a while when enumerating, otherwise + * enumeration would only download deltas since the previous lastUSN and would + * not detect removed entries + */ + ret = dp_opt_set_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT, + LDAP_ENUM_PURGE_TIMEOUT); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot set cleanup timeout, enumeration wouldn't " + "detect removed entries!\n"); + return ret; + } + } + + period = dp_opt_get_int(id_ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + offset = dp_opt_get_int(id_ctx->opts->basic, SDAP_ENUM_REFRESH_OFFSET); + + name = talloc_asprintf(resolver_ctx, "Enumeration [resolver] of %s", + id_ctx->opts->sdom->dom->name); + if (name == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = be_ptask_create(resolver_ctx, be_ctx, + period, /* period */ + first_delay, /* first_delay */ + 5, /* enabled delay */ + offset, /* random offset */ + period, /* timeout */ + 0, /* max_backoff */ + send_fn, recv_fn, + resolver_ctx, name, + BE_PTASK_OFFLINE_SKIP | BE_PTASK_SCHEDULE_FROM_LAST, + &resolver_ctx->sdap_resolver_ctx->task); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to initialize enumeration periodic task\n"); + goto fail; + } + + talloc_free(name); + + return EOK; + +fail: + if (name != NULL) { + talloc_free(name); + } + return ret; +} + +static errno_t +ad_resolver_cleanup_task(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ad_resolver_ctx *resolver_ctx = NULL; + + resolver_ctx = talloc_get_type(pvt, struct ad_resolver_ctx); + if (resolver_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve ad_resolver_ctx!\n"); + return EINVAL; + } + + return ldap_resolver_cleanup(resolver_ctx->sdap_resolver_ctx); +} + +static errno_t +ad_resolver_setup_cleanup(struct ad_resolver_ctx *resolver_ctx) +{ + errno_t ret; + time_t first_delay; + time_t period; + time_t offset; + char *name = NULL; + struct sdap_id_ctx *id_ctx = resolver_ctx->ad_id_ctx->sdap_id_ctx; + + period = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if (period == 0) { + /* Cleanup has been explicitly disabled, so we won't + * create any cleanup tasks. */ + ret = EOK; + goto done; + } + offset = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_OFFSET); + + /* Run the first one in a couple of seconds so that we have time to + * finish initializations first. */ + first_delay = 10; + + name = talloc_asprintf(resolver_ctx, "Cleanup [resolver] of %s", + id_ctx->opts->sdom->dom->name); + if (name == NULL) { + return ENOMEM; + } + + ret = be_ptask_create_sync(resolver_ctx, id_ctx->be, period, first_delay, + 5 /* enabled delay */, offset /* random offset */, + period /* timeout */, 0, + ad_resolver_cleanup_task, resolver_ctx, name, + BE_PTASK_OFFLINE_SKIP, + &resolver_ctx->sdap_resolver_ctx->task); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to initialize cleanup periodic task for %s\n", + id_ctx->opts->sdom->dom->name); + goto done; + } + + ret = EOK; + +done: + if (name != NULL) { + talloc_free(name); + } + + return ret; +} + +errno_t +ad_resolver_setup_tasks(struct be_ctx *be_ctx, + struct ad_resolver_ctx *resolver_ctx, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn) +{ + errno_t ret; + struct sdap_id_ctx *id_ctx = resolver_ctx->ad_id_ctx->sdap_id_ctx; + struct sdap_domain *sdom = id_ctx->opts->sdom; + + /* set up enumeration task */ + if (sdom->dom->enumerate) { + DEBUG(SSSDBG_TRACE_FUNC, "Setting up resolver enumeration for %s\n", + sdom->dom->name); + ret = ad_resolver_setup_enumeration(be_ctx, resolver_ctx, + send_fn, recv_fn); + } else { + /* the enumeration task, runs the cleanup process by itself, + * but if enumeration is not running we need to schedule it */ + DEBUG(SSSDBG_TRACE_FUNC, "Setting up resolver cleanup task for %s\n", + sdom->dom->name); + ret = ad_resolver_setup_cleanup(resolver_ctx); + } + + return ret; +} + +struct ad_resolver_enum_state { + struct ad_resolver_ctx *resolver_ctx; + struct sdap_id_op *sdap_op; + struct tevent_context *ev; + + const char *realm; + struct sdap_domain *sdom; + struct sdap_domain *sditer; +}; + +static void ad_resolver_enumeration_conn_done(struct tevent_req *subreq); + +struct tevent_req * +ad_resolver_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ad_resolver_enum_state *state; + struct ad_resolver_ctx *ctx; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + struct sdap_id_ctx *sdap_id_ctx; + + req = tevent_req_create(mem_ctx, &state, struct ad_resolver_enum_state); + if (req == NULL) { + return NULL; + } + + ctx = talloc_get_type(pvt, struct ad_resolver_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve ad_resolver_ctx!\n"); + ret = EFAULT; + goto fail; + } + + sdap_id_ctx = ctx->ad_id_ctx->sdap_id_ctx; + + state->resolver_ctx = ctx; + state->ev = ev; + state->sdom = sdap_id_ctx->opts->sdom; + state->sditer = state->sdom; + state->realm = dp_opt_get_cstring(ctx->ad_id_ctx->ad_options->basic, + AD_KRB5_REALM); + if (state->realm == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "Missing realm\n"); + ret = EINVAL; + goto fail; + } + + state->sdap_op = sdap_id_op_create(state, sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n"); + ret = ENOMEM; + goto fail; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto fail; + } + tevent_req_set_callback(subreq, ad_resolver_enumeration_conn_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ad_resolver_enumeration_master_done(struct tevent_req *subreq); + +static void +ad_resolver_enumeration_conn_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_resolver_enum_state *state = tevent_req_data(req, + struct ad_resolver_enum_state); + struct sdap_id_ctx *id_ctx = state->resolver_ctx->ad_id_ctx->sdap_id_ctx; + int ret, dp_error; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, + "Backend is marked offline, retry later!\n"); + tevent_req_done(req); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Domain enumeration failed to connect to " \ + "LDAP server: (%d)[%s]\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } + return; + } + + subreq = ad_domain_info_send(state, state->ev, id_ctx->conn, + state->sdap_op, state->sdom->dom->name); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ad_domain_info_send failed.\n"); + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, ad_resolver_enumeration_master_done, req); +} + +static errno_t +ad_resolver_enum_sdom(struct tevent_req *req, + struct sdap_domain *sd, + struct sdap_resolver_ctx *sdap_resolver_ctx, + struct ad_id_ctx *id_ctx); + +static void +ad_resolver_enumeration_master_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_resolver_enum_state *state = tevent_req_data(req, + struct ad_resolver_enum_state); + char *flat_name; + const char *dns_name; + char *master_sid; + char *forest; + struct ad_id_ctx *ad_id_ctx; + + ret = ad_domain_info_recv(subreq, state, + &flat_name, &master_sid, NULL, &forest); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot retrieve master domain info\n"); + tevent_req_error(req, ret); + return; + } + + ad_id_ctx = talloc_get_type(state->sdom->pvt, struct ad_id_ctx); + if (ad_id_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve ad_id_ctx!\n"); + tevent_req_error(req, EINVAL); + return; + } + + dns_name = dp_opt_get_cstring(ad_id_ctx->ad_options->basic, AD_DOMAIN); + if (dns_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing domain name\n"); + ret = EINVAL; + tevent_req_error(req, ret); + return; + } + + ret = sysdb_master_domain_add_info(state->sdom->dom, state->realm, + flat_name, dns_name, + master_sid, forest, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot save master domain info\n"); + tevent_req_error(req, ret); + return; + } + + ret = ad_resolver_enum_sdom(req, state->sdom, + state->resolver_ctx->sdap_resolver_ctx, + ad_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not enumerate domain %s\n", state->sdom->dom->name); + tevent_req_error(req, ret); + return; + } + + /* Execution will resume in ad_enumeration_done */ +} + +static void ad_resolver_enum_sdom_done(struct tevent_req *subreq); + +static errno_t +ad_resolver_enum_sdom(struct tevent_req *req, + struct sdap_domain *sd, + struct sdap_resolver_ctx *sdap_resolver_ctx, + struct ad_id_ctx *id_ctx) +{ + struct tevent_req *subreq; + struct ad_resolver_enum_state *state = tevent_req_data(req, + struct ad_resolver_enum_state); + + /* iphosts are searched for in LDAP */ + subreq = sdap_dom_resolver_enum_send(state, state->ev, + sdap_resolver_ctx, + id_ctx->sdap_id_ctx, + sd, + id_ctx->ldap_ctx); + if (subreq == NULL) { + /* The ptask API will reschedule the enumeration on its own on + * failure */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to schedule enumeration, retrying later!\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ad_resolver_enum_sdom_done, req); + + return EOK; +} + +static void +ad_resolver_enum_sdom_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_resolver_enum_state *state = tevent_req_data(req, + struct ad_resolver_enum_state); + + ret = sdap_dom_resolver_enum_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not enumerate domain %s\n", state->sditer->dom->name); + tevent_req_error(req, ret); + return; + } + + do { + state->sditer = state->sditer->next; + } while (state->sditer && + state->sditer->dom->enumerate == false); + + if (state->sditer != NULL) { + struct ad_id_ctx *ad_id_ctx; + + ad_id_ctx = talloc_get_type(state->sditer->pvt, struct ad_id_ctx); + if (ad_id_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve ad_id_ctx!\n"); + tevent_req_error(req, EINVAL); + return; + } + + ret = ad_resolver_enum_sdom(req, state->sditer, + state->resolver_ctx->sdap_resolver_ctx, + ad_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not enumerate domain %s\n", + state->sditer->dom->name); + tevent_req_error(req, ret); + return; + } + + return; + } + + tevent_req_done(req); +} + +errno_t +ad_resolver_enumeration_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} diff --git a/src/providers/ad/ad_resolver.h b/src/providers/ad/ad_resolver.h new file mode 100644 index 0000000..b59866c --- /dev/null +++ b/src/providers/ad/ad_resolver.h @@ -0,0 +1,41 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#ifndef AD_RESOLVER_H_ +#define AD_RESOLVER_H_ + +errno_t ad_resolver_setup_tasks(struct be_ctx *be_ctx, + struct ad_resolver_ctx *ctx, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn); + +struct tevent_req * +ad_resolver_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); + +errno_t +ad_resolver_enumeration_recv(struct tevent_req *req); + +#endif /* AD_RESOLVER_H_ */ diff --git a/src/providers/ad/ad_srv.c b/src/providers/ad/ad_srv.c new file mode 100644 index 0000000..d45f160 --- /dev/null +++ b/src/providers/ad/ad_srv.c @@ -0,0 +1,496 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "util/sss_ldap.h" +#include "resolv/async_resolv.h" +#include "providers/backend.h" +#include "providers/ad/ad_srv.h" +#include "providers/ad/ad_common.h" +#include "providers/fail_over.h" +#include "providers/fail_over_srv.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async.h" +#include "db/sysdb.h" + +#define AD_SITE_DOMAIN_FMT "%s._sites.%s" + +char *ad_site_dns_discovery_domain(TALLOC_CTX *mem_ctx, + const char *site, + const char *domain) +{ + return talloc_asprintf(mem_ctx, AD_SITE_DOMAIN_FMT, site, domain); +} + +static errno_t ad_sort_servers_by_dns(TALLOC_CTX *mem_ctx, + const char *domain, + struct fo_server_info **_srv, + size_t num) +{ + struct fo_server_info *out = NULL; + struct fo_server_info *srv = NULL; + struct fo_server_info in_domain[num]; + struct fo_server_info out_domain[num]; + size_t srv_index = 0; + size_t in_index = 0; + size_t out_index = 0; + size_t i, j; + + if (_srv == NULL) { + return EINVAL; + } + + srv = *_srv; + + if (num <= 1) { + return EOK; + } + + out = talloc_zero_array(mem_ctx, struct fo_server_info, num); + if (out == NULL) { + return ENOMEM; + } + + /* When several servers share priority, we will prefer the one that + * is located in the same domain as client (e.g. child domain instead + * of forest root) but obey their weight. We will use the fact that + * the servers are already sorted by priority. */ + + for (i = 0; i < num; i++) { + if (is_host_in_domain(srv[i].host, domain)) { + /* this is a preferred server, push it to the in domain list */ + in_domain[in_index] = srv[i]; + in_index++; + } else { + /* this is a normal server, push it to the out domain list */ + out_domain[out_index] = srv[i]; + out_index++; + } + + if (i + 1 == num || srv[i].priority != srv[i + 1].priority) { + /* priority has changed or we have reached the end of the srv list, + * we will merge the list into final list and start over with + * next priority */ + for (j = 0; j < in_index; j++) { + out[srv_index] = in_domain[j]; + talloc_steal(out, out[srv_index].host); + srv_index++; + } + + for (j = 0; j < out_index; j++) { + out[srv_index] = out_domain[j]; + talloc_steal(out, out[srv_index].host); + srv_index++; + } + + in_index = 0; + out_index = 0; + } + } + + talloc_free(*_srv); + *_srv = out; + return EOK; +} + +static void ad_srv_mark_renew_site(void *pvt) +{ + struct ad_srv_plugin_ctx *ctx; + + ctx = talloc_get_type(pvt, struct ad_srv_plugin_ctx); + ctx->renew_site = true; +} + +struct ad_srv_plugin_ctx * +ad_srv_plugin_ctx_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct be_resolv_ctx *be_res, + enum host_database *host_dbs, + struct sdap_options *opts, + struct ad_options *ad_options, + const char *hostname, + const char *ad_domain, + const char *ad_site_override) +{ + struct ad_srv_plugin_ctx *ctx = NULL; + errno_t ret; + + ctx = talloc_zero(mem_ctx, struct ad_srv_plugin_ctx); + if (ctx == NULL) { + return NULL; + } + + ctx->be_ctx = be_ctx; + ctx->be_res = be_res; + ctx->host_dbs = host_dbs; + ctx->opts = opts; + ctx->renew_site = true; + ctx->ad_options = ad_options; + + ctx->hostname = talloc_strdup(ctx, hostname); + if (ctx->hostname == NULL) { + goto fail; + } + + ctx->ad_domain = talloc_strdup(ctx, ad_domain); + if (ctx->ad_domain == NULL) { + goto fail; + } + + if (ad_site_override != NULL) { + ctx->ad_site_override = talloc_strdup(ctx, ad_site_override); + if (ctx->ad_site_override == NULL) { + goto fail; + } + + ctx->ad_options->current_site = talloc_strdup(ctx->ad_options, + ad_site_override); + if (ctx->ad_options->current_site == NULL) { + goto fail; + } + } else { + ret = sysdb_get_site(ctx->ad_options, be_ctx->domain, + &ctx->ad_options->current_site); + if (ret != EOK) { + /* Not fatal. */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get current site from cache [%d]: %s\n", + ret, sss_strerror(ret)); + ctx->ad_options->current_site = NULL; + } + } + + ret = be_add_offline_cb(ctx, be_ctx, ad_srv_mark_renew_site, ctx, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_add_offline_cb failed.\n"); + goto fail; + } + + return ctx; + +fail: + talloc_free(ctx); + return NULL; +} + +struct ad_srv_plugin_state { + struct tevent_context *ev; + struct ad_srv_plugin_ctx *ctx; + const char *service; + const char *protocol; + const char *discovery_domain; + + const char *site; + char *dns_domain; + uint32_t ttl; + const char *forest; + struct fo_server_info *primary_servers; + size_t num_primary_servers; + struct fo_server_info *backup_servers; + size_t num_backup_servers; +}; + +static void ad_srv_plugin_ping_done(struct tevent_req *subreq); +static void ad_srv_plugin_servers_done(struct tevent_req *subreq); + +/* 1. Do a DNS lookup to find any DC in domain + * _ldap._tcp.domain.name + * 2. Send a CLDAP ping to the found DC to get the desirable site + * 3. Do a DNS lookup to find SRV in the site (a) + * _service._protocol.site-name._sites.domain.name + * 4. Do a DNS lookup to find global SRV records (b) + * _service._protocol.domain.name + * 5. If the site is found, use (a) as primary and (b) as backup servers, + * otherwise use (b) as primary servers + */ +struct tevent_req *ad_srv_plugin_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *service, + const char *protocol, + const char *discovery_domain, + void *pvt) +{ + struct ad_srv_plugin_state *state = NULL; + struct ad_srv_plugin_ctx *ctx = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ad_srv_plugin_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ctx = talloc_get_type(pvt, struct ad_srv_plugin_ctx); + if (ctx == NULL) { + ret = EINVAL; + goto immediately; + } + + state->ev = ev; + state->ctx = ctx; + + state->service = talloc_strdup(state, service); + if (state->service == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->protocol = talloc_strdup(state, protocol); + if (state->protocol == NULL) { + ret = ENOMEM; + goto immediately; + } + + if (discovery_domain != NULL) { + state->discovery_domain = talloc_strdup(state, discovery_domain); + } else { + state->discovery_domain = talloc_strdup(state, ctx->ad_domain); + } + if (state->discovery_domain == NULL) { + ret = ENOMEM; + goto immediately; + } + + subreq = ad_cldap_ping_send(state, ev, state->ctx, state->discovery_domain); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ad_srv_plugin_ping_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void ad_srv_plugin_ping_done(struct tevent_req *subreq) +{ + struct ad_srv_plugin_state *state = NULL; + struct tevent_req *req = NULL; + const char *primary_domain = NULL; + const char *backup_domain = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_srv_plugin_state); + + ret = ad_cldap_ping_recv(state, subreq, &state->site, &state->forest); + talloc_zfree(subreq); + + /* Ignore AD site found by dns discovery if specific site is set in + * configuration file. */ + if (state->ctx->ad_site_override != NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Ignoring AD site found by DNS discovery: '%s', " + "using configured value: '%s' instead.\n", + state->site, state->ctx->ad_site_override); + state->site = state->ctx->ad_site_override; + + if (state->forest == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Missing forest information, using %s\n", + state->discovery_domain); + state->forest = state->discovery_domain; + } + + ret = EOK; + } + + primary_domain = state->discovery_domain; + backup_domain = NULL; + + if (ret == EOK) { + /* Remember current site so it can be used during next lookup so + * we can contact directory controllers within a known reachable + * site first. */ + if (state->site != NULL) { + ret = ad_options_switch_site(state->ctx->ad_options, + state->ctx->be_ctx, + state->site, state->forest); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set site [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Do not renew the site again unless we go offline. */ + state->ctx->renew_site = false; + } + + if (strcmp(state->service, "gc") == 0) { + if (state->forest != NULL) { + if (state->site != NULL) { + primary_domain = ad_site_dns_discovery_domain( + state, + state->site, + state->forest); + if (primary_domain == NULL) { + ret = ENOMEM; + goto done; + } + + backup_domain = state->forest; + } else { + primary_domain = state->forest; + backup_domain = NULL; + } + } + } else { + if (state->site != NULL) { + primary_domain = ad_site_dns_discovery_domain( + state, + state->site, + state->discovery_domain); + if (primary_domain == NULL) { + ret = ENOMEM; + goto done; + } + + backup_domain = state->discovery_domain; + } + } + } else if (ret != ENOENT && ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "About to discover primary and " + "backup servers\n"); + + subreq = fo_discover_servers_send(state, state->ev, + state->ctx->be_res->resolv, + state->service, state->protocol, + primary_domain, backup_domain); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ad_srv_plugin_servers_done, req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static void ad_srv_plugin_servers_done(struct tevent_req *subreq) +{ + struct ad_srv_plugin_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_srv_plugin_state); + + ret = fo_discover_servers_recv(state, subreq, &state->dns_domain, + &state->ttl, + &state->primary_servers, + &state->num_primary_servers, + &state->backup_servers, + &state->num_backup_servers); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Got %zu primary and %zu backup servers\n", + state->num_primary_servers, state->num_backup_servers); + + ret = ad_sort_servers_by_dns(state, state->discovery_domain, + &state->primary_servers, + state->num_primary_servers); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to sort primary servers by DNS" + "[%d]: %s\n", ret, sss_strerror(ret)); + /* continue */ + } + + ret = ad_sort_servers_by_dns(state, state->discovery_domain, + &state->backup_servers, + state->num_backup_servers); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to sort backup servers by DNS" + "[%d]: %s\n", ret, sss_strerror(ret)); + /* continue */ + } + + tevent_req_done(req); +} + +errno_t ad_srv_plugin_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_primary_servers, + size_t *_num_primary_servers, + struct fo_server_info **_backup_servers, + size_t *_num_backup_servers) +{ + struct ad_srv_plugin_state *state = NULL; + state = tevent_req_data(req, struct ad_srv_plugin_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_primary_servers) { + *_primary_servers = talloc_steal(mem_ctx, state->primary_servers); + } + + if (_num_primary_servers) { + *_num_primary_servers = state->num_primary_servers; + } + + if (_backup_servers) { + *_backup_servers = talloc_steal(mem_ctx, state->backup_servers); + } + + if (_num_backup_servers) { + *_num_backup_servers = state->num_backup_servers; + } + + if (_dns_domain) { + *_dns_domain = talloc_steal(mem_ctx, state->dns_domain); + } + + if (_ttl) { + *_ttl = state->ttl; + } + + return EOK; +} diff --git a/src/providers/ad/ad_srv.h b/src/providers/ad/ad_srv.h new file mode 100644 index 0000000..fd70f15 --- /dev/null +++ b/src/providers/ad/ad_srv.h @@ -0,0 +1,78 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef __AD_SRV_H__ +#define __AD_SRV_H__ + +struct ad_srv_plugin_ctx { + struct be_ctx *be_ctx; + struct be_resolv_ctx *be_res; + enum host_database *host_dbs; + struct sdap_options *opts; + struct ad_options *ad_options; + const char *hostname; + const char *ad_domain; + const char *ad_site_override; + + bool renew_site; +}; + +struct ad_srv_plugin_ctx * +ad_srv_plugin_ctx_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct be_resolv_ctx *be_res, + enum host_database *host_dbs, + struct sdap_options *opts, + struct ad_options *ad_options, + const char *hostname, + const char *ad_domain, + const char *ad_site_override); + +struct tevent_req *ad_srv_plugin_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *service, + const char *protocol, + const char *discovery_domain, + void *pvt); + +errno_t ad_srv_plugin_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_primary_servers, + size_t *_num_primary_servers, + struct fo_server_info **_backup_servers, + size_t *_num_backup_servers); + +char *ad_site_dns_discovery_domain(TALLOC_CTX *mem_ctx, + const char *site, + const char *domain); + +struct tevent_req *ad_cldap_ping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ad_srv_plugin_ctx *srv_ctx, + const char *discovery_domain); + +errno_t ad_cldap_ping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_site, + const char **_forest); + +#endif /* __AD_SRV_H__ */ diff --git a/src/providers/ad/ad_subdomains.c b/src/providers/ad/ad_subdomains.c new file mode 100644 index 0000000..a8d1892 --- /dev/null +++ b/src/providers/ad/ad_subdomains.c @@ -0,0 +1,2785 @@ +/* + SSSD + + AD Subdomains Module + + Authors: + Sumit Bose + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/sdap_async.h" +#include "providers/ad/ad_subdomains.h" +#include "providers/ad/ad_domain_info.h" +#include "providers/ad/ad_srv.h" +#include "providers/ad/ad_common.h" + +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_ops.h" +#include "util/util_sss_idmap.h" +#include +#include +#include + +/* Avoid that ldb_val is overwritten by data_blob.h */ +#undef ldb_val +#include + +/* Attributes of AD trusted domains */ +#define AD_AT_FLATNAME "flatName" +#define AD_AT_SID "securityIdentifier" +#define AD_AT_TRUST_TYPE "trustType" +#define AD_AT_TRUST_PARTNER "trustPartner" +#define AD_AT_TRUST_ATTRS "trustAttributes" +#define AD_AT_DOMAIN_NAME "cn" +#define AD_AT_TRUST_DIRECTION "trustDirection" + +/* trustType=2 denotes uplevel (NT5 and later) trusted domains. See + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms680342%28v=vs.85%29.aspx + * for example. + * + * The absence of msDS-TrustForestTrustInfo attribute denotes a domain from + * the same forest. See http://msdn.microsoft.com/en-us/library/cc223786.aspx + * for more information. + */ +#define SLAVE_DOMAIN_FILTER_BASE "(objectclass=trustedDomain)(trustType=2)(!(msDS-TrustForestTrustInfo=*))" +#define SLAVE_DOMAIN_FILTER "(&"SLAVE_DOMAIN_FILTER_BASE")" + +/* Attributes of schema objects. See e.g. + * https://docs.microsoft.com/en-us/windows/desktop/AD/characteristics-of-attributes + * for more details + */ +#define AD_SCHEMA_AT_OC "attributeSchema" +#define AD_AT_SCHEMA_NAME "cn" +#define AD_AT_SCHEMA_IS_REPL "isMemberOfPartialAttributeSet" + +/* do not refresh more often than every 5 seconds for now */ +#define AD_SUBDOMAIN_REFRESH_LIMIT 5 + +/* Flags of trustAttributes attribute, see MS-ADTS 6.1.6.7.9 for details */ +#define TRUST_ATTRIBUTE_WITHIN_FOREST 0x00000020 + +/* Flags for trustDirection attribute, see MS-ADTS 6.1.6.7.12 for details */ +#define TRUST_DIRECTION_OUTBOUND 0x00000002 + +static void +ad_disable_gc(struct ad_options *ad_options) +{ + errno_t ret; + + if (dp_opt_get_bool(ad_options->basic, AD_ENABLE_GC) == false) { + return; + } + + DEBUG(SSSDBG_IMPORTANT_INFO, "POSIX attributes were requested " + "but are not present on the server side. Global Catalog " + "lookups will be disabled\n"); + + ret = dp_opt_set_bool(ad_options->basic, + AD_ENABLE_GC, false); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not turn off GC support\n"); + /* Not fatal */ + } +} + +static struct sss_domain_info * +ads_get_root_domain(struct be_ctx *be_ctx, struct sysdb_attrs *attrs) +{ + struct sss_domain_info *dom; + const char *name; + errno_t ret; + + if (attrs == NULL) { + /* Clients joined to the forest root directly don't even discover + * the root domain, so the attrs are expected to be NULL in this + * case + */ + return be_ctx->domain; + } + + ret = sysdb_attrs_get_string(attrs, AD_AT_TRUST_PARTNER, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + return NULL; + } + + /* With a subsequent run, the root should already be known */ + for (dom = be_ctx->domain; dom != NULL; + dom = get_next_domain(dom, SSS_GND_ALL_DOMAINS)) { + + if (strcasecmp(dom->name, name) == 0) { + /* The forest root is special, although it might be disabled for + * general lookups we still want to try to get the domains in the + * forest from a DC of the forest root */ + if (sss_domain_get_state(dom) == DOM_DISABLED + && !sss_domain_is_forest_root(dom)) { + return NULL; + } + return dom; + } + } + + return NULL; +} + +static struct sdap_domain * +ads_get_root_sdap_domain(struct be_ctx *be_ctx, + struct sdap_options *opts, + struct sysdb_attrs *attrs) +{ + struct sdap_domain *root_sdom; + struct sss_domain_info *root_dom; + + root_dom = ads_get_root_domain(be_ctx, attrs); + if (root_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ads_get_root_domain did not find the domain\n"); + return NULL; + } + + root_sdom = sdap_domain_get(opts, root_dom); + if (root_sdom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to find sdap_domain for the root domain\n"); + return NULL; + } + + return root_sdom; +} + +static errno_t ad_get_enabled_domains(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *ad_id_ctx, + const char *ad_domain, + const char ***_ad_enabled_domains) +{ + int ret; + const char *str; + const char *option_name; + const char **domains = NULL; + int count; + bool is_ad_in_domains; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + str = dp_opt_get_cstring(ad_id_ctx->ad_options->basic, AD_ENABLED_DOMAINS); + if (str == NULL) { + *_ad_enabled_domains = NULL; + ret = EOK; + goto done; + } + + count = 0; + ret = split_on_separator(tmp_ctx, str, ',', true, true, + discard_const_p(char **, &domains), &count); + if (ret != EOK) { + option_name = ad_id_ctx->ad_options->basic[AD_ENABLED_DOMAINS].opt_name; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse option [%s], [%i] [%s]!\n", + option_name, ret, sss_strerror(ret)); + ret = EINVAL; + goto done; + } + + is_ad_in_domains = false; + for (int i = 0; i < count; i++) { + is_ad_in_domains += strcasecmp(ad_domain, domains[i]) == 0 ? true : false; + } + + if (is_ad_in_domains == false) { + domains = talloc_realloc(tmp_ctx, domains, const char*, count + 2); + if (domains == NULL) { + ret = ENOMEM; + goto done; + } + + domains[count] = talloc_strdup(domains, ad_domain); + if (domains[count] == NULL) { + ret = ENOMEM; + goto done; + } + + domains[count + 1] = NULL; + } else { + domains = talloc_realloc(tmp_ctx, domains, const char*, count + 1); + if (domains == NULL) { + ret = ENOMEM; + goto done; + } + + domains[count] = NULL; + } + + *_ad_enabled_domains = talloc_steal(mem_ctx, domains); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static bool is_domain_enabled(const char *domain, + const char **enabled_doms) +{ + if (enabled_doms == NULL) { + return true; + } + + return string_in_list(domain, discard_const_p(char *, enabled_doms), false); +} + +static errno_t +update_parent_sdap_list(struct sdap_domain *parent_list, + struct sdap_domain *child_sdap) +{ + struct sdap_domain *sditer; + + DLIST_FOR_EACH(sditer, parent_list) { + if (sditer->dom == child_sdap->dom) { + break; + } + } + + if (sditer == NULL) { + /* Nothing to do */ + return EOK; + } + + /* Update the search bases */ + sdap_domain_copy_search_bases(sditer, child_sdap); + + return EOK; +} + +static errno_t +ad_subdom_ad_ctx_new(struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx, + struct sss_domain_info *subdom, + struct ad_id_ctx **_subdom_id_ctx) +{ + struct ad_options *ad_options; + struct ad_id_ctx *ad_id_ctx; + const char *gc_service_name; + const char *service_name; + struct ad_srv_plugin_ctx *srv_ctx; + char *ad_domain; + char *ad_site_override; + struct sdap_domain *sdom; + errno_t ret; + const char *realm; + const char *servers; + const char *backup_servers; + const char *hostname; + const char *keytab; + char *subdom_conf_path; + bool use_kdcinfo = false; + size_t n_lookahead_primary = SSS_KRB5_LOOKAHEAD_PRIMARY_DEFAULT; + size_t n_lookahead_backup = SSS_KRB5_LOOKAHEAD_BACKUP_DEFAULT; + bool ad_use_ldaps = false; + + realm = dp_opt_get_cstring(id_ctx->ad_options->basic, AD_KRB5_REALM); + hostname = dp_opt_get_cstring(id_ctx->ad_options->basic, AD_HOSTNAME); + keytab = dp_opt_get_cstring(id_ctx->ad_options->basic, AD_KEYTAB); + ad_domain = subdom->name; + if (realm == NULL || hostname == NULL || ad_domain == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "Missing realm or hostname.\n"); + return EINVAL; + } + + subdom_conf_path = subdomain_create_conf_path(id_ctx, subdom); + if (subdom_conf_path == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "subdomain_create_conf_path failed\n"); + return ENOMEM; + } + + ad_options = ad_create_2way_trust_options(id_ctx, + be_ctx->cdb, + subdom_conf_path, + be_ctx->provider, + realm, + subdom, + hostname, keytab); + if (ad_options == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD options\n"); + talloc_free(ad_options); + talloc_free(subdom_conf_path); + return ENOMEM; + } + + ret = ad_inherit_opts_if_needed(id_ctx->ad_options->basic, + ad_options->basic, + be_ctx->cdb, subdom_conf_path, + AD_USE_LDAPS); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to inherit option [%s] to sub-domain [%s]. " + "This error is ignored but might cause issues or unexpected " + "behavior later on.\n", + id_ctx->ad_options->basic[AD_USE_LDAPS].opt_name, + subdom->name); + } + + if (dp_opt_get_bool(ad_options->basic, AD_USE_LDAPS)) { + ad_set_ssf_and_mech_for_ldaps(ad_options->id); + } + + ret = ad_inherit_opts_if_needed(id_ctx->sdap_id_ctx->opts->basic, + ad_options->id->basic, + be_ctx->cdb, subdom_conf_path, + SDAP_SASL_MECH); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to inherit option [%s] to sub-domain [%s]. " + "This error is ignored but might cause issues or unexpected " + "behavior later on.\n", + id_ctx->ad_options->id->basic[SDAP_SASL_MECH].opt_name, + subdom->name); + } + + talloc_free(subdom_conf_path); + + ad_site_override = dp_opt_get_string(ad_options->basic, AD_SITE); + + gc_service_name = talloc_asprintf(ad_options, "sd_gc_%s", subdom->name); + if (gc_service_name == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + + service_name = talloc_asprintf(ad_options, "sd_%s", subdom->name); + if (service_name == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + + servers = dp_opt_get_string(ad_options->basic, AD_SERVER); + backup_servers = dp_opt_get_string(ad_options->basic, AD_BACKUP_SERVER); + ad_use_ldaps = dp_opt_get_bool(ad_options->basic, AD_USE_LDAPS); + + if (id_ctx->ad_options->auth_ctx != NULL + && id_ctx->ad_options->auth_ctx->opts != NULL) { + use_kdcinfo = dp_opt_get_bool(id_ctx->ad_options->auth_ctx->opts, + KRB5_USE_KDCINFO); + sss_krb5_parse_lookahead( + dp_opt_get_string(id_ctx->ad_options->auth_ctx->opts, + KRB5_KDCINFO_LOOKAHEAD), + &n_lookahead_primary, + &n_lookahead_backup); + } + + DEBUG(SSSDBG_TRACE_ALL, + "Init failover for [%s][%s] with use_kdcinfo [%s].\n", + subdom->name, subdom->realm, use_kdcinfo ? "true" : "false"); + + ret = ad_failover_init(ad_options, be_ctx, servers, backup_servers, + subdom->realm, service_name, gc_service_name, + subdom->name, use_kdcinfo, ad_use_ldaps, + n_lookahead_primary, + n_lookahead_backup, + &ad_options->service); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD failover\n"); + talloc_free(ad_options); + return ret; + } + + ad_id_ctx = ad_id_ctx_init(ad_options, be_ctx); + if (ad_id_ctx == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + ad_id_ctx->sdap_id_ctx->opts = ad_options->id; + ad_options->id_ctx = ad_id_ctx; + + /* use AD plugin */ + srv_ctx = ad_srv_plugin_ctx_init(be_ctx, be_ctx, be_ctx->be_res, + default_host_dbs, + ad_id_ctx->ad_options->id, + ad_id_ctx->ad_options, + hostname, + ad_domain, + ad_site_override); + if (srv_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n"); + return ENOMEM; + } + be_fo_set_srv_lookup_plugin(be_ctx, ad_srv_plugin_send, + ad_srv_plugin_recv, srv_ctx, "AD"); + + ret = sdap_domain_subdom_add(ad_id_ctx->sdap_id_ctx, + ad_id_ctx->sdap_id_ctx->opts->sdom, + subdom->parent); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize sdap domain\n"); + talloc_free(ad_options); + return ret; + } + + sdom = sdap_domain_get(ad_id_ctx->sdap_id_ctx->opts, subdom); + if (sdom == NULL) { + return EFAULT; + } + + sdap_inherit_options(subdom->parent->sd_inherit, + id_ctx->sdap_id_ctx->opts, + ad_id_ctx->sdap_id_ctx->opts); + + /* Set up the ID mapping object */ + ad_id_ctx->sdap_id_ctx->opts->idmap_ctx = + id_ctx->sdap_id_ctx->opts->idmap_ctx; + + ret = ad_set_search_bases(ad_options->id, sdom); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to set LDAP search bases for " + "domain '%s'. Will try to use automatically detected search " + "bases.", subdom->name); + } + + ret = update_parent_sdap_list(id_ctx->sdap_id_ctx->opts->sdom, + sdom); + if (ret != EOK) { + return ret; + } + + *_subdom_id_ctx = ad_id_ctx; + return EOK; +} + +struct ad_subdomains_ctx { + struct be_ctx *be_ctx; + struct ad_id_ctx *ad_id_ctx; + struct sdap_id_ctx *sdap_id_ctx; + + struct sdap_domain *sdom; + char *domain_name; + const char **ad_enabled_domains; + + time_t last_refreshed; +}; + +static errno_t ad_subdom_enumerates(struct sss_domain_info *parent, + struct sysdb_attrs *attrs, + bool *_enumerates) +{ + errno_t ret; + const char *name; + + ret = sysdb_attrs_get_string(attrs, AD_AT_TRUST_PARTNER, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + return ret; + } + + *_enumerates = subdomain_enumerates(parent, name); + return EOK; +} + +static enum sss_domain_mpg_mode +get_default_subdom_mpg_mode(struct sdap_idmap_ctx *idmap_ctx, + struct sss_domain_info *parent, + const char *subdom_name, + char *subdom_sid_str) +{ + bool use_id_mapping; + bool inherit_option; + enum sss_domain_mpg_mode default_mpg_mode; + + inherit_option = string_in_list(CONFDB_DOMAIN_AUTO_UPG, + parent->sd_inherit, false); + if (inherit_option) { + return get_domain_mpg_mode(parent); + } + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(idmap_ctx, + subdom_name, + subdom_sid_str); + if (use_id_mapping == true) { + default_mpg_mode = MPG_ENABLED; + } else { + /* Domains that use the POSIX attributes set by the admin must + * inherit the MPG setting from the parent domain so that the + * auto_private_groups options works for trusted domains as well + */ + default_mpg_mode = get_domain_mpg_mode(parent); + } + + return default_mpg_mode; +} + +static enum sss_domain_mpg_mode +ad_subdom_mpg_mode(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + struct sss_domain_info *parent, + enum sss_domain_mpg_mode default_mpg_mode, + const char *subdom_name) +{ + char *subdom_conf_path; + char *mpg_str_opt; + errno_t ret; + enum sss_domain_mpg_mode ret_mode; + + subdom_conf_path = subdomain_create_conf_path_from_str(mem_ctx, + parent->name, + subdom_name); + if (subdom_conf_path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "subdom_conf_path failed, will use %s mode as fallback\n", + str_domain_mpg_mode(default_mpg_mode)); + return default_mpg_mode; + } + + ret = confdb_get_string(cdb, mem_ctx, subdom_conf_path, + CONFDB_DOMAIN_AUTO_UPG, + NULL, + &mpg_str_opt); + talloc_free(subdom_conf_path); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "condb_get_string failed, will use %s mode as fallback\n", + str_domain_mpg_mode(default_mpg_mode)); + return default_mpg_mode; + } + + if (mpg_str_opt == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Subdomain MPG mode not set, using %s\n", + str_domain_mpg_mode(default_mpg_mode)); + return default_mpg_mode; + } + + ret_mode = str_to_domain_mpg_mode(mpg_str_opt); + talloc_free(mpg_str_opt); + return ret_mode; +} + +static errno_t +ad_subdom_store(struct confdb_ctx *cdb, + struct sdap_idmap_ctx *idmap_ctx, + struct sss_domain_info *domain, + struct sysdb_attrs *subdom_attrs, + bool enumerate) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + char *realm; + const char *flat; + const char *dns; + errno_t ret; + enum idmap_error_code err; + struct ldb_message_element *el; + char *sid_str = NULL; + enum sss_domain_mpg_mode mpg_mode; + enum sss_domain_mpg_mode default_mpg_mode; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(subdom_attrs, AD_AT_TRUST_PARTNER, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "failed to get subdomain name\n"); + goto done; + } + + realm = get_uppercase_realm(tmp_ctx, name); + if (!realm) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(subdom_attrs, AD_AT_FLATNAME, &flat); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "failed to get flat name of subdomain %s\n", + name); + goto done; + } + + ret = sysdb_attrs_get_string(subdom_attrs, AD_AT_DOMAIN_NAME, &dns); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "failed to get dns name of subdomain %s\n", + name); + goto done; + } + + ret = sysdb_attrs_get_el(subdom_attrs, AD_AT_SID, &el); + if (ret != EOK || el->num_values != 1) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_attrs_get_el failed.\n"); + goto done; + } + + err = sss_idmap_bin_sid_to_sid(idmap_ctx->map, el->values[0].data, + el->values[0].length, &sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert SID: [%s].\n", idmap_error_string(err)); + ret = EFAULT; + goto done; + } + + default_mpg_mode = get_default_subdom_mpg_mode(idmap_ctx, domain, + name, sid_str); + + mpg_mode = ad_subdom_mpg_mode(tmp_ctx, cdb, domain, + default_mpg_mode, name); + DEBUG(SSSDBG_CONF_SETTINGS, "MPG mode of %s is %s\n", + name, str_domain_mpg_mode(mpg_mode)); + + ret = sysdb_subdomain_store(domain->sysdb, name, realm, flat, dns, sid_str, + mpg_mode, enumerate, domain->forest, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_subdomain_store failed.\n"); + goto done; + } + + ret = EOK; +done: + sss_idmap_free_sid(idmap_ctx->map, sid_str); + talloc_free(tmp_ctx); + + return ret; +} + +/* When reading trusted domains from the local DC we are basically interested + * in domains from the local forest we are trusting, i.e. users from this + * domain can connect to us. To not unnecessarily bloat the list of domains + * and make multi-domain searches slow we filter domains from other forest and + * domains we do not trust. + * In future we might add config options to broaden the scope and allow more + * domains. + * If ad_filter_domains() returns successfully with EOK in input array is not + * valid anymore and should be freed by the caller. */ +static errno_t ad_filter_domains(TALLOC_CTX *mem_ctx, + struct sysdb_attrs **subdomains, + size_t num_subdomains, + struct sysdb_attrs ***_sd_out, + size_t *_num_sd_out) +{ + int ret; + size_t c; + uint32_t tmp_uint32_t; + const char *value; + struct sysdb_attrs **sd_out; + size_t num_sd_out = 0; + + sd_out = talloc_zero_array(mem_ctx, struct sysdb_attrs *, + num_subdomains + 1); + if (sd_out == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to allocate memory for sub-domain list.\n"); + return ENOMEM; + } + + for (c = 0; c < num_subdomains; c++) { + ret = sysdb_attrs_get_string(subdomains[c], AD_AT_TRUST_PARTNER, + &value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + talloc_free(sd_out); + return ret; + } + + /* Ignore direct trusts to domains from other forests + * (TRUST_ATTRIBUTE_WITHIN_FOREST is not set) or domains we do not + * trust (TRUST_DIRECTION_OUTBOUND is not set) */ + + tmp_uint32_t = 0; + ret = sysdb_attrs_get_uint32_t(subdomains[c], AD_AT_TRUST_ATTRS, + &tmp_uint32_t); + if (ret != EOK + || (tmp_uint32_t & TRUST_ATTRIBUTE_WITHIN_FOREST) == 0) { + DEBUG(SSSDBG_FUNC_DATA, + "TRUST_ATTRIBUTE_WITHIN_FOREST not set for [%s].\n", + value); + continue; + } + + tmp_uint32_t = 0; + ret = sysdb_attrs_get_uint32_t(subdomains[c], AD_AT_TRUST_DIRECTION, + &tmp_uint32_t); + if (ret != EOK + || (tmp_uint32_t & TRUST_DIRECTION_OUTBOUND) == 0) { + DEBUG(SSSDBG_FUNC_DATA, + "TRUST_DIRECTION_OUTBOUND not set for [%s].\n", + value); + continue; + } + + sd_out[num_sd_out] = subdomains[c]; + num_sd_out++; + } + + for (c = 0; c < num_sd_out; c++) { + sd_out[c] = talloc_steal(sd_out, sd_out[c]); + } + + *_sd_out = sd_out; + *_num_sd_out = num_sd_out; + + return EOK; +} + +/* How many times we keep a domain not found during searches before it will be + * removed. */ +#define MAX_NOT_FOUND 6 + +static errno_t ad_subdomains_refresh(struct be_ctx *be_ctx, + struct sdap_idmap_ctx *idmap_ctx, + struct sdap_options *opts, + struct sysdb_attrs **subdomains, + size_t num_subdomains, + bool root_domain, + time_t *_last_refreshed, + bool *_changes) +{ + struct sdap_domain *sdom; + struct sss_domain_info *domain; + struct sss_domain_info *dom; + bool handled[num_subdomains]; + const char *value; + const char *root_name = NULL; + size_t c, h; + int ret; + bool enumerate; + + domain = be_ctx->domain; + memset(handled, 0, sizeof(bool) * num_subdomains); + h = 0; + + if (root_domain) { + ret = sysdb_attrs_get_string(subdomains[0], AD_AT_TRUST_PARTNER, + &root_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + } + + /* check existing subdomains */ + for (dom = get_next_domain(domain, SSS_GND_DESCEND); + dom && IS_SUBDOMAIN(dom); /* if we get back to a parent, stop */ + dom = get_next_domain(dom, 0)) { + + /* If we are handling root domain, skip all the other domains. We don't + * want to accidentally remove non-root domains + */ + if (root_name && strcmp(root_name, dom->name) != 0) { + continue; + } + + for (c = 0; c < num_subdomains; c++) { + if (handled[c]) { + continue; + } + ret = sysdb_attrs_get_string(subdomains[c], AD_AT_TRUST_PARTNER, + &value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + if (strcmp(value, dom->name) == 0) { + break; + } + } + + if (c >= num_subdomains) { + DEBUG(SSSDBG_CONF_SETTINGS, "Domain [%s] not in current list.\n", + dom->name); + /* Since the forest root might not have trustedDomain objects for + * each domain in the forest, especially e.g. for child-domains of + * child-domains, we cannot reliable say if a domain is still + * present or not. + * Maybe it would work to check the crossRef objects in + * CN=Partitions,CN=Configuration as well to understand if a + * domain is still known in the forest or not. + * For the time being we use a counter, if a domain was not found + * after multiple attempts it will be deleted. */ + + if (dom->not_found_counter++ < MAX_NOT_FOUND) { + DEBUG(SSSDBG_TRACE_ALL, + "Domain [%s] was not found [%zu] times.\n", dom->name, + dom->not_found_counter); + continue; + } + + /* ok this subdomain does not exist anymore, let's clean up */ + sss_domain_set_state(dom, DOM_DISABLED); + + /* Just disable the forest root but do not remove sdap data */ + if (sss_domain_is_forest_root(dom)) { + DEBUG(SSSDBG_TRACE_ALL, + "Skipping removal of forest root sdap data.\n"); + + ret = sysdb_domain_set_enabled(dom->sysdb, dom->name, false); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to disable domain %s " + "[%d]: %s\n", dom->name, ret, sss_strerror(ret)); + goto done; + } + continue; + } + + ret = sysdb_subdomain_delete(dom->sysdb, dom->name); + if (ret != EOK) { + goto done; + } + + sdom = sdap_domain_get(opts, dom); + if (sdom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Domain does not exist?\n"); + continue; + } + + /* Remove the subdomain from the list of LDAP domains */ + sdap_domain_remove(opts, dom); + + /* terminate all requests for this subdomain so we can free it */ + dp_terminate_domain_requests(be_ctx->provider, dom->name); + talloc_zfree(sdom); + + } else { + /* ok let's try to update it */ + ret = ad_subdom_enumerates(domain, subdomains[c], &enumerate); + if (ret != EOK) { + goto done; + } + + dom->not_found_counter = 0; + ret = ad_subdom_store(be_ctx->cdb, idmap_ctx, domain, + subdomains[c], enumerate); + if (ret) { + /* Nothing we can do about the error. Let's at least try + * to reuse the existing domains + */ + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to parse subdom data, " + "will try to use cached subdomain\n"); + } + handled[c] = true; + h++; + } + } + + if (num_subdomains == h) { + /* all domains were already accounted for and have been updated */ + ret = EOK; + *_changes = false; + goto done; + } + + /* if we get here it means we have changes to the subdomains list */ + *_changes = true; + + for (c = 0; c < num_subdomains; c++) { + if (handled[c]) { + continue; + } + /* Nothing we can do about the error. Let's at least try + * to reuse the existing domains. + */ + ret = ad_subdom_enumerates(domain, subdomains[c], &enumerate); + if (ret != EOK) { + goto done; + } + + ret = ad_subdom_store(be_ctx->cdb, idmap_ctx, domain, + subdomains[c], enumerate); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to parse subdom data, " + "will try to use cached subdomain\n"); + } + } + + ret = EOK; + +done: + if (ret != EOK) { + *_last_refreshed = 0; + } else { + *_last_refreshed = time(NULL); + } + + return ret; +} + +static errno_t ad_subdomains_process(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char **enabled_domains_list, + size_t nsd, struct sysdb_attrs **sd, + struct sysdb_attrs *root, + size_t *_nsd_out, + struct sysdb_attrs ***_sd_out) +{ + size_t i, sdi; + struct sysdb_attrs **sd_out; + const char *sd_name; + const char *root_name; + errno_t ret; + + if (root == NULL && enabled_domains_list == NULL) { + /* We are connected directly to the root domain. The 'sd' + * list is complete and we can just use it + */ + *_nsd_out = nsd; + *_sd_out = sd; + return EOK; + } + + /* If we searched for root separately, we must: + * a) treat the root domain as a subdomain + * b) filter the subdomain we are connected to from the subdomain + * list, from our point of view, it's the master domain + */ + sd_out = talloc_zero_array(mem_ctx, struct sysdb_attrs *, nsd+1); + if (sd_out == NULL) { + return ENOMEM; + } + + sdi = 0; + for (i = 0; i < nsd; i++) { + ret = sysdb_attrs_get_string(sd[i], AD_AT_TRUST_PARTNER, &sd_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto fail; + } + + if (is_domain_enabled(sd_name, enabled_domains_list) == false) { + DEBUG(SSSDBG_TRACE_FUNC, "Disabling subdomain %s\n", sd_name); + + /* The subdomain is now disabled in configuraiton file, we + * need to delete its cached content so it is not returned + * by responders. The subdomain shares sysdb with its parent + * domain so it is OK to use domain->sysdb. */ + ret = sysdb_subdomain_delete(domain->sysdb, sd_name); + if (ret != EOK) { + goto fail; + } + continue; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Enabling subdomain %s\n", sd_name); + } + + if (strcasecmp(sd_name, domain->name) == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Not including primary domain %s in the subdomain list\n", + domain->name); + continue; + } + + sd_out[sdi] = talloc_steal(sd_out, sd[i]); + sdi++; + } + + /* Now include the root */ + if (root != NULL) { + ret = sysdb_attrs_get_string(root, AD_AT_TRUST_PARTNER, &root_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto fail; + } + + if (is_domain_enabled(root_name, enabled_domains_list) == true) { + sd_out[sdi] = talloc_steal(sd_out, root); + sdi++; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Disabling forest root domain %s\n", + root_name); + ret = sysdb_domain_set_enabled(domain->sysdb, root_name, false); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to disable domain %s " + "[%d]: %s\n", root_name, ret, sss_strerror(ret)); + goto fail; + } + } + } + + *_nsd_out = sdi; + *_sd_out = sd_out; + return EOK; + +fail: + talloc_free(sd_out); + return ret; +} + +static errno_t +ads_store_sdap_subdom(struct ad_subdomains_ctx *ctx, + struct sss_domain_info *parent) +{ + int ret; + struct sdap_domain *sditer; + struct ad_id_ctx *subdom_id_ctx; + + ret = sdap_domain_subdom_add(ctx->sdap_id_ctx, ctx->sdom, parent); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_domain_subdom_add failed.\n"); + return ret; + } + + ret = ad_set_search_bases(ctx->ad_id_ctx->ad_options->id, ctx->sdom); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "failed to set ldap search bases for " + "domain '%s'. will try to use automatically detected search " + "bases.", ctx->sdom->dom->name); + } + + DLIST_FOR_EACH(sditer, ctx->sdom) { + if (IS_SUBDOMAIN(sditer->dom) && sditer->pvt == NULL) { + ret = ad_subdom_ad_ctx_new(ctx->be_ctx, ctx->ad_id_ctx, + sditer->dom, &subdom_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_subdom_ad_ctx_new failed.\n"); + } else { + sditer->pvt = subdom_id_ctx; + } + } + } + + return EOK; +} + +static errno_t ad_subdom_reinit(struct ad_subdomains_ctx *subdoms_ctx) +{ + const char *path; + errno_t ret; + bool canonicalize = false; + struct sss_domain_info *dom; + + path = dp_opt_get_string(subdoms_ctx->ad_id_ctx->ad_options->basic, + AD_KRB5_CONFD_PATH); + + if (subdoms_ctx->ad_id_ctx->ad_options->auth_ctx != NULL + && subdoms_ctx->ad_id_ctx->ad_options->auth_ctx->opts != NULL) { + canonicalize = dp_opt_get_bool( + subdoms_ctx->ad_id_ctx->ad_options->auth_ctx->opts, + KRB5_CANONICALIZE); + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "Auth provider data is not available, " + "most probably because the auth provider " + "is not 'ad'. Kerberos configuration " + "snippet to set the 'canonicalize' option " + "will not be created.\n"); + } + + ret = sss_write_krb5_conf_snippet(path, canonicalize, true); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "sss_write_krb5_conf_snippet failed.\n"); + /* Just continue */ + } + + ret = sysdb_update_subdomains(subdoms_ctx->be_ctx->domain, + subdoms_ctx->be_ctx->cdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_subdomains failed.\n"); + return ret; + } + + ret = sss_write_domain_mappings(subdoms_ctx->be_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "sss_krb5_write_mappings failed.\n"); + /* Just continue */ + } + + ret = ads_store_sdap_subdom(subdoms_ctx, subdoms_ctx->be_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ads_store_sdap_subdom failed.\n"); + return ret; + } + + /* Make sure disabled domains are not re-enabled accidentally */ + if (subdoms_ctx->ad_enabled_domains != NULL) { + for (dom = subdoms_ctx->be_ctx->domain->subdomains; dom; + dom = get_next_domain(dom, false)) { + if (!is_domain_enabled(dom->name, + subdoms_ctx->ad_enabled_domains)) { + sss_domain_set_state(dom, DOM_DISABLED); + } + } + } + + return EOK; +} + +struct ad_get_slave_domain_state { + struct tevent_context *ev; + struct ad_subdomains_ctx *sd_ctx; + struct be_ctx *be_ctx; + struct sdap_options *opts; + struct sdap_idmap_ctx *idmap_ctx; + struct sysdb_attrs *root_attrs; + struct sdap_domain *root_sdom; + struct sdap_id_op *sdap_op; +}; + +static errno_t ad_get_slave_domain_retry(struct tevent_req *req); +static void ad_get_slave_domain_connect_done(struct tevent_req *subreq); +static void ad_get_slave_domain_done(struct tevent_req *subreq); + +static struct tevent_req * +ad_get_slave_domain_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ad_subdomains_ctx *sd_ctx, + struct sysdb_attrs *root_attrs, + struct ad_id_ctx *root_id_ctx) +{ + struct ad_get_slave_domain_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ad_get_slave_domain_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->sd_ctx = sd_ctx; + state->be_ctx = sd_ctx->be_ctx; + state->opts = root_id_ctx->sdap_id_ctx->opts; + state->idmap_ctx = root_id_ctx->sdap_id_ctx->opts->idmap_ctx; + state->root_attrs = root_attrs; + state->root_sdom = ads_get_root_sdap_domain(state->be_ctx, + state->opts, + state->root_attrs); + if (state->root_sdom == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto immediately; + } + + state->sdap_op = sdap_id_op_create(state, root_id_ctx->ldap_ctx->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + ret = ad_get_slave_domain_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ad_get_slave_domain_retry(struct tevent_req *req) +{ + struct ad_get_slave_domain_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct ad_get_slave_domain_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, ad_get_slave_domain_connect_done, req); + + return EAGAIN; +} + +static void ad_get_slave_domain_connect_done(struct tevent_req *subreq) +{ + struct ad_get_slave_domain_state *state; + struct tevent_req *req = NULL; + int dp_error; + errno_t ret; + const char *attrs[] = { AD_AT_FLATNAME, AD_AT_TRUST_PARTNER, + AD_AT_SID, AD_AT_TRUST_TYPE, AD_AT_DOMAIN_NAME, + AD_AT_TRUST_ATTRS, AD_AT_TRUST_DIRECTION, NULL }; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_get_slave_domain_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to connect to LDAP " + "[%d]: %s\n", ret, sss_strerror(ret)); + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, "No AD server is available, " + "cannot get the subdomain list while offline\n"); + ret = ERR_OFFLINE; + } + tevent_req_error(req, ret); + return; + } + + subreq = sdap_search_bases_send(state, state->ev, state->opts, + sdap_id_op_handle(state->sdap_op), + state->root_sdom->search_bases, + NULL, false, 0, + SLAVE_DOMAIN_FILTER, attrs, NULL); + if (subreq == NULL) { + tevent_req_error(req, ret); + return; + } + + tevent_req_set_callback(subreq, ad_get_slave_domain_done, req); + return; +} + +static void ad_get_slave_domain_done(struct tevent_req *subreq) +{ + struct ad_get_slave_domain_state *state; + struct tevent_req *req; + struct sysdb_attrs **reply; + size_t reply_count; + struct sysdb_attrs **subdoms; + size_t nsubdoms; + bool has_changes; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_get_slave_domain_state); + + ret = sdap_search_bases_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup slave domain data " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* We continue to finish sdap_id_op. */ + } + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ad_get_slave_domain_retry(req); + if (ret != EOK) { + goto done; + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + ret = ERR_OFFLINE; + goto done; + } else if (ret != EOK) { + goto done; + } + + /* Based on whether we are connected to the forest root or not, we might + * need to exclude the subdomain we are connected to from the list of + * subdomains. + */ + ret = ad_subdomains_process(state, state->be_ctx->domain, + state->sd_ctx->ad_enabled_domains, + reply_count, reply, state->root_attrs, + &nsubdoms, &subdoms); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot process subdomain list\n"); + tevent_req_error(req, ret); + return; + } + + /* Got all the subdomains, let's process them. */ + ret = ad_subdomains_refresh(state->be_ctx, state->idmap_ctx, state->opts, + subdoms, nsubdoms, false, + &state->sd_ctx->last_refreshed, + &has_changes); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to refresh subdomains.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "There are %schanges\n", + has_changes ? "" : "no "); + + if (has_changes) { + ret = ad_subdom_reinit(state->sd_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not reinitialize subdomains\n"); + goto done; + } + } + + state->sd_ctx->last_refreshed = time(NULL); + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ad_get_slave_domain_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static struct ad_id_ctx * +ads_get_dom_id_ctx(struct be_ctx *be_ctx, + struct ad_id_ctx *ad_id_ctx, + struct sss_domain_info *domain, + struct sdap_options *opts) +{ + errno_t ret; + struct sdap_domain *sdom; + struct ad_id_ctx *dom_id_ctx; + + sdom = sdap_domain_get(opts, domain); + if (sdom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the sdom for %s!\n", domain->name); + return NULL; + } + + if (sdom->pvt == NULL) { + ret = ad_subdom_ad_ctx_new(be_ctx, ad_id_ctx, domain, + &dom_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_subdom_ad_ctx_new failed.\n"); + return NULL; + } + + sdom->pvt = dom_id_ctx; + } else { + dom_id_ctx = sdom->pvt; + } + + dom_id_ctx->ldap_ctx->ignore_mark_offline = true; + return dom_id_ctx; +} + +struct ad_get_root_domain_state { + struct ad_subdomains_ctx *sd_ctx; + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct sdap_idmap_ctx *idmap_ctx; + struct sdap_options *opts; + const char *domain; + const char *forest; + + struct sysdb_attrs **reply; + size_t reply_count; + struct ad_id_ctx *root_id_ctx; + struct sysdb_attrs *root_domain_attrs; +}; + +static void ad_get_root_domain_done(struct tevent_req *subreq); +static void ad_check_root_domain_done(struct tevent_req *subreq); +static errno_t +ad_get_root_domain_refresh(struct ad_get_root_domain_state *state); + +struct tevent_req * +ad_check_domain_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ad_id_ctx *ad_id_ctx, + const char *dom_name, + const char *parent_dom_name); +errno_t ad_check_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_flat, + char **_id, + char **_site, + char **_forest); + +static struct tevent_req * +ad_get_root_domain_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *domain, + const char *forest, + struct sdap_handle *sh, + struct ad_subdomains_ctx *sd_ctx) +{ + struct ad_get_root_domain_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + struct sdap_options *opts; + errno_t ret; + const char *attrs[] = { AD_AT_FLATNAME, AD_AT_TRUST_PARTNER, + AD_AT_SID, AD_AT_TRUST_TYPE, AD_AT_TRUST_DIRECTION, + AD_AT_TRUST_ATTRS, AD_AT_DOMAIN_NAME, NULL }; + + req = tevent_req_create(mem_ctx, &state, struct ad_get_root_domain_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (forest == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Name of forest root domain not available, l" + "using cached data, if available.\n"); + ret = EINVAL; + goto immediately; + } else if (strcasecmp(domain, forest) == 0) { + state->root_id_ctx = sd_ctx->ad_id_ctx; + state->root_domain_attrs = NULL; + ret = EOK; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Looking up the forest root domain.\n"); + + state->sd_ctx = sd_ctx; + state->opts = opts = sd_ctx->sdap_id_ctx->opts; + state->be_ctx = sd_ctx->be_ctx; + state->idmap_ctx = opts->idmap_ctx; + state->ev = ev; + state->domain = domain; + state->forest = forest; + + subreq = sdap_search_bases_return_first_send(state, ev, opts, sh, + opts->sdom->search_bases, + NULL, false, 0, + SLAVE_DOMAIN_FILTER, attrs, + NULL); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ad_get_root_domain_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static struct sysdb_attrs *find_domain(size_t count, struct sysdb_attrs **reply, + const char *dom_name) +{ + size_t c; + const char *name; + int ret; + + for (c = 0; c < count; c++) { + ret = sysdb_attrs_get_string(reply[c], AD_AT_DOMAIN_NAME, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to find domain name, skipping"); + continue; + } + if (strcasecmp(name, dom_name) == 0) { + return reply[c]; + } + } + + return NULL; +} + +static void ad_get_root_domain_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_get_root_domain_state *state; + errno_t ret; + bool has_changes = false; + struct sysdb_attrs **unfiltered_reply; + size_t unfiltered_reply_count; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_get_root_domain_state); + + ret = sdap_search_bases_return_first_recv(subreq, state, + &unfiltered_reply_count, + &unfiltered_reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to lookup forest root information " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + if (state->sd_ctx->ad_enabled_domains == NULL) { + ret = ad_filter_domains(state, unfiltered_reply, unfiltered_reply_count, + &state->reply, &state->reply_count); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to filter list of returned domains.\n"); + goto done; + } + } else { + DEBUG(SSSDBG_TRACE_ALL, + "ad_enabled_domains is set, skipping domain filtering.\n"); + state->reply_count = unfiltered_reply_count; + state->reply = unfiltered_reply; + } + + if (state->reply_count == 0 + || find_domain(state->reply_count, state->reply, + state->forest) == NULL) { + + if (state->reply_count > 0) { + /* refresh the other domains we have found before checking forest + * root */ + ret = ad_subdomains_refresh(state->be_ctx, state->idmap_ctx, + state->opts, + state->reply, state->reply_count, false, + &state->sd_ctx->last_refreshed, + &has_changes); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ad_subdomains_refresh failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (has_changes) { + ret = ad_subdom_reinit(state->sd_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not reinitialize subdomains\n"); + goto done; + } + } + } + + DEBUG(SSSDBG_OP_FAILURE, + "No information provided for root domain, trying directly.\n"); + subreq = ad_check_domain_send(state, state->ev, state->be_ctx, + state->sd_ctx->ad_id_ctx, state->forest, + state->domain); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ad_check_domain_send() failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ad_check_root_domain_done, req); + return; + } + + ret = ad_get_root_domain_refresh(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_get_root_domain_refresh() failed.\n"); + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void ad_check_root_domain_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_get_root_domain_state *state; + errno_t ret; + char *flat = NULL; + char *id = NULL; + enum idmap_error_code err; + struct ldb_val id_val; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_get_root_domain_state); + + ret = ad_check_domain_recv(state, subreq, &flat, &id, NULL, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to check forest root information " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + if (flat == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "NetBIOS name of forest root not available.\n"); + ret = EINVAL; + goto done; + } + + if (id == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Domain SID of forest root not available.\n"); + ret = EINVAL; + goto done; + } + + state->reply = talloc_array(state, struct sysdb_attrs *, 1); + if (state->reply == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array() failed.\n"); + ret = ENOMEM; + goto done; + } + + state->reply[0] = sysdb_new_attrs(state->reply); + if (state->reply[0] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs() failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(state->reply[0], AD_AT_FLATNAME, flat); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string() failed.\n"); + goto done; + } + + ret = sysdb_attrs_add_string(state->reply[0], AD_AT_TRUST_PARTNER, + state->forest); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string() failed.\n"); + goto done; + } + + ret = sysdb_attrs_add_string(state->reply[0], AD_AT_DOMAIN_NAME, + state->forest); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string() failed.\n"); + goto done; + } + + err = sss_idmap_sid_to_bin_sid(state->idmap_ctx->map, id, + &id_val.data, &id_val.length); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not convert SID: [%s].\n", idmap_error_string(err)); + ret = EFAULT; + goto done; + } + + ret = sysdb_attrs_add_val(state->reply[0], AD_AT_SID, &id_val); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string() failed.\n"); + goto done; + } + + state->reply_count = 1; + + ret = ad_get_root_domain_refresh(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_get_root_domain_refresh() failed.\n"); + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t +ad_get_root_domain_refresh(struct ad_get_root_domain_state *state) +{ + struct sss_domain_info *root_domain; + bool has_changes; + errno_t ret; + + ret = ad_subdomains_refresh(state->be_ctx, state->idmap_ctx, state->opts, + state->reply, state->reply_count, false, + &state->sd_ctx->last_refreshed, + &has_changes); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_subdomains_refresh failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (has_changes) { + ret = ad_subdom_reinit(state->sd_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not reinitialize subdomains\n"); + goto done; + } + } + + state->root_domain_attrs = find_domain(state->reply_count, state->reply, + state->forest); + root_domain = ads_get_root_domain(state->be_ctx, state->root_domain_attrs); + if (root_domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Could not find the root domain\n"); + ret = EFAULT; + goto done; + } + + state->root_id_ctx = ads_get_dom_id_ctx(state->be_ctx, + state->sd_ctx->ad_id_ctx, + root_domain, state->opts); + if (state->root_id_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot create id ctx for the root domain\n"); + ret = EFAULT; + goto done; + } + + ret = EOK; + +done: + return ret; +} + +static errno_t ad_get_root_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_attrs, + struct ad_id_ctx **_id_ctx) +{ + struct ad_get_root_domain_state *state = NULL; + state = tevent_req_data(req, struct ad_get_root_domain_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_attrs = talloc_steal(mem_ctx, state->root_domain_attrs); + *_id_ctx = state->root_id_ctx; + + return EOK; +} + +static void ad_check_gc_usability_search_done(struct tevent_req *subreq); + +struct ad_check_gc_usability_state { + struct sdap_options *sdap_opts; + + const char *attrs[3]; + + bool is_gc_usable; +}; + +static struct tevent_req * +ad_check_gc_usability_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ad_options *ad_options, + struct sdap_options *sdap_opts, + struct sdap_id_op *op, + const char *domain_name, + const char *domain_sid) +{ + struct ad_check_gc_usability_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + const char *filter = NULL; + errno_t ret; + bool uses_id_mapping; + + req = tevent_req_create(mem_ctx, &state, + struct ad_check_gc_usability_state); + if (req == NULL) { + return NULL; + } + state->sdap_opts = sdap_opts; + state->is_gc_usable = false; + + if (dp_opt_get_bool(ad_options->basic, AD_ENABLE_GC) == false) { + DEBUG(SSSDBG_TRACE_FUNC, "GC explicitly disabled\n"); + state->is_gc_usable = false; + ret = EOK; + goto immediately; + } + + uses_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + sdap_opts->idmap_ctx, + domain_name, + domain_sid); + if (uses_id_mapping == true) { + DEBUG(SSSDBG_TRACE_FUNC, "GC always usable while ID mapping\n"); + state->is_gc_usable = true; + ret = EOK; + goto immediately; + } + + /* The schema partition is replicated across all DCs in the forest, so + * it's safe to use the baseDN even if e.g. joined to a child domain + * even though the base DN "looks" like a part of the forest root + * tree. On the other hand, it doesn't make sense to guess the value + * if we can't detect it from the rootDSE. + */ + if (state->sdap_opts->schema_basedn == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "No idea where to look for the schema, disabling GC\n"); + state->is_gc_usable = false; + ret = EOK; + goto immediately; + } + + state->attrs[0] = AD_AT_SCHEMA_NAME; + state->attrs[1] = AD_AT_SCHEMA_IS_REPL; + state->attrs[2] = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for POSIX attributes in GC\n"); + + filter = talloc_asprintf( + state, + "(&(objectclass=%s)(|(%s=%s)(%s=%s)))", + AD_SCHEMA_AT_OC, + AD_AT_SCHEMA_NAME, + state->sdap_opts->user_map[SDAP_AT_USER_UID].name, + AD_AT_SCHEMA_NAME, + state->sdap_opts->group_map[SDAP_AT_GROUP_GID].name); + if (filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_get_generic_send(state, + ev, + state->sdap_opts, + sdap_id_op_handle(op), + state->sdap_opts->schema_basedn, + LDAP_SCOPE_SUBTREE, + filter, + state->attrs, + NULL, 0, + dp_opt_get_int(state->sdap_opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, ad_check_gc_usability_search_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ad_check_gc_usability_search_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_check_gc_usability_state *state = tevent_req_data(req, + struct ad_check_gc_usability_state); + errno_t ret; + size_t reply_count; + struct sysdb_attrs **reply = NULL; + bool uid = false; + bool gid = false; + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_get_generic_recv failed [%d]: %s\n", + ret, strerror(ret)); + /* We continue to finish sdap_id_op. */ + } + + if (reply_count == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Nothing found, so no POSIX attrs can exist\n"); + state->is_gc_usable = false; + tevent_req_done(req); + return; + } + + for (size_t i = 0; i < reply_count; i++) { + const char *name = NULL; + const char *is_in_partial_set = NULL; + bool *val = NULL; + + ret = sysdb_attrs_get_string(reply[i], AD_AT_SCHEMA_NAME, &name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot get "AD_AT_SCHEMA_NAME"\n"); + continue; + } + + if (strcasecmp(name, state->sdap_opts->user_map[SDAP_AT_USER_UID].name) == 0) { + val = &uid; + } else if (strcasecmp(name, state->sdap_opts->user_map[SDAP_AT_USER_GID].name) == 0) { + val = &gid; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected attribute\n"); + continue; + } + + ret = sysdb_attrs_get_string(reply[i], + AD_AT_SCHEMA_IS_REPL, + &is_in_partial_set); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot get "AD_AT_SCHEMA_IS_REPL"\n"); + continue; + } + + if (strcasecmp(is_in_partial_set, "true") == 0) { + *val = true; + } + } + + if (uid == true && gid == true) { + state->is_gc_usable = true; + } + + if (state->is_gc_usable == true) { + DEBUG(SSSDBG_FUNC_DATA, "Server has POSIX attributes. Global Catalog will " + "be used for user and group lookups. Note that if " + "only a subset of POSIX attributes is present " + "in GC, the non-replicated attributes are " + "currently not read from the LDAP port\n"); + } + + tevent_req_done(req); +} + +static errno_t ad_check_gc_usability_recv(struct tevent_req *req, + bool *_is_gc_usable) +{ + struct ad_check_gc_usability_state *state = NULL; + + state = tevent_req_data(req, struct ad_check_gc_usability_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_is_gc_usable = state->is_gc_usable; + return EOK; +} + +struct ad_subdomains_refresh_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ad_subdomains_ctx *sd_ctx; + struct sdap_id_op *sdap_op; + struct sdap_id_ctx *id_ctx; + struct ad_options *ad_options; + + char *forest; +}; + +static errno_t ad_subdomains_refresh_retry(struct tevent_req *req); +static void ad_subdomains_refresh_connect_done(struct tevent_req *subreq); +static void ad_subdomains_refresh_master_done(struct tevent_req *subreq); +static void ad_subdomains_refresh_gc_check_done(struct tevent_req *subreq); +static void ad_subdomains_refresh_root_done(struct tevent_req *subreq); +static void ad_subdomains_refresh_done(struct tevent_req *subreq); + +static struct tevent_req * +ad_subdomains_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ad_subdomains_ctx *sd_ctx) +{ + struct ad_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ad_subdomains_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->be_ctx = sd_ctx->be_ctx; + state->sd_ctx = sd_ctx; + state->id_ctx = sd_ctx->sdap_id_ctx; + state->ad_options = sd_ctx->ad_id_ctx->ad_options; + + state->sdap_op = sdap_id_op_create(state, + sd_ctx->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + ret = ad_subdomains_refresh_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ad_subdomains_refresh_retry(struct tevent_req *req) +{ + struct ad_subdomains_refresh_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct ad_subdomains_refresh_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, ad_subdomains_refresh_connect_done, req); + + return EAGAIN; +} + +static void ad_subdomains_refresh_connect_done(struct tevent_req *subreq) +{ + struct ad_subdomains_refresh_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_subdomains_refresh_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to connect to LDAP " + "[%d]: %s\n", ret, sss_strerror(ret)); + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, "No AD server is available, " + "cannot get the subdomain list while offline\n"); + ret = ERR_OFFLINE; + } + tevent_req_error(req, ret); + return; + } + + /* connect to the DC we are a member of */ + subreq = ad_domain_info_send(state, state->ev, state->id_ctx->conn, + state->sdap_op, state->sd_ctx->domain_name); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ad_subdomains_refresh_master_done, req); + return; +} + +static void ad_subdomains_refresh_master_done(struct tevent_req *subreq) +{ + struct ad_subdomains_refresh_state *state; + struct tevent_req *req; + const char *realm; + const char *dns; + char *master_sid; + char *flat_name; + char *site = NULL; + errno_t ret; + char *ad_site_override = NULL; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_subdomains_refresh_state); + + ret = ad_domain_info_recv(subreq, state, &flat_name, &master_sid, + &site, &state->forest); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get master domain information " + "[%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->forest == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Forest name was not found, using the one " + "which was already discovered [%s].\n", + state->ad_options->current_forest != NULL ? + state->ad_options->current_forest : + "- not available-"); + if (state->ad_options->current_forest != NULL) { + state->forest = talloc_strdup(state, + state->ad_options->current_forest); + if (state->forest == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy forest name.\n"); + tevent_req_error(req, ENOMEM); + return; + } + } + } + + /* If the site was not discovered during the DNS discovery, e.g. because + * the server name was given explicitly in sssd.conf, we try to set the + * site here. */ + if (state->ad_options->current_site == NULL) { + /* Ignore AD site found in netlogon attribute if specific site is set in + * configuration file. */ + ad_site_override = dp_opt_get_string(state->ad_options->basic, AD_SITE); + if (ad_site_override != NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Ignoring AD site found by DNS discovery: '%s', " + "using configured value: '%s' instead.\n", + site, ad_site_override); + site = ad_site_override; + } + + if (site != NULL) { + ret = ad_options_switch_site(state->ad_options, state->be_ctx, site, + state->forest); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store forest and site name, " + "will try again after a new lookup.\n"); + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Site name currently not available will try again later. " + "The site name can be added manually my setting 'ad_site' " + "in sssd.conf.\n"); + } + } + + realm = dp_opt_get_cstring(state->ad_options->basic, AD_KRB5_REALM); + if (realm == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "Missing realm.\n"); + tevent_req_error(req, EINVAL); + return; + } + + dns = dp_opt_get_cstring(state->ad_options->basic, AD_DOMAIN); + if (dns == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "Missing domain name.\n"); + tevent_req_error(req, EINVAL); + return; + } + + ret = sysdb_master_domain_add_info(state->be_ctx->domain, realm, flat_name, + dns, master_sid, state->forest, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot save master domain info [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + subreq = ad_check_gc_usability_send(state, + state->ev, + state->ad_options, + state->id_ctx->opts, + state->sdap_op, + state->be_ctx->domain->name, + master_sid); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ad_subdomains_refresh_gc_check_done, req); +} + +static void ad_subdomains_refresh_gc_check_done(struct tevent_req *subreq) +{ + struct ad_subdomains_refresh_state *state; + struct tevent_req *req; + const char **subdoms; + const char *ad_domain; + bool is_gc_usable; + errno_t ret; + int i; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_subdomains_refresh_state); + + ret = ad_check_gc_usability_recv(subreq, &is_gc_usable); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to get GC usability status\n"); + is_gc_usable = false; + } + + if (is_gc_usable == false) { + ad_disable_gc(state->ad_options); + } + + /* + * If ad_enabled_domains contains only master domain + * we shouldn't lookup other domains. + */ + if (state->sd_ctx->ad_enabled_domains != NULL) { + if (talloc_array_length(state->sd_ctx->ad_enabled_domains) == 2) { + if (strcasecmp(state->sd_ctx->ad_enabled_domains[0], + state->be_ctx->domain->name) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "No other enabled domain than master.\n"); + + ret = sysdb_list_subdomains(state, state->be_ctx->domain->sysdb, + &subdoms); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to list subdomains " + "[%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + for (i = 0; subdoms[i] != NULL; i++) { + ret = sysdb_subdomain_delete(state->be_ctx->domain->sysdb, + subdoms[i]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to remove subdomain " + "[%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + } + + tevent_req_done(req); + return; + } + } + } + + ad_domain = dp_opt_get_cstring(state->ad_options->basic, AD_DOMAIN); + if (ad_domain == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Missing AD domain name, falling back to sssd domain name\n"); + ad_domain = state->sd_ctx->be_ctx->domain->name; + } + + subreq = ad_get_root_domain_send(state, state->ev, ad_domain, state->forest, + sdap_id_op_handle(state->sdap_op), + state->sd_ctx); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ad_subdomains_refresh_root_done, req); + return; +} + +static void ad_subdomains_refresh_root_done(struct tevent_req *subreq) +{ + struct ad_subdomains_refresh_state *state; + struct tevent_req *req; + struct ad_id_ctx *root_id_ctx; + struct sysdb_attrs *root_attrs; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_subdomains_refresh_state); + + /* Note: For clients joined to the root domain, root_attrs is NULL, + * see ad_get_root_domain_send() + */ + ret = ad_get_root_domain_recv(state, subreq, &root_attrs, &root_id_ctx); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get forest root [%d]: %s\n", + ret, sss_strerror(ret)); + root_attrs = NULL; + root_id_ctx = NULL; + /* We continue to finish sdap_id_op. */ + } + + /* We finish sdap_id_op here since we connect + * to forest root for slave domains. */ + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ad_subdomains_refresh_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + tevent_req_error(req, ERR_OFFLINE); + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + subreq = ad_get_slave_domain_send(state, state->ev, state->sd_ctx, + root_attrs, root_id_ctx); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ad_subdomains_refresh_done, req); + return; +} + +static void ad_subdomains_refresh_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = ad_get_slave_domain_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to get subdomains [%d]: %s\n", + ret, sss_strerror(ret)); + } + + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Unable to refresh subdomains [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Subdomains refreshed.\n"); + tevent_req_done(req); +} + +static errno_t ad_subdomains_refresh_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ad_subdomains_handler_state { + struct dp_reply_std reply; +}; + +static void ad_subdomains_handler_done(struct tevent_req *subreq); + +static struct tevent_req * +ad_subdomains_handler_send(TALLOC_CTX *mem_ctx, + struct ad_subdomains_ctx *sd_ctx, + struct dp_subdomains_data *data, + struct dp_req_params *params) +{ + struct ad_subdomains_handler_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ad_subdomains_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + + if (sd_ctx->last_refreshed > time(NULL) - AD_SUBDOMAIN_REFRESH_LIMIT) { + DEBUG(SSSDBG_TRACE_FUNC, "Subdomains were recently refreshed, " + "nothing to do\n"); + ret = EOK; + goto immediately; + } + + subreq = ad_subdomains_refresh_send(state, params->ev, sd_ctx); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ad_subdomains_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ad_subdomains_handler_done(struct tevent_req *subreq) +{ + struct ad_subdomains_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_subdomains_handler_state); + + ret = ad_subdomains_refresh_recv(subreq); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + tevent_req_done(req); +} + +static errno_t ad_subdomains_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct ad_subdomains_handler_state *state; + + state = tevent_req_data(req, struct ad_subdomains_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} + +static struct tevent_req * +ad_subdomains_ptask_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ad_subdomains_ctx *sd_ctx; + sd_ctx = talloc_get_type(pvt, struct ad_subdomains_ctx); + + return ad_subdomains_refresh_send(mem_ctx, ev, sd_ctx); +} + +static errno_t +ad_subdomains_ptask_recv(struct tevent_req *req) +{ + return ad_subdomains_refresh_recv(req); +} + +errno_t ad_subdomains_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ad_id_ctx *ad_id_ctx, + struct dp_method *dp_methods) +{ + struct ad_subdomains_ctx *sd_ctx; + const char *ad_domain; + const char **ad_enabled_domains = NULL; + time_t period; + time_t offset; + errno_t ret; + + ad_domain = dp_opt_get_string(ad_id_ctx->ad_options->basic, AD_DOMAIN); + + sd_ctx = talloc_zero(mem_ctx, struct ad_subdomains_ctx); + if (sd_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + ret = ad_get_enabled_domains(sd_ctx, ad_id_ctx, ad_domain, + &ad_enabled_domains); + if (ret != EOK) { + return EINVAL; + } + + sd_ctx->be_ctx = be_ctx; + sd_ctx->sdom = ad_id_ctx->sdap_id_ctx->opts->sdom; + sd_ctx->sdap_id_ctx = ad_id_ctx->sdap_id_ctx; + sd_ctx->domain_name = talloc_strdup(sd_ctx, ad_domain); + if (sd_ctx->domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + sd_ctx->ad_enabled_domains = ad_enabled_domains; + sd_ctx->ad_id_ctx = ad_id_ctx; + + dp_set_method(dp_methods, DPM_DOMAINS_HANDLER, + ad_subdomains_handler_send, ad_subdomains_handler_recv, sd_ctx, + struct ad_subdomains_ctx, struct dp_subdomains_data, struct dp_reply_std); + + period = be_ctx->domain->subdomain_refresh_interval; + offset = be_ctx->domain->subdomain_refresh_interval_offset; + ret = be_ptask_create(sd_ctx, be_ctx, period, 0, 0, offset, period, 0, + ad_subdomains_ptask_send, ad_subdomains_ptask_recv, + sd_ctx, + "Subdomains Refresh", + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_SCHEDULE_FROM_LAST, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup ptask " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Ignore, responders will trigger refresh from time to time. */ + } + + ret = ad_subdom_reinit(sd_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not reinitialize subdomains. " + "Users from trusted domains might not be resolved correctly\n"); + /* Ignore this error and try to discover the subdomains later */ + } + + return EOK; +} + +struct ad_check_domain_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct sdap_id_op *sdap_op; + struct ad_id_ctx *dom_id_ctx; + struct sdap_options *opts; + + const char *dom_name; + struct sss_domain_info *dom; + struct sss_domain_info *parent; + struct sdap_domain *sdom; + + char *flat; + char *site; + char *forest; + char *sid; +}; + +static void ad_check_domain_connect_done(struct tevent_req *subreq); +static void ad_check_domain_done(struct tevent_req *subreq); + +static int ad_check_domain_destructor(void *mem) +{ + struct ad_check_domain_state *state = talloc_get_type(mem, + struct ad_check_domain_state); + + if (state->sdom != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Removing sdap domain [%s].\n", + state->dom->name); + sdap_domain_remove(state->opts, state->dom); + /* terminate all requests for this subdomain so we can free it */ + dp_terminate_domain_requests(state->be_ctx->provider, state->dom->name); + talloc_zfree(state->sdom); + } + + if (state->dom != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Removing domain [%s].\n", state->dom->name); + sss_domain_set_state(state->dom, DOM_DISABLED); + DLIST_REMOVE(state->be_ctx->domain->subdomains, state->dom); + talloc_zfree(state->dom); + } + + return 0; +} + +struct tevent_req * +ad_check_domain_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ad_id_ctx *ad_id_ctx, + const char *dom_name, + const char *parent_dom_name) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct ad_check_domain_state *state; + + req = tevent_req_create(mem_ctx, &state, struct ad_check_domain_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->opts = ad_id_ctx->sdap_id_ctx->opts; + state->dom_name = dom_name; + state->parent = NULL; + state->sdom = NULL; + + state->dom = find_domain_by_name(be_ctx->domain, dom_name, true); + if (state->dom == NULL) { + state->parent = find_domain_by_name(be_ctx->domain, parent_dom_name, + true); + if (state->parent == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to find domain object for domain [%s].\n", + parent_dom_name); + ret = ENOENT; + goto immediately; + } + + state->dom = new_subdomain(state->parent, state->parent, dom_name, + dom_name, NULL, NULL, NULL, MPG_DISABLED, false, + state->parent->forest, + NULL, 0, be_ctx->cdb, true); + if (state->dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "new_subdomain() failed.\n"); + ret = EINVAL; + goto immediately; + } + + talloc_set_destructor((TALLOC_CTX *) state, ad_check_domain_destructor); + + DLIST_ADD_END(state->parent->subdomains, state->dom, + struct sss_domain_info *); + + ret = sdap_domain_add(state->opts, state->dom, &state->sdom); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_domain_subdom_add failed.\n"); + goto immediately; + } + + ret = ad_set_search_bases(ad_id_ctx->ad_options->id, state->sdom); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "failed to set ldap search bases for " + "domain '%s'. Will try to use automatically detected search " + "bases.", state->sdom->dom->name); + } + + } + + state->dom_id_ctx = ads_get_dom_id_ctx(be_ctx, ad_id_ctx, state->dom, + state->opts); + if (state->dom_id_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ads_get_dom_id_ctx() failed.\n"); + ret = EINVAL; + goto immediately; + } + + state->sdap_op = sdap_id_op_create(state, + state->dom_id_ctx->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + tevent_req_set_callback(subreq, ad_check_domain_connect_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ad_check_domain_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_check_domain_state *state; + int ret; + int dp_error; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_check_domain_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to connect to LDAP " + "[%d]: %s\n", ret, sss_strerror(ret)); + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, "No AD server is available, " + "cannot get the subdomain list while offline\n"); + ret = ERR_OFFLINE; + } + tevent_req_error(req, ret); + return; + } + + subreq = ad_domain_info_send(state, state->ev, + state->dom_id_ctx->sdap_id_ctx->conn, + state->sdap_op, state->dom_name); + + tevent_req_set_callback(subreq, ad_check_domain_done, req); + + return; +} + +static void ad_check_domain_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ad_check_domain_state *state; + errno_t ret; + + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_check_domain_state); + + ret = ad_domain_info_recv(subreq, state, &state->flat, &state->sid, + &state->site, &state->forest); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to lookup domain information " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "%s %s %s %s.\n", state->flat, state->sid, + state->site, state->forest); + + /* New domain was successfully checked, remove destructor. */ + talloc_set_destructor(state, NULL); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t ad_check_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_flat, + char **_id, + char **_site, + char **_forest) +{ + struct ad_check_domain_state *state = tevent_req_data(req, + struct ad_check_domain_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_flat) { + *_flat = talloc_steal(mem_ctx, state->flat); + } + + if (_site) { + *_site = talloc_steal(mem_ctx, state->site); + } + + if (_forest) { + *_forest = talloc_steal(mem_ctx, state->forest); + } + + if (_id) { + *_id = talloc_steal(mem_ctx, state->sid); + } + + return EOK; +} diff --git a/src/providers/ad/ad_subdomains.h b/src/providers/ad/ad_subdomains.h new file mode 100644 index 0000000..adc286b --- /dev/null +++ b/src/providers/ad/ad_subdomains.h @@ -0,0 +1,36 @@ +/* + SSSD + + AD Subdomains Module + + Authors: + Sumit Bose + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _AD_SUBDOMAINS_H_ +#define _AD_SUBDOMAINS_H_ + +#include "providers/backend.h" +#include "providers/ad/ad_common.h" + +errno_t ad_subdomains_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ad_id_ctx *ad_id_ctx, + struct dp_method *dp_methods); + +#endif /* _AD_SUBDOMAINS_H_ */ diff --git a/src/providers/ad/ad_sudo.c b/src/providers/ad/ad_sudo.c new file mode 100644 index 0000000..51c6451 --- /dev/null +++ b/src/providers/ad/ad_sudo.c @@ -0,0 +1,56 @@ +/* + SSSD + + AD SUDO Provider Initialization functions + + Authors: + Sumit Bose + + Copyright (C) 2014 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_opts.h" +#include "providers/ldap/sdap_sudo.h" + +errno_t ad_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + errno_t ret; + struct ad_options *ad_options; + struct sdap_options *ldap_options; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing sudo AD back end\n"); + + ret = sdap_sudo_init(mem_ctx, + be_ctx, + id_ctx->sdap_id_ctx, + ad_sudorule_map, + dp_methods); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize LDAP SUDO [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ad_options = id_ctx->ad_options; + ldap_options = id_ctx->sdap_id_ctx->opts; + + ad_options->id->sudorule_map = ldap_options->sudorule_map; + return EOK; +} diff --git a/src/providers/backend.h b/src/providers/backend.h new file mode 100644 index 0000000..abf0e91 --- /dev/null +++ b/src/providers/backend.h @@ -0,0 +1,228 @@ +/* + SSSD + + Data Provider, private header file + + Copyright (C) Simo Sorce 2008 + + 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 3 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; 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, see . +*/ + +#ifndef __DP_BACKEND_H__ +#define __DP_BACKEND_H__ + +#include "providers/data_provider.h" +#include "providers/fail_over.h" +#include "providers/be_refresh.h" +#include "providers/data_provider/dp.h" +#include "util/child_common.h" +#include "util/session_recording.h" +#include "db/sysdb.h" + +/* a special token, if used in place of the hostname, denotes that real + * hostnames should be looked up from DNS using SRV requests + */ +#define BE_SRV_IDENTIFIER "_srv_" + +struct be_ctx; + +typedef void (*be_callback_t)(void *); + +struct be_resolv_ctx { + struct resolv_ctx *resolv; + struct dp_option *opts; + + enum restrict_family family_order; +}; + +struct be_svc_data { + struct be_svc_data *prev; + struct be_svc_data *next; + + const char *name; + struct fo_service *fo_service; + + char *last_good_srv; + int last_good_port; + time_t last_status_change; + bool run_callbacks; + + struct be_svc_callback *callbacks; + struct fo_server *first_resolved; +}; + +struct be_failover_ctx { + struct fo_ctx *fo_ctx; + struct be_resolv_ctx *be_res; + + struct be_svc_data *svcs; + struct tevent_timer *primary_server_handler; +}; + +struct be_cb; + +struct be_ctx { + struct tevent_context *ev; + struct confdb_ctx *cdb; + struct sss_domain_info *domain; + const char *identity; + const char *conf_path; + const char *sbus_name; + uid_t uid; + gid_t gid; + char override_space; + struct session_recording_conf sr_conf; + struct be_failover_ctx *be_fo; + struct be_resolv_ctx *be_res; + struct file_watch_ctx *file_ctx; + + /* Functions to be invoked when the + * backend goes online or offline + */ + struct be_cb *online_cb_list; + bool run_online_cb; + struct be_cb *offline_cb_list; + bool run_offline_cb; + struct be_cb *reconnect_cb_list; + /* In contrast to online_cb_list which are only run if the backend is + * offline the unconditional_online_cb_list should be run whenever the + * backend receives a request to go online. The typical use case is to + * reset timers independently of the state of the backend. */ + struct be_cb *unconditional_online_cb_list; + + bool offline; + /* Periodically check if we can go online. */ + struct be_ptask *check_if_online_ptask; + + struct sbus_connection *mon_conn; + + struct be_refresh_ctx *refresh_ctx; + + size_t check_online_ref_count; + int check_online_retry_delay; + + struct data_provider *provider; + + /* Indicates whether the last state of the DP that has been logged is + * DP_ERR_OK or DP_ERR_OFFLINE. The only usage of this var, so far, is + * to log the DP status without spamming the syslog/journal. */ + int last_dp_state; +}; + +bool be_is_offline(struct be_ctx *ctx); +void be_mark_offline(struct be_ctx *ctx); +void be_mark_dom_offline(struct sss_domain_info *dom, struct be_ctx *ctx); + +int be_add_reconnect_cb(TALLOC_CTX *mem_ctx, + struct be_ctx *ctx, + be_callback_t cb, + void *pvt, + struct be_cb **reconnect_cb); +void be_run_reconnect_cb(struct be_ctx *be); + +int be_add_online_cb(TALLOC_CTX *mem_ctx, + struct be_ctx *ctx, + be_callback_t cb, + void *pvt, + struct be_cb **online_cb); +void be_run_online_cb(struct be_ctx *be); +int be_add_unconditional_online_cb(TALLOC_CTX *mem_ctx, struct be_ctx *ctx, + be_callback_t cb, void *pvt, + struct be_cb **unconditional_online_cb); +void be_run_unconditional_online_cb(struct be_ctx *be); + +int be_add_offline_cb(TALLOC_CTX *mem_ctx, + struct be_ctx *ctx, + be_callback_t cb, + void *pvt, + struct be_cb **online_cb); +void be_run_offline_cb(struct be_ctx *be); + +/* from data_provider_fo.c */ +enum be_fo_protocol { + BE_FO_PROTO_TCP, + BE_FO_PROTO_UDP, + BE_FO_PROTO_SENTINEL +}; + +typedef void (be_svc_callback_fn_t)(void *, struct fo_server *); + +int be_init_failover(struct be_ctx *ctx); +int be_fo_is_srv_identifier(const char *server); +int be_fo_add_service(struct be_ctx *ctx, const char *service_name, + datacmp_fn user_data_cmp); +int be_fo_service_add_callback(TALLOC_CTX *memctx, + struct be_ctx *ctx, const char *service_name, + be_svc_callback_fn_t *fn, void *private_data); +int be_fo_get_server_count(struct be_ctx *ctx, const char *service_name); + +void be_fo_set_srv_lookup_plugin(struct be_ctx *ctx, + fo_srv_lookup_plugin_send_t send_fn, + fo_srv_lookup_plugin_recv_t recv_fn, + void *pvt, + const char *plugin_name); + +errno_t be_fo_set_dns_srv_lookup_plugin(struct be_ctx *be_ctx, + const char *hostname); + +int be_fo_add_srv_server(struct be_ctx *ctx, + const char *service_name, + const char *query_service, + const char *default_discovery_domain, + enum be_fo_protocol proto, + bool proto_fallback, void *user_data); +int be_fo_add_server(struct be_ctx *ctx, const char *service_name, + const char *server, int port, void *user_data, + bool primary); + +struct tevent_req *be_resolve_server_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct be_ctx *ctx, + const char *service_name, + bool first_try); +int be_resolve_server_recv(struct tevent_req *req, + TALLOC_CTX *ref_ctx, + struct fo_server **srv); + +#define be_fo_set_port_status(ctx, service_name, server, status) \ + _be_fo_set_port_status(ctx, service_name, server, status, \ + __LINE__, __FILE__, __FUNCTION__) + +void _be_fo_set_port_status(struct be_ctx *ctx, + const char *service_name, + struct fo_server *server, + enum port_status status, + int line, + const char *file, + const char *function); + +/* + * Instruct fail-over to try next server on the next connect attempt. + * Should be used after connection to service was unexpectedly dropped + * but there is no authoritative information on whether active server is down. + */ +void be_fo_try_next_server(struct be_ctx *ctx, const char *service_name); + +int be_fo_run_callbacks_at_next_request(struct be_ctx *ctx, + const char *service_name); + +void reset_fo(struct be_ctx *be_ctx); +void be_fo_reset_svc(struct be_ctx *be_ctx, const char *svc_name); + +const char *be_fo_get_active_server_name(struct be_ctx *ctx, + const char *service_name); + +errno_t be_res_init(struct be_ctx *ctx); + +#endif /* __DP_BACKEND_H___ */ diff --git a/src/providers/be_dyndns.c b/src/providers/be_dyndns.c new file mode 100644 index 0000000..2c655ef --- /dev/null +++ b/src/providers/be_dyndns.c @@ -0,0 +1,1371 @@ +/* + SSSD + + dp_dyndns.c + + Authors: + Stephen Gallagher + Jakub Hrozek + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "util/util.h" +#include "confdb/confdb.h" +#include "util/child_common.h" +#include "providers/data_provider.h" +#include "providers/backend.h" +#include "providers/be_dyndns.h" +#include "resolv/async_resolv.h" + +#ifndef DYNDNS_TIMEOUT +#define DYNDNS_TIMEOUT 15 +#endif /* DYNDNS_TIMEOUT */ + +/* MASK represents special value for matching all interfaces */ +#define MASK "*" + +struct sss_iface_addr { + struct sss_iface_addr *next; + struct sss_iface_addr *prev; + + struct sockaddr *addr; +}; + +struct sockaddr * +sss_iface_addr_get_address(struct sss_iface_addr *address) +{ + if (address == NULL) { + return NULL; + } + + return address->addr; +} + +struct sss_iface_addr *sss_iface_addr_get_next(struct sss_iface_addr *address) +{ + if (address) { + return address->next; + } + + return NULL; +} + +void sss_iface_addr_concatenate(struct sss_iface_addr **list, + struct sss_iface_addr *list2) +{ + DLIST_CONCATENATE((*list), list2, struct sss_iface_addr*); +} + +static errno_t addr_to_str(struct sockaddr *addr, + char *dst, size_t size) +{ + const void *src; + const char *res; + errno_t ret; + + switch(addr->sa_family) { + case AF_INET: + src = &(((struct sockaddr_in *)addr)->sin_addr); + break; + case AF_INET6: + src = &(((struct sockaddr_in6 *)addr)->sin6_addr); + break; + default: + ret = ERR_ADDR_FAMILY_NOT_SUPPORTED; + goto done; + } + + res = inet_ntop(addr->sa_family, src, dst, size); + if (res == NULL) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, "inet_ntop failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + return ret; +} + +errno_t +sss_iface_addr_list_as_str_list(TALLOC_CTX *mem_ctx, + struct sss_iface_addr *ifaddr_list, + char ***_straddrs) +{ + struct sss_iface_addr *ifaddr; + size_t count; + int ai; + char **straddrs; + char ip_addr[INET6_ADDRSTRLEN]; + errno_t ret; + + count = 0; + DLIST_FOR_EACH(ifaddr, ifaddr_list) { + count++; + } + + straddrs = talloc_array(mem_ctx, char *, count+1); + if (straddrs == NULL) { + return ENOMEM; + } + + ai = 0; + DLIST_FOR_EACH(ifaddr, ifaddr_list) { + + ret = addr_to_str(ifaddr->addr, ip_addr, INET6_ADDRSTRLEN); + if (ret == ERR_ADDR_FAMILY_NOT_SUPPORTED) { + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "addr_to_str failed: %d:[%s],\n", + ret, sss_strerror(ret)); + goto fail; + } + + straddrs[ai] = talloc_strdup(straddrs, ip_addr); + if (straddrs[ai] == NULL) { + ret = ENOMEM; + goto fail; + } + ai++; + } + + straddrs[count] = NULL; + *_straddrs = straddrs; + return EOK; + +fail: + talloc_free(straddrs); + return ret; +} + +static bool +ok_for_dns(struct sockaddr *sa) +{ + struct sockaddr_in sa4; + struct sockaddr_in6 sa6; + + switch (sa->sa_family) { + case AF_INET6: + memcpy(&sa6, sa, sizeof(struct sockaddr_in6)); + return check_ipv6_addr(&sa6.sin6_addr, SSS_NO_SPECIAL); + case AF_INET: + memcpy(&sa4, sa, sizeof(struct sockaddr_in)); + return check_ipv4_addr(&sa4.sin_addr, SSS_NO_SPECIAL); + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown address family\n"); + return false; + } + + return true; +} + +static bool supported_address_family(sa_family_t sa_family) +{ + return sa_family == AF_INET || sa_family == AF_INET6; +} + +static bool matching_name(const char *ifname, const char *ifname2) +{ + return (strcmp(MASK, ifname) == 0) || (strcasecmp(ifname, ifname2) == 0); +} + +/* Collect IP addresses associated with an interface */ +errno_t +sss_iface_addr_list_get(TALLOC_CTX *mem_ctx, const char *ifname, + struct sss_iface_addr **_addrlist) +{ + struct ifaddrs *ifaces = NULL; + struct ifaddrs *ifa; + errno_t ret; + size_t addrsize; + struct sss_iface_addr *address; + struct sss_iface_addr *addrlist = NULL; + + /* Get the IP addresses associated with the + * specified interface + */ + errno = 0; + ret = getifaddrs(&ifaces); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Could not read interfaces [%d][%s]\n", ret, strerror(ret)); + goto done; + } + + for (ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) { + /* Some interfaces don't have an ifa_addr */ + if (!ifa->ifa_addr) continue; + + /* Add IP addresses to the list */ + if (supported_address_family(ifa->ifa_addr->sa_family) + && matching_name(ifname, ifa->ifa_name) + && ok_for_dns(ifa->ifa_addr)) { + + /* Add this address to the IP address list */ + address = talloc_zero(mem_ctx, struct sss_iface_addr); + if (!address) { + ret = ENOMEM; + goto done; + } + + addrsize = ifa->ifa_addr->sa_family == AF_INET ? \ + sizeof(struct sockaddr_in) : \ + sizeof(struct sockaddr_in6); + + address->addr = talloc_memdup(address, ifa->ifa_addr, + addrsize); + if (address->addr == NULL) { + ret = ENOMEM; + goto done; + } + + /* steal old dlist to the new head */ + talloc_steal(address, addrlist); + DLIST_ADD(addrlist, address); + } + } + + if (addrlist != NULL) { + /* OK, some result was found */ + ret = EOK; + *_addrlist = addrlist; + } else { + /* No result was found */ + DEBUG(SSSDBG_TRACE_FUNC, + "No IP usable for DNS was found for interface: %s.\n", ifname); + ret = ENOENT; + } + +done: + freeifaddrs(ifaces); + return ret; +} + +static char * +nsupdate_msg_add_fwd(char *update_msg, struct sss_iface_addr *addresses, + const char *hostname, int ttl, uint8_t remove_af, bool update_per_family) +{ + struct sss_iface_addr *new_record; + char ip_addr[INET6_ADDRSTRLEN]; + char *updateipv4 = talloc_strdup(update_msg, ""); + char *updateipv6 = talloc_strdup(update_msg, ""); + errno_t ret; + + /* Remove existing entries as needed */ + if (remove_af & DYNDNS_REMOVE_A) { + updateipv4 = talloc_asprintf_append(updateipv4, + "update delete %s. in A\n", + hostname); + if (updateipv4 == NULL) { + return NULL; + } + } + + if (remove_af & DYNDNS_REMOVE_AAAA) { + updateipv6 = talloc_asprintf_append(updateipv6, + "update delete %s. in AAAA\n", + hostname); + if (updateipv6 == NULL) { + return NULL; + } + } + + DLIST_FOR_EACH(new_record, addresses) { + ret = addr_to_str(new_record->addr, ip_addr, INET6_ADDRSTRLEN); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "addr_to_str failed: %d:[%s],\n", + ret, sss_strerror(ret)); + return NULL; + } + + switch (new_record->addr->sa_family) { + case AF_INET: + updateipv4 = talloc_asprintf_append(updateipv4, + "update add %s. %d in %s %s\n", + hostname, ttl, "A", ip_addr); + if (updateipv4 == NULL) { + return NULL; + } + + break; + case AF_INET6: + updateipv6 = talloc_asprintf_append(updateipv6, + "update add %s. %d in %s %s\n", + hostname, ttl, "AAAA", ip_addr); + if (updateipv6 == NULL) { + return NULL; + } + + break; + } + } + + if (update_per_family && updateipv4[0] && updateipv6[0]) { + /* update per family and both families present */ + return talloc_asprintf_append(update_msg, + "%s" + "send\n" + "%s" + "send\n", + updateipv4, + updateipv6); + } + + return talloc_asprintf_append(update_msg, + "%s" + "%s" + "send\n", + updateipv4, + updateipv6); +} + +static uint8_t *nsupdate_convert_address(struct sockaddr *add_address) +{ + uint8_t *addr; + + switch(add_address->sa_family) { + case AF_INET: + addr = (uint8_t *) &((struct sockaddr_in *) add_address)->sin_addr; + break; + case AF_INET6: + addr = (uint8_t *) &((struct sockaddr_in6 *) add_address)->sin6_addr; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown address family\n"); + addr = NULL; + break; + } + + return addr; +} + +static char * +nsupdate_msg_add_ptr(char *update_msg, struct sss_iface_addr *addresses, + const char *hostname, int ttl, uint8_t remove_af, + bool update_per_family) +{ + char *updateipv4 = talloc_strdup(update_msg, ""); + char *updateipv6 = talloc_strdup(update_msg, ""); + char *ptr; + struct sss_iface_addr *address_it; + uint8_t *addr; + + if (!updateipv4 || !updateipv6) { + return NULL; + } + + DLIST_FOR_EACH(address_it, addresses) { + addr = nsupdate_convert_address(address_it->addr); + if (addr == NULL) { + return NULL; + } + + ptr = resolv_get_string_ptr_address(update_msg, address_it->addr->sa_family, + addr); + if (ptr == NULL) { + return NULL; + } + + switch (address_it->addr->sa_family) { + case AF_INET: + if (remove_af & DYNDNS_REMOVE_A) { + updateipv4 = talloc_asprintf_append(updateipv4, + "update delete %s in PTR\n", + ptr); + if (updateipv4 == NULL) { + return NULL; + } + } + + updateipv4 = talloc_asprintf_append(updateipv4, + "update add %s %d in PTR %s.\nsend\n", + ptr, ttl, hostname); + break; + case AF_INET6: + if (remove_af & DYNDNS_REMOVE_AAAA) { + updateipv6 = talloc_asprintf_append(updateipv6, + "update delete %s in PTR\n", + ptr); + if (updateipv6 == NULL) { + return NULL; + } + } + updateipv6 = talloc_asprintf_append(updateipv6, + "update add %s %d in PTR %s.\nsend\n", + ptr, ttl, hostname); + break; + } + + talloc_free(ptr); + if (!updateipv4 || !updateipv6) { + return NULL; + } + } + + return talloc_asprintf_append(update_msg, + "%s" + "%s", + updateipv4, + updateipv6); +} + +static char * +nsupdate_msg_add_realm_cmd(TALLOC_CTX *mem_ctx, const char *realm) +{ + if (realm != NULL) { + return talloc_asprintf(mem_ctx, "realm %s\n", realm); + } else { + return talloc_asprintf(mem_ctx, "\n"); + } +} + +static char * +nsupdate_msg_create_common(TALLOC_CTX *mem_ctx, const char *realm, + const char *servername) +{ + char *realm_directive; + char *update_msg; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return NULL; + + realm_directive = nsupdate_msg_add_realm_cmd(tmp_ctx, realm); + if (!realm_directive) { + goto fail; + } + + /* The realm_directive would now either contain an empty string or be + * completely empty so we don't need to add another newline here + */ + if (servername) { + DEBUG(SSSDBG_FUNC_DATA, + "Creating update message for server [%s] and realm [%s].\n", + servername, realm); + + /* Add the server, realm and headers */ + update_msg = talloc_asprintf(tmp_ctx, "server %s\n%s", + servername, realm_directive); + } else if (realm != NULL) { + DEBUG(SSSDBG_FUNC_DATA, + "Creating update message for realm [%s].\n", realm); + /* Add the realm headers */ + update_msg = talloc_asprintf(tmp_ctx, "%s", realm_directive); + } else { + DEBUG(SSSDBG_FUNC_DATA, + "Creating update message for auto-discovered realm.\n"); + update_msg = talloc_asprintf(tmp_ctx, "%s", realm_directive); + } + talloc_free(realm_directive); + if (update_msg == NULL) { + goto fail; + } + + update_msg = talloc_steal(mem_ctx, update_msg); + talloc_free(tmp_ctx); + return update_msg; + +fail: + talloc_free(tmp_ctx); + return NULL; +} + +errno_t +be_nsupdate_create_fwd_msg(TALLOC_CTX *mem_ctx, const char *realm, + const char *servername, + const char *hostname, const unsigned int ttl, + uint8_t remove_af, struct sss_iface_addr *addresses, + bool update_per_family, + char **_update_msg) +{ + int ret; + char *update_msg; + TALLOC_CTX *tmp_ctx; + + /* in some cases realm could have been NULL if we weren't using TSIG */ + if (hostname == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + update_msg = nsupdate_msg_create_common(tmp_ctx, realm, servername); + if (update_msg == NULL) { + ret = ENOMEM; + goto done; + } + + update_msg = nsupdate_msg_add_fwd(update_msg, addresses, hostname, + ttl, remove_af, update_per_family); + if (update_msg == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + " -- Begin nsupdate message -- \n" + "%s" + " -- End nsupdate message -- \n", + update_msg); + + ret = ERR_OK; + *_update_msg = talloc_steal(mem_ctx, update_msg); +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +be_nsupdate_create_ptr_msg(TALLOC_CTX *mem_ctx, const char *realm, + const char *servername, + const char *hostname, const unsigned int ttl, + uint8_t remove_af, struct sss_iface_addr *addresses, + bool update_per_family, + char **_update_msg) +{ + errno_t ret; + char *update_msg; + TALLOC_CTX *tmp_ctx; + + /* in some cases realm could have been NULL if we weren't using TSIG */ + if (hostname == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + update_msg = nsupdate_msg_create_common(tmp_ctx, realm, servername); + if (update_msg == NULL) { + ret = ENOMEM; + goto done; + } + + update_msg = nsupdate_msg_add_ptr(update_msg, addresses, hostname, + ttl, remove_af, update_per_family); + if (update_msg == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + " -- Begin nsupdate message -- \n" + "%s" + " -- End nsupdate message -- \n", + update_msg); + + ret = ERR_OK; + *_update_msg = talloc_steal(mem_ctx, update_msg); + +done: + talloc_free(tmp_ctx); + return ret; +} + +struct nsupdate_get_addrs_state { + struct tevent_context *ev; + struct be_resolv_ctx *be_res; + enum host_database *db; + const char *hostname; + + /* Use sss_addr in this request */ + struct sss_iface_addr *addrlist; + size_t count; +}; + +static void nsupdate_get_addrs_done(struct tevent_req *subreq); + +struct tevent_req * +nsupdate_get_addrs_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_resolv_ctx *be_res, + const char *hostname) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct nsupdate_get_addrs_state *state; + + req = tevent_req_create(mem_ctx, &state, struct nsupdate_get_addrs_state); + if (req == NULL) { + return NULL; + } + state->be_res = be_res; + state->ev = ev; + state->hostname = talloc_strdup(state, hostname); + if (state->hostname == NULL) { + ret = ENOMEM; + goto done; + } + + state->db = talloc_array(state, enum host_database, 2); + if (state->db == NULL) { + ret = ENOMEM; + goto done; + } + state->db[0] = DB_DNS; + state->db[1] = DB_SENTINEL; + + subreq = resolv_gethostbyname_send(state, ev, be_res->resolv, hostname, + state->be_res->family_order, + state->db); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, nsupdate_get_addrs_done, req); + + ret = ERR_OK; +done: + if (ret != ERR_OK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void +nsupdate_get_addrs_done(struct tevent_req *subreq) +{ + errno_t ret; + size_t count; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct nsupdate_get_addrs_state *state = tevent_req_data(req, + struct nsupdate_get_addrs_state); + struct resolv_hostent *rhostent; + struct sss_iface_addr *addr; + int i; + int resolv_status; + enum restrict_family retry_family_order; + + ret = resolv_gethostbyname_recv(subreq, state, &resolv_status, NULL, + &rhostent); + talloc_zfree(subreq); + + /* If the retry did not match, simply quit */ + if (ret == ENOENT) { + /* If the resolver is set to honor both address families + * it automatically retries the other one internally, so ENOENT + * means neither matched and we can simply quit. + */ + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not resolve address for this machine, error [%d]: %s, " + "resolver returned: [%d]: %s\n", ret, sss_strerror(ret), + resolv_status, resolv_strerror(resolv_status)); + goto done; + } + + /* EOK */ + + if (rhostent->addr_list) { + for (count=0; rhostent->addr_list[count]; count++); + } else { + /* The address list is NULL. This is probably a bug in + * c-ares, but we need to handle it gracefully. + */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Lookup of [%s] returned no addresses. Skipping.\n", + rhostent->name); + count = 0; + } + + for (i=0; i < count; i++) { + addr = talloc(state, struct sss_iface_addr); + if (addr == NULL) { + ret = ENOMEM; + goto done; + } + + addr->addr = resolv_get_sockaddr_address_index(addr, rhostent, 0, i, NULL); + if (addr->addr == NULL) { + ret = ENOMEM; + goto done; + } + + if (state->addrlist) { + talloc_steal(state->addrlist, addr); + } + + /* steal old dlist to the new head */ + talloc_steal(addr, state->addrlist); + DLIST_ADD(state->addrlist, addr); + } + state->count += count; + + /* If the resolver is set to honor both address families + * and the first one matched, retry the second one to + * get the complete list. + */ + if (((state->be_res->family_order == IPV4_FIRST && + rhostent->family == AF_INET) || + (state->be_res->family_order == IPV6_FIRST && + rhostent->family == AF_INET6))) { + + retry_family_order = (state->be_res->family_order == IPV4_FIRST) ? \ + IPV6_ONLY : \ + IPV4_ONLY; + + subreq = resolv_gethostbyname_send(state, state->ev, + state->be_res->resolv, + state->hostname, + retry_family_order, + state->db); + if (!subreq) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, nsupdate_get_addrs_done, req); + return; + } + + /* The second address matched either immediately or after a retry. + * No need to retry again. */ + ret = EOK; + +done: + if (ret == EOK) { + /* All done */ + tevent_req_done(req); + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, + "nsupdate_get_addrs_done failed: [%d]: [%s]\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + } + /* EAGAIN - another lookup in progress */ +} + +errno_t +nsupdate_get_addrs_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sss_iface_addr **_addrlist, + size_t *_count) +{ + struct nsupdate_get_addrs_state *state = tevent_req_data(req, + struct nsupdate_get_addrs_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_addrlist) { + *_addrlist = talloc_steal(mem_ctx, state->addrlist); + } + + if (_count) { + *_count = state->count; + } + + return EOK; +} + +/* Write the nsupdate_msg into the already forked child, wait until + * the child finishes + * + * This is not a typical tevent_req styled request as it ends either after + * a timeout or when the child finishes operation. + */ +struct nsupdate_child_state { + int pipefd_to_child; + struct tevent_timer *timeout_handler; + struct sss_child_ctx_old *child_ctx; + + int child_status; +}; + +static void +nsupdate_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); +static void +nsupdate_child_handler(int child_status, + struct tevent_signal *sige, + void *pvt); + +static void nsupdate_child_stdin_done(struct tevent_req *subreq); + +static struct tevent_req * +nsupdate_child_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int pipefd_to_child, + pid_t child_pid, + char *child_stdin) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct nsupdate_child_state *state; + struct timeval tv; + + req = tevent_req_create(mem_ctx, &state, struct nsupdate_child_state); + if (req == NULL) { + close(pipefd_to_child); + return NULL; + } + state->pipefd_to_child = pipefd_to_child; + + /* Set up SIGCHLD handler */ + ret = child_handler_setup(ev, child_pid, nsupdate_child_handler, req, + &state->child_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_DYNDNS_FAILED; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(DYNDNS_TIMEOUT, 0); + state->timeout_handler = tevent_add_timer(ev, req, tv, + nsupdate_child_timeout, req); + if(state->timeout_handler == NULL) { + ret = ERR_DYNDNS_FAILED; + goto done; + } + + /* Write the update message to the nsupdate child */ + subreq = write_pipe_send(req, ev, + (uint8_t *) child_stdin, + strlen(child_stdin)+1, + state->pipefd_to_child); + if (subreq == NULL) { + ret = ERR_DYNDNS_FAILED; + goto done; + } + tevent_req_set_callback(subreq, nsupdate_child_stdin_done, req); + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void +nsupdate_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = + talloc_get_type(pvt, struct tevent_req); + struct nsupdate_child_state *state = + tevent_req_data(req, struct nsupdate_child_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for dynamic DNS update\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_DYNDNS_TIMEOUT); +} + +static void +nsupdate_child_stdin_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct nsupdate_child_state *state = + tevent_req_data(req, struct nsupdate_child_state); + + /* Verify that the buffer was sent, then return + * and wait for the sigchld handler to finish. + */ + DEBUG(SSSDBG_TRACE_LIBS, "Sending nsupdate data complete\n"); + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Sending nsupdate data failed [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ERR_DYNDNS_FAILED); + return; + } + + PIPE_FD_CLOSE(state->pipefd_to_child); + + /* Now either wait for the timeout to fire or the child + * to finish + */ +} + +static void +nsupdate_child_handler(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct nsupdate_child_state *state = + tevent_req_data(req, struct nsupdate_child_state); + + state->child_status = child_status; + + if (WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Dynamic DNS child failed with status [%d]\n", child_status); + tevent_req_error(req, ERR_DYNDNS_FAILED); + return; + } + + if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + "Dynamic DNS child was terminated by signal [%d]\n", + WTERMSIG(child_status)); + tevent_req_error(req, ERR_DYNDNS_FAILED); + return; + } + + tevent_req_done(req); +} + +static errno_t +nsupdate_child_recv(struct tevent_req *req, int *child_status) +{ + struct nsupdate_child_state *state = + tevent_req_data(req, struct nsupdate_child_state); + + *child_status = state->child_status; + + PIPE_FD_CLOSE(state->pipefd_to_child); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return ERR_OK; +} + +/* Fork a nsupdate child, write the nsupdate_msg into stdin and wait for the child + * to finish one way or another + */ +struct be_nsupdate_state { + int child_status; +}; + +static void be_nsupdate_done(struct tevent_req *subreq); +static char **be_nsupdate_args(TALLOC_CTX *mem_ctx, + enum be_nsupdate_auth auth_type, + bool force_tcp); + +struct tevent_req *be_nsupdate_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + enum be_nsupdate_auth auth_type, + char *nsupdate_msg, + bool force_tcp) +{ + int pipefd_to_child[2] = PIPE_INIT; + pid_t child_pid; + errno_t ret; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct be_nsupdate_state *state; + char **args; + int debug_fd; + + req = tevent_req_create(mem_ctx, &state, struct be_nsupdate_state); + if (req == NULL) { + return NULL; + } + state->child_status = 0; + + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + + if (child_pid == 0) { /* child */ + PIPE_FD_CLOSE(pipefd_to_child[1]); + ret = dup2(pipefd_to_child[0], STDIN_FILENO); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "dup2 failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + if (debug_level >= SSSDBG_TRACE_LIBS) { + debug_fd = get_fd_from_debug_file(); + ret = dup2(debug_fd, STDERR_FILENO); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "dup2 failed [%d][%s].\n", ret, strerror(ret)); + /* stderr is not fatal */ + } + } + + args = be_nsupdate_args(state, auth_type, force_tcp); + if (args == NULL) { + ret = ENOMEM; + goto done; + } + + errno = 0; + execv(NSUPDATE_PATH, args); + /* The child should never end up here */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "execv failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } else if (child_pid > 0) { /* parent */ + PIPE_FD_CLOSE(pipefd_to_child[0]); + + /* the nsupdate_child request now owns the pipefd and is responsible + * for closing it + */ + subreq = nsupdate_child_send(state, ev, pipefd_to_child[1], + child_pid, nsupdate_msg); + if (subreq == NULL) { + ret = ERR_DYNDNS_FAILED; + goto done; + } + tevent_req_set_callback(subreq, be_nsupdate_done, req); + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fork failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + ret = EOK; +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_to_child); + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static char ** +be_nsupdate_args(TALLOC_CTX *mem_ctx, + enum be_nsupdate_auth auth_type, + bool force_tcp) +{ + char **argv; + int argc = 0; + + argv = talloc_zero_array(mem_ctx, char *, 6); + if (argv == NULL) { + return NULL; + } + + argv[argc] = talloc_strdup(argv, NSUPDATE_PATH); + if (argv[argc] == NULL) { + goto fail; + } + argc++; + + switch (auth_type) { + case BE_NSUPDATE_AUTH_NONE: + DEBUG(SSSDBG_FUNC_DATA, "nsupdate auth type: none\n"); + break; + case BE_NSUPDATE_AUTH_GSS_TSIG: + DEBUG(SSSDBG_FUNC_DATA, "nsupdate auth type: GSS-TSIG\n"); + argv[argc] = talloc_strdup(argv, "-g"); + if (argv[argc] == NULL) { + goto fail; + } + argc++; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown nsupdate auth type %d\n", auth_type); + goto fail; + } + + if (force_tcp) { + DEBUG(SSSDBG_FUNC_DATA, "TCP is set to on\n"); + argv[argc] = talloc_strdup(argv, "-v"); + if (argv[argc] == NULL) { + goto fail; + } + argc++; + } + + if (debug_level >= SSSDBG_TRACE_LIBS) { + argv[argc] = talloc_strdup(argv, "-d"); + if (argv[argc] == NULL) { + goto fail; + } + argc++; + } + + if (debug_level >= SSSDBG_TRACE_INTERNAL) { + argv[argc] = talloc_strdup(argv, "-D"); + if (argv[argc] == NULL) { + goto fail; + } + argc++; + } + + return argv; + +fail: + talloc_free(argv); + return NULL; +} + +static void +be_nsupdate_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct be_nsupdate_state *state = + tevent_req_data(req, struct be_nsupdate_state); + errno_t ret; + + ret = nsupdate_child_recv(subreq, &state->child_status); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "nsupdate child execution failed [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_FUNC_DATA, + "nsupdate child status: %d\n", state->child_status); + tevent_req_done(req); +} + +errno_t +be_nsupdate_recv(struct tevent_req *req, int *child_status) +{ + struct be_nsupdate_state *state = + tevent_req_data(req, struct be_nsupdate_state); + + *child_status = state->child_status; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t +be_nsupdate_check(void) +{ + errno_t ret; + struct stat stat_buf; + + /* Ensure that nsupdate exists */ + errno = 0; + ret = stat(NSUPDATE_PATH, &stat_buf); + if (ret == -1) { + ret = errno; + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "%s does not exist. Dynamic DNS updates disabled\n", + NSUPDATE_PATH); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Could not set up dynamic DNS updates: [%d][%s]\n", + ret, strerror(ret)); + } + } + + return ret; +} + +static struct dp_option default_dyndns_opts[] = { + { "dyndns_update", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_update_per_family", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "dyndns_refresh_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER }, + { "dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "dyndns_ttl", DP_OPT_NUMBER, { .number = 1200 }, NULL_NUMBER }, + { "dyndns_update_ptr", DP_OPT_BOOL, BOOL_TRUE, BOOL_FALSE }, + { "dyndns_force_tcp", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_auth", DP_OPT_STRING, { "gss-tsig" }, NULL_STRING }, + { "dyndns_auth_ptr", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "dyndns_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + + DP_OPTION_TERMINATOR +}; + +errno_t +be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx, + struct dp_option *defopts, + struct be_nsupdate_ctx **_ctx) +{ + errno_t ret; + struct dp_option *src_opts; + struct be_nsupdate_ctx *ctx; + char *strauth; + + ctx = talloc_zero(mem_ctx, struct be_nsupdate_ctx); + if (ctx == NULL) return ENOMEM; + + src_opts = defopts ? defopts : default_dyndns_opts; + + ret = dp_get_options(ctx, be_ctx->cdb, be_ctx->conf_path, + src_opts, DP_OPT_DYNDNS, &ctx->opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot retrieve dynamic DNS options\n"); + return ret; + } + + strauth = dp_opt_get_string(ctx->opts, DP_OPT_DYNDNS_AUTH); + if (strcasecmp(strauth, "gss-tsig") == 0) { + ctx->auth_type = BE_NSUPDATE_AUTH_GSS_TSIG; + } else if (strcasecmp(strauth, "none") == 0) { + ctx->auth_type = BE_NSUPDATE_AUTH_NONE; + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unknown dyndns auth type %s\n", strauth); + return EINVAL; + } + + strauth = dp_opt_get_string(ctx->opts, DP_OPT_DYNDNS_AUTH_PTR); + if (strauth == NULL) { + ctx->auth_ptr_type = ctx->auth_type; + } else if (strcasecmp(strauth, "gss-tsig") == 0) { + ctx->auth_ptr_type = BE_NSUPDATE_AUTH_GSS_TSIG; + } else if (strcasecmp(strauth, "none") == 0) { + ctx->auth_ptr_type = BE_NSUPDATE_AUTH_NONE; + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unknown dyndns ptr auth type %s\n", strauth); + return EINVAL; + } + + *_ctx = ctx; + return ERR_OK; +} + +static bool match_ip(const struct sockaddr *sa, + const struct sockaddr *sb) +{ + size_t addrsize; + bool res; + const void *addr_a; + const void *addr_b; + + if (sa->sa_family == AF_INET) { + addrsize = sizeof(struct in_addr); + addr_a = (const void *) &((const struct sockaddr_in *) sa)->sin_addr; + addr_b = (const void *) &((const struct sockaddr_in *) sb)->sin_addr; + } else if (sa->sa_family == AF_INET6) { + addrsize = sizeof(struct in6_addr); + addr_a = (const void *) &((const struct sockaddr_in6 *) sa)->sin6_addr; + addr_b = (const void *) &((const struct sockaddr_in6 *) sb)->sin6_addr; + } else { + res = false; + goto done; + } + + if (sa->sa_family != sb->sa_family) { + res = false; + goto done; + } + + res = memcmp(addr_a, addr_b, addrsize) == 0; + +done: + return res; +} + +static errno_t find_iface_by_addr(TALLOC_CTX *mem_ctx, + const struct sockaddr *ss, + const char **_iface_name) +{ + struct ifaddrs *ifaces = NULL; + struct ifaddrs *ifa; + errno_t ret; + + ret = getifaddrs(&ifaces); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Could not read interfaces [%d][%s]\n", ret, sss_strerror(ret)); + goto done; + } + + for (ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) { + + /* Some interfaces don't have an ifa_addr */ + if (!ifa->ifa_addr) continue; + + if (match_ip(ss, ifa->ifa_addr)) { + const char *iface_name; + iface_name = talloc_strdup(mem_ctx, ifa->ifa_name); + if (iface_name == NULL) { + ret = ENOMEM; + } else { + *_iface_name = iface_name; + ret = EOK; + } + goto done; + } + } + ret = ENOENT; + +done: + freeifaddrs(ifaces); + return ret; +} + +errno_t sss_get_dualstack_addresses(TALLOC_CTX *mem_ctx, + struct sockaddr *ss, + struct sss_iface_addr **_iface_addrs) +{ + struct sss_iface_addr *iface_addrs; + const char *iface_name = NULL; + TALLOC_CTX *tmp_ctx; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = find_iface_by_addr(tmp_ctx, ss, &iface_name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "find_iface_by_addr failed: %d:[%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_iface_addr_list_get(tmp_ctx, iface_name, &iface_addrs); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sss_iface_addr_list_get failed: %d:[%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + *_iface_addrs = talloc_steal(mem_ctx, iface_addrs); + +done: + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/be_dyndns.h b/src/providers/be_dyndns.h new file mode 100644 index 0000000..2185fee --- /dev/null +++ b/src/providers/be_dyndns.h @@ -0,0 +1,140 @@ +/* + SSSD + + dp_dyndns.h + + Authors: + Jakub Hrozek + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + + +#ifndef DP_DYNDNS_H_ +#define DP_DYNDNS_H_ + +/* dynamic dns helpers */ +struct sss_iface_addr; + +typedef void (*nsupdate_timer_fn_t)(void *pvt); + +enum be_nsupdate_auth { + BE_NSUPDATE_AUTH_NONE, + BE_NSUPDATE_AUTH_GSS_TSIG, +}; + +struct be_nsupdate_ctx { + struct dp_option *opts; + enum be_nsupdate_auth auth_type; + enum be_nsupdate_auth auth_ptr_type; + + time_t last_refresh; + bool timer_in_progress; + struct tevent_timer *refresh_timer; + nsupdate_timer_fn_t timer_callback; + void *timer_pvt; +}; + +enum dp_dyndns_opts { + DP_OPT_DYNDNS_UPDATE, + DP_OPT_DYNDNS_UPDATE_PER_FAMILY, + DP_OPT_DYNDNS_REFRESH_INTERVAL, + DP_OPT_DYNDNS_REFRESH_OFFSET, + DP_OPT_DYNDNS_IFACE, + DP_OPT_DYNDNS_TTL, + DP_OPT_DYNDNS_UPDATE_PTR, + DP_OPT_DYNDNS_FORCE_TCP, + DP_OPT_DYNDNS_AUTH, + DP_OPT_DYNDNS_AUTH_PTR, + DP_OPT_DYNDNS_SERVER, + + DP_OPT_DYNDNS /* attrs counter */ +}; + +#define DYNDNS_REMOVE_A 0x1 +#define DYNDNS_REMOVE_AAAA 0x2 + +errno_t be_nsupdate_check(void); + +errno_t +be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx, + struct dp_option *defopts, + struct be_nsupdate_ctx **_ctx); + +errno_t +sss_iface_addr_list_get(TALLOC_CTX *mem_ctx, const char *ifname, + struct sss_iface_addr **_addrlist); + +errno_t +sss_iface_addr_list_as_str_list(TALLOC_CTX *mem_ctx, + struct sss_iface_addr *ifaddr_list, + char ***_straddrs); + +errno_t +be_nsupdate_create_fwd_msg(TALLOC_CTX *mem_ctx, const char *realm, + const char *servername, + const char *hostname, const unsigned int ttl, + uint8_t remove_af, struct sss_iface_addr *addresses, + bool update_per_family, + char **_update_msg); + +errno_t +be_nsupdate_create_ptr_msg(TALLOC_CTX *mem_ctx, const char *realm, + const char *servername, + const char *hostname, const unsigned int ttl, + uint8_t remove_af, struct sss_iface_addr *addresses, + bool update_per_family, + char **_update_msg); + +/* Returns: + * * ERR_OK - on success + * * ERR_DYNDNS_FAILED - if nsupdate fails for any reason + * * ERR_DYNDNS_TIMEOUT - if the update times out. child_status + * is ETIMEDOUT in this case + */ +struct tevent_req *be_nsupdate_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + enum be_nsupdate_auth auth_type, + char *nsupdate_msg, + bool force_tcp); +errno_t be_nsupdate_recv(struct tevent_req *req, int *child_status); + +struct tevent_req * nsupdate_get_addrs_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_resolv_ctx *be_res, + const char *hostname); +errno_t +nsupdate_get_addrs_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sss_iface_addr **_addrlist, + size_t *_count); + +void +sss_iface_addr_concatenate(struct sss_iface_addr **list, + struct sss_iface_addr *list2); + +errno_t +sss_get_dualstack_addresses(TALLOC_CTX *mem_ctx, + struct sockaddr *ss, + struct sss_iface_addr **_iface_addrs); + +struct sss_iface_addr * +sss_iface_addr_get_next(struct sss_iface_addr *address); + +struct sockaddr * +sss_iface_addr_get_address(struct sss_iface_addr *address); + +#endif /* DP_DYNDNS_H_ */ diff --git a/src/providers/be_ptask.c b/src/providers/be_ptask.c new file mode 100644 index 0000000..b351a58 --- /dev/null +++ b/src/providers/be_ptask.c @@ -0,0 +1,548 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "providers/backend.h" +#include "providers/be_ptask_private.h" +#include "providers/be_ptask.h" + +#define backoff_allowed(ptask) (ptask->max_backoff != 0) + +enum be_ptask_delay { + BE_PTASK_FIRST_DELAY, + BE_PTASK_ENABLED_DELAY, + BE_PTASK_PERIOD +}; + +static void be_ptask_schedule(struct be_ptask *task, + enum be_ptask_delay delay_type, + uint32_t from); + +static int be_ptask_destructor(void *pvt) +{ + struct be_ptask *task; + + task = talloc_get_type(pvt, struct be_ptask); + if (task == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "BUG: task is NULL\n"); + return 0; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Terminating periodic task [%s]\n", task->name); + + return 0; +} + +static void be_ptask_online_cb(void *pvt) +{ + struct be_ptask *task = NULL; + + task = talloc_get_type(pvt, struct be_ptask); + if (task == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "BUG: task is NULL\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Back end is online\n"); + be_ptask_enable(task); +} + +static void be_ptask_offline_cb(void *pvt) +{ + struct be_ptask *task = NULL; + task = talloc_get_type(pvt, struct be_ptask); + + DEBUG(SSSDBG_TRACE_FUNC, "Back end is offline\n"); + be_ptask_disable(task); +} + +static void be_ptask_timeout(struct tevent_context *ev, + struct tevent_timer *tt, + struct timeval tv, + void *pvt) +{ + struct be_ptask *task = NULL; + task = talloc_get_type(pvt, struct be_ptask); + + DEBUG(SSSDBG_OP_FAILURE, "Task [%s]: timed out\n", task->name); + + talloc_zfree(task->req); + be_ptask_schedule(task, BE_PTASK_PERIOD, BE_PTASK_SCHEDULE_FROM_NOW); +} + +static void be_ptask_done(struct tevent_req *req); + +static void be_ptask_execute(struct tevent_context *ev, + struct tevent_timer *tt, + struct timeval tv, + void *pvt) +{ + struct be_ptask *task = NULL; + struct tevent_timer *timeout = NULL; + + task = talloc_get_type(pvt, struct be_ptask); + task->timer = NULL; /* timer is freed by tevent */ + + if (be_is_offline(task->be_ctx)) { + DEBUG(SSSDBG_TRACE_FUNC, "Back end is offline\n"); + if (task->flags & BE_PTASK_OFFLINE_SKIP) { + be_ptask_schedule(task, BE_PTASK_PERIOD, + BE_PTASK_SCHEDULE_FROM_NOW); + return; + } + else if(task->flags & BE_PTASK_OFFLINE_DISABLE) { + /* This case is normally handled by offline callback but we + * should handle it here as well since we can get here in some + * special cases for example unit tests or tevent events order. */ + be_ptask_disable(task); + return; + } + /* BE_PTASK_OFFLINE_EXECUTE */ + /* continue */ + } + + DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: executing task, timeout %lu " + "seconds\n", task->name, task->timeout); + + task->last_execution = tv.tv_sec; + + task->req = task->send_fn(task, task->ev, task->be_ctx, task, task->pvt); + if (task->req == NULL) { + /* skip this iteration and try again later */ + DEBUG(SSSDBG_OP_FAILURE, "Task [%s]: failed to execute task, " + "will try again later\n", task->name); + + be_ptask_schedule(task, BE_PTASK_PERIOD, BE_PTASK_SCHEDULE_FROM_NOW); + return; + } + + tevent_req_set_callback(task->req, be_ptask_done, task); + + /* schedule timeout */ + if (task->timeout > 0) { + tv = sss_tevent_timeval_current_ofs_time_t(task->timeout); + timeout = tevent_add_timer(task->ev, task->req, tv, + be_ptask_timeout, task); + if (timeout == NULL) { + /* If we can't guarantee a timeout, + * we need to cancel the request. */ + talloc_zfree(task->req); + + DEBUG(SSSDBG_OP_FAILURE, "Task [%s]: failed to set timeout, " + "the task will be rescheduled\n", task->name); + + be_ptask_schedule(task, BE_PTASK_PERIOD, + BE_PTASK_SCHEDULE_FROM_NOW); + } + } + + return; +} + +static void be_ptask_done(struct tevent_req *req) +{ + struct be_ptask *task = NULL; + errno_t ret; + + task = tevent_req_callback_data(req, struct be_ptask); + + ret = task->recv_fn(req); + talloc_zfree(req); + task->req = NULL; + switch (ret) { + case EOK: + DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: finished successfully\n", + task->name); + + be_ptask_schedule(task, BE_PTASK_PERIOD, task->flags); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Task [%s]: failed with [%d]: %s\n", + task->name, ret, sss_strerror(ret)); + + be_ptask_schedule(task, BE_PTASK_PERIOD, BE_PTASK_SCHEDULE_FROM_NOW); + break; + } +} + +static void be_ptask_schedule(struct be_ptask *task, + enum be_ptask_delay delay_type, + uint32_t from) +{ + struct timeval tv = { 0, }; + time_t delay = 0; + + if (!task->enabled) { + DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: disabled\n", task->name); + return; + } + + switch (delay_type) { + case BE_PTASK_FIRST_DELAY: + delay = task->first_delay; + break; + case BE_PTASK_ENABLED_DELAY: + delay = task->enabled_delay; + break; + case BE_PTASK_PERIOD: + if (task->flags & BE_PTASK_NO_PERIODIC) { + /* Periodic task is disabled, */ + /* only online/offline change can cause some activity. */ + return; + } + + if (backoff_allowed(task)) { + /* double the period for the next execution */ + task->period = MIN(task->period * 2, task->max_backoff); + } + + delay = task->period; + break; + } + + /* add random offset */ + if (task->random_offset != 0) { + delay = delay + (sss_rand() % task->random_offset); + } + + if(from & BE_PTASK_SCHEDULE_FROM_NOW) { + tv = sss_tevent_timeval_current_ofs_time_t(delay); + + DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: scheduling task %lu seconds " + "from now [%lu]\n", task->name, delay, tv.tv_sec); + } + else if (from & BE_PTASK_SCHEDULE_FROM_LAST) { + tv = tevent_timeval_set(task->last_execution + delay, 0); + + DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: scheduling task %lu seconds " + "from last execution time [%lu]\n", + task->name, delay, tv.tv_sec); + } + + if (task->timer != NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Task [%s]: another timer is already " + "active?\n", task->name); + talloc_zfree(task->timer); + } + + task->timer = tevent_add_timer(task->ev, task, tv, be_ptask_execute, task); + if (task->timer == NULL) { + /* nothing we can do about it */ + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to schedule task [%s]\n", + task->name); + be_ptask_disable(task); + } + + task->next_execution = tv.tv_sec; +} + +static unsigned int be_ptask_flag_bits(uint32_t flags) +{ + unsigned int cnt = 0; + while (flags != 0) { + cnt += flags & 1; + flags >>= 1; + } + return cnt; +} + +static int be_ptask_flag_check(uint32_t flags) +{ + uint32_t tmpflags; + + tmpflags = flags & (BE_PTASK_SCHEDULE_FROM_LAST | + BE_PTASK_SCHEDULE_FROM_NOW); + if (be_ptask_flag_bits(tmpflags) != 1) { + return EINVAL; + } + + tmpflags = flags & (BE_PTASK_OFFLINE_SKIP | + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_OFFLINE_EXECUTE); + if (be_ptask_flag_bits(tmpflags) != 1) { + return EINVAL; + } + + return EOK; +} + +errno_t be_ptask_create(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + time_t period, + time_t first_delay, + time_t enabled_delay, + time_t random_offset, + time_t timeout, + time_t max_backoff, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt, + const char *name, + uint32_t flags, + struct be_ptask **_task) +{ + struct be_ptask *task = NULL; + errno_t ret; + + if (be_ctx == NULL || send_fn == NULL || recv_fn == NULL + || name == NULL) { + return EINVAL; + } + + if (period == 0 && (flags & BE_PTASK_NO_PERIODIC) == 0) { + return EINVAL; + } + + /* check flags, some of them are exclusive, some must be present */ + ret = be_ptask_flag_check(flags); + if (ret != EOK) { + return ret; + } + + task = talloc_zero(mem_ctx, struct be_ptask); + if (task == NULL) { + ret = ENOMEM; + goto done; + } + + task->ev = be_ctx->ev; + task->be_ctx = be_ctx; + task->period = period; + task->orig_period = period; + task->first_delay = first_delay; + task->enabled_delay = enabled_delay; + task->random_offset = random_offset; + task->max_backoff = max_backoff; + task->timeout = timeout; + task->send_fn = send_fn; + task->recv_fn = recv_fn; + task->pvt = pvt; + task->name = talloc_strdup(task, name); + if (task->name == NULL) { + ret = ENOMEM; + goto done; + } + + task->flags = flags; + task->enabled = true; + + talloc_set_destructor((TALLOC_CTX*)task, be_ptask_destructor); + + if (flags & BE_PTASK_OFFLINE_DISABLE) { + /* install offline and online callbacks */ + ret = be_add_online_cb(task, be_ctx, be_ptask_online_cb, task, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to install online callback [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = be_add_offline_cb(task, be_ctx, be_ptask_offline_cb, task, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to install offline callback [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Periodic task [%s] was created\n", task->name); + + be_ptask_schedule(task, BE_PTASK_FIRST_DELAY, BE_PTASK_SCHEDULE_FROM_NOW); + + if (_task != NULL) { + *_task = task; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(task); + } + + return ret; +} + +void be_ptask_enable(struct be_ptask *task) +{ + if (task != NULL) { + if (task->enabled) { + DEBUG(SSSDBG_MINOR_FAILURE, "Task [%s]: already enabled\n", + task->name); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: enabling task\n", task->name); + + task->enabled = true; + be_ptask_schedule(task, BE_PTASK_ENABLED_DELAY, + BE_PTASK_SCHEDULE_FROM_NOW); + } +} + +/* Disable the task, but if a request already in progress, let it finish. */ +void be_ptask_disable(struct be_ptask *task) +{ + if (task != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: disabling task\n", task->name); + + talloc_zfree(task->timer); + task->enabled = false; + task->period = task->orig_period; + } +} + +/* Cancel current timer and schedule new one. */ +void be_ptask_postpone(struct be_ptask *task) +{ + if (task == NULL) { + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: rescheduling task\n", task->name); + talloc_zfree(task->timer); + talloc_zfree(task->req); + task->period = task->orig_period; + + be_ptask_schedule(task, BE_PTASK_PERIOD, BE_PTASK_SCHEDULE_FROM_NOW); +} + +void be_ptask_destroy(struct be_ptask **task) +{ + talloc_zfree(*task); +} + +time_t be_ptask_get_period(struct be_ptask *task) +{ + return task->period; +} + +time_t be_ptask_get_timeout(struct be_ptask *task) +{ + return task->timeout; +} + +bool be_ptask_running(struct be_ptask *task) +{ + return task->req != NULL; +} + +struct be_ptask_sync_ctx { + be_ptask_sync_t fn; + void *pvt; +}; + +struct be_ptask_sync_state { + int dummy; +}; + +/* This is not an asynchronous request so there is not any _done function. */ +static struct tevent_req * +be_ptask_sync_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct be_ptask_sync_ctx *ctx = NULL; + struct be_ptask_sync_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct be_ptask_sync_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ctx = talloc_get_type(pvt, struct be_ptask_sync_ctx); + ret = ctx->fn(mem_ctx, ev, be_ctx, be_ptask, ctx->pvt); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t be_ptask_sync_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t be_ptask_create_sync(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + time_t period, + time_t first_delay, + time_t enabled_delay, + time_t random_offset, + time_t timeout, + time_t max_backoff, + be_ptask_sync_t fn, + void *pvt, + const char *name, + uint32_t flags, + struct be_ptask **_task) +{ + errno_t ret; + struct be_ptask_sync_ctx *ctx = NULL; + + ctx = talloc_zero(mem_ctx, struct be_ptask_sync_ctx); + if (ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ctx->fn = fn; + ctx->pvt = pvt; + + ret = be_ptask_create(mem_ctx, be_ctx, period, first_delay, + enabled_delay, random_offset, timeout, + max_backoff, be_ptask_sync_send, be_ptask_sync_recv, + ctx, name, flags | BE_PTASK_SCHEDULE_FROM_LAST, + _task); + if (ret != EOK) { + goto done; + } + + talloc_steal(*_task, ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(ctx); + } + + return ret; +} diff --git a/src/providers/be_ptask.h b/src/providers/be_ptask.h new file mode 100644 index 0000000..0c5f1fb --- /dev/null +++ b/src/providers/be_ptask.h @@ -0,0 +1,152 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _DP_PTASK_H_ +#define _DP_PTASK_H_ + +#include +#include +#include + +/* solve circular dependency */ +struct be_ctx; + +struct be_ptask; + +/* be_ptask flags */ + +/** + * Do not schedule periodic task. This flag is useful for tasks that + * should be performed only when there is offline/online change. + */ +#define BE_PTASK_NO_PERIODIC 0x0001 + +/** + * Flags defining the starting point for scheduling a task + */ +/* Schedule starting from now, typically this is used when scheduling + * relative to the finish time */ +#define BE_PTASK_SCHEDULE_FROM_NOW 0x0002 +/* Schedule relative to the start time of the task */ +#define BE_PTASK_SCHEDULE_FROM_LAST 0x0004 + +/** + * Flags defining how should task behave when back end is offline. + */ +/* current request will be skipped and rescheduled to 'now + period' */ +#define BE_PTASK_OFFLINE_SKIP 0x0008 +/* An offline and online callback is registered. The task is disabled */ +#define BE_PTASK_OFFLINE_DISABLE 0x0010 +/* current request will be executed as planned */ +#define BE_PTASK_OFFLINE_EXECUTE 0x0020 + +typedef struct tevent_req * +(*be_ptask_send_t)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); + +/** + * If EOK, task will be scheduled again to 'last_execution_time + period'. + * If other error code, task will be rescheduled to 'now + period'. + */ +typedef errno_t +(*be_ptask_recv_t)(struct tevent_req *req); + +/** + * If EOK, task will be scheduled again to 'last_execution_time + period'. + * If other error code, task will be rescheduled to 'now + period'. + */ +typedef errno_t +(*be_ptask_sync_t)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); + +/** + * The first execution is scheduled first_delay seconds after the task is + * created. + * + * Subsequent runs will be scheduled depending on the value of the + * success_schedule_type parameter: + * - BE_PTASK_SCHEDULE_FROM_NOW: period seconds from the finish time + * - BE_PTASK_SCHEDULE_FROM_LAST: period seconds from the last start time + * + * If the test fails, another run is always scheduled period seconds + * from the finish time. + * + * If request does not complete in timeout seconds, it will be + * cancelled and rescheduled to 'now + period'. + * + * If the task is reenabled, it will be scheduled again to + * 'now + enabled_delay'. + * + * The random_offset is maximum number of seconds added to the + * expected delay. Set to 0 if no randomization is needed. + * + * If max_backoff is not 0 then the period is doubled + * every time the task is scheduled. The maximum value of + * period is max_backoff. The value of period will be reset to + * original value when the task is disabled. With max_backoff + * set to zero, this feature is disabled. + * + * If an internal error occurred, the task is automatically disabled. + */ +errno_t be_ptask_create(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + time_t period, + time_t first_delay, + time_t enabled_delay, + time_t random_offset, + time_t timeout, + time_t max_backoff, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt, + const char *name, + uint32_t flags, + struct be_ptask **_task); + +errno_t be_ptask_create_sync(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + time_t period, + time_t first_delay, + time_t enabled_delay, + time_t random_offset, + time_t timeout, + time_t max_backoff, + be_ptask_sync_t fn, + void *pvt, + const char *name, + uint32_t flags, + struct be_ptask **_task); + +void be_ptask_enable(struct be_ptask *task); +void be_ptask_disable(struct be_ptask *task); +void be_ptask_postpone(struct be_ptask *task); +void be_ptask_destroy(struct be_ptask **task); + +time_t be_ptask_get_period(struct be_ptask *task); +time_t be_ptask_get_timeout(struct be_ptask *task); +bool be_ptask_running(struct be_ptask *task); + +#endif /* _DP_PTASK_H_ */ diff --git a/src/providers/be_ptask_private.h b/src/providers/be_ptask_private.h new file mode 100644 index 0000000..3971935 --- /dev/null +++ b/src/providers/be_ptask_private.h @@ -0,0 +1,47 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2014 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef DP_PTASK_PRIVATE_H_ +#define DP_PTASK_PRIVATE_H_ + +struct be_ptask { + struct tevent_context *ev; + struct be_ctx *be_ctx; + time_t orig_period; + time_t first_delay; + time_t enabled_delay; + time_t random_offset; + time_t timeout; + time_t max_backoff; + be_ptask_send_t send_fn; + be_ptask_recv_t recv_fn; + void *pvt; + const char *name; + + time_t period; /* computed period */ + time_t next_execution; /* next time when the task is scheduled */ + time_t last_execution; /* last time when send was called */ + struct tevent_req *req; /* active tevent request */ + struct tevent_timer *timer; /* active tevent timer */ + uint32_t flags; + bool enabled; +}; + +#endif /* DP_PTASK_PRIVATE_H_ */ diff --git a/src/providers/be_refresh.c b/src/providers/be_refresh.c new file mode 100644 index 0000000..95cac20 --- /dev/null +++ b/src/providers/be_refresh.c @@ -0,0 +1,581 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include + +#include "providers/backend.h" +#include "providers/be_ptask.h" +#include "providers/be_refresh.h" +#include "util/util_errors.h" +#include "db/sysdb.h" + +static errno_t be_refresh_get_values_ex(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + time_t period, + struct ldb_dn *base_dn, + const char *key_attr, + const char *value_attr, + enum sysdb_cache_type search_cache, + char ***_values) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *attrs[] = {value_attr, NULL}; + const char *filter = NULL; + char **values = NULL; + struct sysdb_attrs **records = NULL; + struct ldb_result *res; + time_t now = time(NULL); + errno_t ret; + + if (key_attr == NULL || domain == NULL || base_dn == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = talloc_asprintf(tmp_ctx, "(&(%s<=%lld))", + key_attr, (long long) now + period); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_with_ts_attr(tmp_ctx, domain, base_dn, + LDB_SCOPE_SUBTREE, + search_cache, + filter, attrs, + &res); + if (ret != EOK) { + goto done; + } + + ret = sysdb_msg2attrs(tmp_ctx, res->count, res->msgs, &records); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not convert ldb message to sysdb_attrs\n"); + goto done; + } + + ret = sysdb_attrs_to_list(tmp_ctx, records, res->count, value_attr, &values); + if (ret != EOK) { + goto done; + } + + *_values = talloc_steal(mem_ctx, values); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t be_refresh_get_values(TALLOC_CTX *mem_ctx, + enum be_refresh_type type, + const char *attr_name, + struct sss_domain_info *domain, + time_t period, + char ***_values) +{ + struct ldb_dn *base_dn = NULL; + errno_t ret; + const char *key_attr; + enum sysdb_cache_type search_cache = SYSDB_CACHE_TYPE_TIMESTAMP; + + switch (type) { + case BE_REFRESH_TYPE_INITGROUPS: + key_attr = SYSDB_INITGR_EXPIRE; + base_dn = sysdb_user_base_dn(mem_ctx, domain); + break; + case BE_REFRESH_TYPE_USERS: + key_attr = SYSDB_CACHE_EXPIRE; + base_dn = sysdb_user_base_dn(mem_ctx, domain); + break; + case BE_REFRESH_TYPE_GROUPS: + key_attr = SYSDB_CACHE_EXPIRE; + base_dn = sysdb_group_base_dn(mem_ctx, domain); + break; + case BE_REFRESH_TYPE_NETGROUPS: + key_attr = SYSDB_CACHE_EXPIRE; + // Netgroup will reside in persistent cache rather than timestamp one + search_cache = SYSDB_CACHE_TYPE_PERSISTENT; + base_dn = sysdb_netgroup_base_dn(mem_ctx, domain); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Uknown or unsupported refresh type %d\n", type); + return ERR_INTERNAL; + break; + } + + if (base_dn == NULL) { + return ENOMEM; + } + + ret = be_refresh_get_values_ex(mem_ctx, domain, period, + base_dn, key_attr, + attr_name, search_cache, _values); + + talloc_free(base_dn); + return ret; +} + +struct be_refresh_cb_ctx { + const char *name; + const char *attr_name; + bool enabled; + struct be_refresh_cb cb; +}; + +struct be_refresh_ctx { + struct be_refresh_cb_ctx callbacks[BE_REFRESH_TYPE_SENTINEL]; +}; + +static errno_t be_refresh_ctx_init(struct be_ctx *be_ctx, + const char *attr_name) +{ + struct be_refresh_ctx *ctx = NULL; + uint32_t refresh_interval; + uint32_t offset; + errno_t ret; + + ctx = talloc_zero(be_ctx, struct be_refresh_ctx); + if (ctx == NULL) { + return ENOMEM; + } + + ctx->callbacks[BE_REFRESH_TYPE_INITGROUPS].name = "initgroups"; + ctx->callbacks[BE_REFRESH_TYPE_INITGROUPS].attr_name = SYSDB_NAME; + ctx->callbacks[BE_REFRESH_TYPE_USERS].name = "users"; + ctx->callbacks[BE_REFRESH_TYPE_USERS].attr_name = attr_name; + ctx->callbacks[BE_REFRESH_TYPE_GROUPS].name = "groups"; + ctx->callbacks[BE_REFRESH_TYPE_GROUPS].attr_name = attr_name; + ctx->callbacks[BE_REFRESH_TYPE_NETGROUPS].name = "netgroups"; + ctx->callbacks[BE_REFRESH_TYPE_NETGROUPS].attr_name = SYSDB_NAME; + + refresh_interval = be_ctx->domain->refresh_expired_interval; + if (refresh_interval > 0) { + offset = be_ctx->domain->refresh_expired_interval_offset; + ret = be_ptask_create(be_ctx, be_ctx, refresh_interval, 30, 5, offset, + refresh_interval, 0, + be_refresh_send, be_refresh_recv, + ctx, "Refresh Records", + BE_PTASK_OFFLINE_SKIP | + BE_PTASK_SCHEDULE_FROM_NOW, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to initialize refresh periodic task [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(ctx); + return ret; + } + } + + be_ctx->refresh_ctx = ctx; + return EOK; +} + +static errno_t be_refresh_add_cb(struct be_refresh_ctx *ctx, + enum be_refresh_type type, + struct be_refresh_cb *cb) +{ + if (ctx == NULL || cb->send_fn == NULL || cb->recv_fn == NULL + || type >= BE_REFRESH_TYPE_SENTINEL) { + return EINVAL; + } + + if (ctx->callbacks[type].enabled) { + return EEXIST; + } + + ctx->callbacks[type].enabled = true; + ctx->callbacks[type].cb.send_fn = cb->send_fn; + ctx->callbacks[type].cb.recv_fn = cb->recv_fn; + ctx->callbacks[type].cb.pvt = cb->pvt; + + return EOK; +} + +static errno_t be_refresh_set_callbacks(struct be_refresh_ctx *refresh_ctx, + struct be_refresh_cb *callbacks) +{ + errno_t ret; + + if (callbacks == NULL || refresh_ctx == NULL) { + return EINVAL; + } + + ret = be_refresh_add_cb(refresh_ctx, + BE_REFRESH_TYPE_INITGROUPS, + &callbacks[BE_REFRESH_TYPE_INITGROUPS]); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh of initgroups " + "will not work [%d]: %s\n", ret, strerror(ret)); + } + + ret = be_refresh_add_cb(refresh_ctx, + BE_REFRESH_TYPE_USERS, + &callbacks[BE_REFRESH_TYPE_USERS]); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh of users " + "will not work [%d]: %s\n", ret, strerror(ret)); + } + + ret = be_refresh_add_cb(refresh_ctx, + BE_REFRESH_TYPE_GROUPS, + &callbacks[BE_REFRESH_TYPE_GROUPS]); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh of groups " + "will not work [%d]: %s\n", ret, strerror(ret)); + } + + ret = be_refresh_add_cb(refresh_ctx, + BE_REFRESH_TYPE_NETGROUPS, + &callbacks[BE_REFRESH_TYPE_NETGROUPS]); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh of netgroups " + "will not work [%d]: %s\n", ret, strerror(ret)); + } + + return EOK; +} + +errno_t be_refresh_ctx_init_with_callbacks(struct be_ctx *be_ctx, + const char *attr_name, + struct be_refresh_cb *callbacks) +{ + errno_t ret; + + if (be_ctx == NULL || attr_name == NULL || callbacks == NULL) { + return EINVAL; + } + + ret = be_refresh_ctx_init(be_ctx, attr_name); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize refresh_ctx\n"); + return ret; + } + + ret = be_refresh_set_callbacks(be_ctx->refresh_ctx, callbacks); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize refresh callbacks\n"); + return ENOMEM; + } + + return EOK; +} + +struct be_refresh_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct be_refresh_ctx *ctx; + struct be_refresh_cb_ctx *cb_ctx; + + struct sss_domain_info *domain; + enum be_refresh_type index; + time_t period; + + char **refresh_values; + size_t refresh_val_size; + size_t refresh_index; + + size_t batch_size; + char **refresh_batch; +}; + +static errno_t be_refresh_batch_step(struct tevent_req *req, + uint32_t msec_delay); +static void be_refresh_batch_step_wakeup(struct tevent_context *ev, + struct tevent_timer *tt, + struct timeval tv, + void *pvt); +static errno_t be_refresh_step(struct tevent_req *req); +static void be_refresh_done(struct tevent_req *subreq); + +struct tevent_req *be_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct be_refresh_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct be_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->domain = be_ctx->domain; + state->period = be_ptask_get_period(be_ptask); + state->ctx = talloc_get_type(pvt, struct be_refresh_ctx); + if (state->ctx == NULL) { + ret = EINVAL; + goto immediately; + } + + state->batch_size = 200; + state->refresh_batch = talloc_zero_array(state, char *, state->batch_size+1); + if (state->refresh_batch == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = be_refresh_step(req); + if (ret == EOK) { + goto immediately; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_refresh_step() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t be_refresh_step(struct tevent_req *req) +{ + struct be_refresh_state *state = NULL; + errno_t ret; + + state = tevent_req_data(req, struct be_refresh_state); + + while (state->domain != NULL) { + /* find first enabled callback */ + state->cb_ctx = &state->ctx->callbacks[state->index]; + while (state->index != BE_REFRESH_TYPE_SENTINEL + && !state->cb_ctx->enabled) { + state->index++; + state->cb_ctx = &state->ctx->callbacks[state->index]; + } + + /* if not found than continue with next domain */ + if (state->index == BE_REFRESH_TYPE_SENTINEL) { + state->domain = get_next_domain(state->domain, + SSS_GND_DESCEND); + /* we can update just subdomains */ + if (state->domain != NULL && !IS_SUBDOMAIN(state->domain)) { + break; + } + state->index = 0; + continue; + } + + if (state->cb_ctx->cb.send_fn == NULL + || state->cb_ctx->cb.recv_fn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid parameters!\n"); + ret = ERR_INTERNAL; + goto done; + } + + talloc_zfree(state->refresh_values); + ret = be_refresh_get_values(state, state->index, + state->cb_ctx->attr_name, + state->domain, state->period, + &state->refresh_values); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to obtain DN list [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + for (state->refresh_val_size = 0; + state->refresh_values[state->refresh_val_size] != NULL; + state->refresh_val_size++); + + DEBUG(SSSDBG_TRACE_FUNC, "Refreshing %zu %s in domain %s\n", + state->refresh_val_size, + state->cb_ctx->name, + state->domain->name); + + ret = be_refresh_batch_step(req, 0); + if (ret == EOK) { + state->index++; + continue; + } else if (ret != EAGAIN) { + goto done; + } + /* EAGAIN only, refreshing something.. */ + + state->index++; + goto done; + } + + ret = EOK; + +done: + return ret; +} + +static errno_t be_refresh_batch_step(struct tevent_req *req, + uint32_t msec_delay) +{ + struct be_refresh_state *state = tevent_req_data(req, struct be_refresh_state); + struct timeval tv; + struct tevent_timer *timeout = NULL; + + size_t remaining; + size_t batch_size; + + memset(state->refresh_batch, 0, sizeof(char *) * state->batch_size); + + if (state->refresh_index >= state->refresh_val_size) { + DEBUG(SSSDBG_FUNC_DATA, "The batch is done\n"); + state->refresh_index = 0; + return EOK; + } + + remaining = state->refresh_val_size - state->refresh_index; + batch_size = MIN(remaining, state->batch_size); + DEBUG(SSSDBG_FUNC_DATA, + "This batch will refresh %zu entries (so far %zu/%zu)\n", + batch_size, state->refresh_index, state->refresh_val_size); + + for (size_t i = 0; i < batch_size; i++) { + state->refresh_batch[i] = state->refresh_values[state->refresh_index]; + state->refresh_index++; + } + + tv = tevent_timeval_current_ofs(0, msec_delay * 1000); + timeout = tevent_add_timer(state->be_ctx->ev, req, tv, + be_refresh_batch_step_wakeup, req); + if (timeout == NULL) { + return ENOMEM; + } + + return EAGAIN; +} + +static void be_refresh_batch_step_wakeup(struct tevent_context *ev, + struct tevent_timer *tt, + struct timeval tv, + void *pvt) +{ + struct tevent_req *req; + struct tevent_req *subreq = NULL; + struct be_refresh_state *state = NULL; + + req = talloc_get_type(pvt, struct tevent_req); + state = tevent_req_data(req, struct be_refresh_state); + + DEBUG(SSSDBG_TRACE_INTERNAL, "Issuing refresh\n"); + subreq = state->cb_ctx->cb.send_fn(state, state->ev, state->be_ctx, + state->domain, + state->refresh_batch, + state->cb_ctx->cb.pvt); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, be_refresh_done, req); +} + +static void be_refresh_done(struct tevent_req *subreq) +{ + struct be_refresh_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct be_refresh_state); + + ret = state->cb_ctx->cb.recv_fn(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = be_refresh_batch_step(req, 500); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Another batch in this step in progress\n"); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "be_refresh_batch_step failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "All batches in this step refreshed\n"); + + /* Proceed to the next step */ + ret = be_refresh_step(req); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Another step in progress\n"); + return; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t be_refresh_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct dp_id_data *be_refresh_acct_req(TALLOC_CTX *mem_ctx, + uint32_t entry_type, + uint32_t filter_type, + struct sss_domain_info *domain) +{ + struct dp_id_data *account_req; + + account_req = talloc_zero(mem_ctx, struct dp_id_data); + if (account_req == NULL) { + return NULL; + } + + account_req->entry_type = entry_type; + account_req->filter_type = filter_type; + account_req->extra_value = NULL; + account_req->domain = domain->name; + return account_req; +} diff --git a/src/providers/be_refresh.h b/src/providers/be_refresh.h new file mode 100644 index 0000000..68be401 --- /dev/null +++ b/src/providers/be_refresh.h @@ -0,0 +1,99 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _DP_REFRESH_H_ +#define _DP_REFRESH_H_ + +#include +#include + +#include "providers/be_ptask.h" + +/* solve circular dependency */ +struct be_ctx; + +#define REFRESH_SEND_RECV_FNS(outer_base, inner_base, req_type) \ + \ +static struct tevent_req * \ +outer_base ##_send(TALLOC_CTX *mem_ctx, \ + struct tevent_context *ev, \ + struct be_ctx *be_ctx, \ + struct sss_domain_info *domain, \ + char **names, \ + void *pvt) \ +{ \ + return inner_base ##_send(mem_ctx, ev, \ + be_ctx, domain, \ + req_type, names, pvt); \ +} \ + \ +static errno_t outer_base ##_recv(struct tevent_req *req) \ +{ \ + return inner_base ##_recv(req); \ +} \ + +/** + * name_list contains SYSDB_NAME of all expired records. + */ +typedef struct tevent_req * +(*be_refresh_send_t)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + char **values, + void *pvt); + +typedef errno_t +(*be_refresh_recv_t)(struct tevent_req *req); + +enum be_refresh_type { + BE_REFRESH_TYPE_INITGROUPS, + BE_REFRESH_TYPE_USERS, + BE_REFRESH_TYPE_GROUPS, + BE_REFRESH_TYPE_NETGROUPS, + BE_REFRESH_TYPE_SENTINEL +}; + +struct be_refresh_cb { + be_refresh_send_t send_fn; + be_refresh_recv_t recv_fn; + void *pvt; +}; + +struct be_refresh_ctx; + +errno_t be_refresh_ctx_init_with_callbacks(struct be_ctx *be_ctx, + const char *attr_name, + struct be_refresh_cb *callbacks); + +struct tevent_req *be_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); + +errno_t be_refresh_recv(struct tevent_req *req); + +struct dp_id_data *be_refresh_acct_req(TALLOC_CTX *mem_ctx, + uint32_t entry_type, + uint32_t filter_type, + struct sss_domain_info *domain); + +#endif /* _DP_REFRESH_H_ */ diff --git a/src/providers/data_provider.h b/src/providers/data_provider.h new file mode 100644 index 0000000..36a82b8 --- /dev/null +++ b/src/providers/data_provider.h @@ -0,0 +1,274 @@ +/* + SSSD + + Data Provider, private header file + + Copyright (C) Simo Sorce 2008 + + 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 3 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; 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, see . +*/ + +#ifndef __DATA_PROVIDER_H__ +#define __DATA_PROVIDER_H__ + +#include "config.h" + +#include +#include +#include +#include +#ifdef USE_KEYRING +#include +#include +#endif +#include +#include +#include +#include + +#include "util/util.h" +#include "confdb/confdb.h" +#include "sss_client/sss_cli.h" +#include "util/authtok.h" +#include "util/sss_pam_data.h" +#include "providers/data_provider_req.h" + +#define DATA_PROVIDER_VERSION 0x0001 + +/** + * @defgroup pamHandler PAM DBUS request + * @ingroup sss_pam + * + * The PAM responder send all the data it has received from the PAM client to + * the authentication backend with a DBUS message. + * + * As a response it expects a PAM return value (see pam(3) for details). + * The backend may send any number of additional messages (see ...) which are + * forwarded by the PAM responder to the PAM client. + * @{ + */ + +/** Then pamHandler Request + * + * The following two functions can help you to pack and unpack the DBUS + * message for a PAM request. If it is necessary to create the DBUS message by + * hand it must have the following elements: + * + * @param DBUS_TYPE_INT32 PAM Command, see #sss_cli_command for allowed values + * @param DBUS_TYPE_STRING User name, this value is send by the PAM client and + * contains the value of the PAM item PAM_USER + * @param DBUS_TYPE_STRING Service name, this value is send by the PAM client + * and contains the value of the PAM item PAM_SERVICE + * @param DBUS_TYPE_STRING TTY name this value is send by the PAM client and + * contains the value of the PAM item PAM_TTY + * @param DBUS_TYPE_STRING Remote user, this value is send by the PAM client + * and contains the value of the PAM item PAM_RUSER + * @param DBUS_TYPE_STRING Remote host, this value is send by the PAM client + * and contains the value of the PAM item PAM_RHOST + * @param DBUS_TYPE_UINT32 Type of the authentication token, see #sss_authtok_type + * for allowed values + * @param DBUS_TYPE_ARRAY__(BYTE) Authentication token, DBUS array which + * contains the authentication token, it is not required that passwords have a + * trailing \\0, this value is send by the PAM client and contains the value of + * the PAM item PAM_AUTHTOK or PAM_OLDAUTHTOK if the PAM command is + * #SSS_PAM_CHAUTHTOK or #SSS_PAM_CHAUTHTOK_PRELIM + * @param DBUS_TYPE_UINT32 Type of the new authentication token, see + * #sss_authtok_type for allowed values + * @param DBUS_TYPE_ARRAY__(BYTE) New authentication token, DBUS array which + * contains the new authentication token for a password change, it is not + * required that passwords have a trailing \\0, this value is send by the PAM + * client and contains the value of the PAM item PAM_AUTHTOK if the PAM + * command is #SSS_PAM_CHAUTHTOK or #SSS_PAM_CHAUTHTOK_PRELIM + * @param DBUS_TYPE_INT32 Privileged flag is set to a non-zero value if the + * PAM client connected to the PAM responder via the privileged pipe, i.e. if + * the PAM client is running with root privileges + * @param DBUS_TYPE_UINT32 + * + * @retval DBUS_TYPE_UINT32 PAM return value, PAM_AUTHINFO_UNAVAIL is used to + * indicate that the provider is offline and that the PAM responder should try + * a cached authentication, for all other return value see the man pages for + * the corresponding PAM service functions + * @retval DBUS_TYPE_ARRAY__(STRUCT) Zero or more additional getAccountInfo + * messages, here the DBUS_TYPE_STRUCT is build of a DBUS_TYPE_UINT32 holding + * an identifier (see #response_type) and DBUS_TYPE_G_BYTE_ARRAY with the data + * of the message. + */ + + +/** + * @} + */ /* end of group pamHandler */ + +#define DP_ERR_DECIDE -1 +#define DP_ERR_OK 0 +#define DP_ERR_OFFLINE 1 +#define DP_ERR_TIMEOUT 2 +#define DP_ERR_FATAL 3 + +#define BE_FILTER_NAME 1 +#define BE_FILTER_IDNUM 2 +#define BE_FILTER_ENUM 3 +#define BE_FILTER_SECID 4 +#define BE_FILTER_UUID 5 +#define BE_FILTER_CERT 6 +#define BE_FILTER_WILDCARD 7 +#define BE_FILTER_ADDR 8 + +#define DP_SEC_ID "secid" +#define DP_CERT "cert" +/* sizeof() counts the trailing \0 so we must subtract 1 for the string + * length */ +#define DP_SEC_ID_LEN (sizeof(DP_SEC_ID) - 1) +#define DP_CERT_LEN (sizeof(DP_CERT) - 1) + +#define DP_WILDCARD "wildcard" +#define DP_WILDCARD_LEN (sizeof(DP_WILDCARD) - 1) + +#define EXTRA_NAME_IS_UPN "U" +#define EXTRA_INPUT_MAYBE_WITH_VIEW "V" + +/* from dp_auth_util.c */ +#define SSS_SERVER_INFO 0x80000000 + +#define SSS_KRB5_INFO 0x40000000 +#define SSS_LDAP_INFO 0x20000000 +#define SSS_PROXY_INFO 0x10000000 + +#define SSS_KRB5_INFO_TGT_LIFETIME (SSS_SERVER_INFO|SSS_KRB5_INFO|0x01) +#define SSS_KRB5_INFO_UPN (SSS_SERVER_INFO|SSS_KRB5_INFO|0x02) + +bool dp_pack_pam_request(DBusMessage *msg, struct pam_data *pd); +bool dp_unpack_pam_request(DBusMessage *msg, TALLOC_CTX *mem_ctx, + struct pam_data **new_pd, DBusError *dbus_error); + +bool dp_pack_pam_response(DBusMessage *msg, struct pam_data *pd); +bool dp_unpack_pam_response(DBusMessage *msg, struct pam_data *pd, + DBusError *dbus_error); + +void dp_id_callback(DBusPendingCall *pending, void *ptr); + +#ifdef BUILD_FILES_PROVIDER +/* Reserved filter name for request which waits until the files provider finishes mirroring + * the file content + */ +#define DP_REQ_OPT_FILES_INITGR "files_initgr_request" +#endif + +/* Helpers */ + +#define NULL_STRING { .string = NULL } +#define NULL_BLOB { .blob = { NULL, 0 } } +#define NULL_NUMBER { .number = 0 } +#define BOOL_FALSE { .boolean = false } +#define BOOL_TRUE { .boolean = true } + +enum dp_opt_type { + DP_OPT_STRING, + DP_OPT_BLOB, + DP_OPT_NUMBER, + DP_OPT_BOOL +}; + +struct dp_opt_blob { + uint8_t *data; + size_t length; +}; + +union dp_opt_value { + const char *cstring; + char *string; + struct dp_opt_blob blob; + int number; + bool boolean; +}; + +struct dp_option { + const char *opt_name; + enum dp_opt_type type; + union dp_opt_value def_val; + union dp_opt_value val; +}; + +#define DP_OPTION_TERMINATOR { NULL, 0, NULL_STRING, NULL_STRING } + +void dp_option_inherit_match(char **inherit_opt_list, + int option, + struct dp_option *parent_opts, + struct dp_option *subdom_opts); + +void dp_option_inherit(int option, + struct dp_option *parent_opts, + struct dp_option *subdom_opts); + +int dp_get_options(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct dp_option *def_opts, + int num_opts, + struct dp_option **_opts); + +int dp_copy_options(TALLOC_CTX *memctx, + struct dp_option *src_opts, + int num_opts, + struct dp_option **_opts); + +int dp_copy_defaults(TALLOC_CTX *memctx, + struct dp_option *src_opts, + int num_opts, + struct dp_option **_opts); + +const char *_dp_opt_get_cstring(struct dp_option *opts, + int id, const char *location); +char *_dp_opt_get_string(struct dp_option *opts, + int id, const char *location); +struct dp_opt_blob _dp_opt_get_blob(struct dp_option *opts, + int id, const char *location); +int _dp_opt_get_int(struct dp_option *opts, + int id, const char *location); +bool _dp_opt_get_bool(struct dp_option *opts, + int id, const char *location); +#define dp_opt_get_cstring(o, i) _dp_opt_get_cstring(o, i, __FUNCTION__) +#define dp_opt_get_string(o, i) _dp_opt_get_string(o, i, __FUNCTION__) +#define dp_opt_get_blob(o, i) _dp_opt_get_blob(o, i, __FUNCTION__) +#define dp_opt_get_int(o, i) _dp_opt_get_int(o, i, __FUNCTION__) +#define dp_opt_get_bool(o, i) _dp_opt_get_bool(o, i, __FUNCTION__) + +int _dp_opt_set_string(struct dp_option *opts, int id, + const char *s, const char *location); +int _dp_opt_set_blob(struct dp_option *opts, int id, + struct dp_opt_blob b, const char *location); +int _dp_opt_set_int(struct dp_option *opts, int id, + int i, const char *location); +int _dp_opt_set_bool(struct dp_option *opts, int id, + bool b, const char *location); +#define dp_opt_set_string(o, i, v) _dp_opt_set_string(o, i, v, __FUNCTION__) +#define dp_opt_set_blob(o, i, v) _dp_opt_set_blob(o, i, v, __FUNCTION__) +#define dp_opt_set_int(o, i, v) _dp_opt_set_int(o, i, v, __FUNCTION__) +#define dp_opt_set_bool(o, i, v) _dp_opt_set_bool(o, i, v, __FUNCTION__) + +/* Generic Data Provider options */ + +/* Resolver DP options */ +enum dp_res_opts { + DP_RES_OPT_FAMILY_ORDER, + DP_RES_OPT_RESOLVER_TIMEOUT, + DP_RES_OPT_RESOLVER_OP_TIMEOUT, + DP_RES_OPT_RESOLVER_SERVER_TIMEOUT, + DP_RES_OPT_RESOLVER_USE_SEARCH_LIST, + DP_RES_OPT_DNS_DOMAIN, + + DP_RES_OPTS /* attrs counter */ +}; + +#endif /* __DATA_PROVIDER_ */ diff --git a/src/providers/data_provider/dp.c b/src/providers/data_provider/dp.c new file mode 100644 index 0000000..99f27fc --- /dev/null +++ b/src/providers/data_provider/dp.c @@ -0,0 +1,288 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "config.h" +#include "providers/data_provider/dp.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "sss_iface/sss_iface_async.h" +#include "providers/backend.h" +#include "util/util.h" + +static errno_t +dp_init_interface(struct data_provider *provider) +{ + errno_t ret; + + SBUS_INTERFACE(iface_dp_client, + sssd_DataProvider_Client, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_DataProvider_Client, Register, dp_client_register, provider) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_dp_backend, + sssd_DataProvider_Backend, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_DataProvider_Backend, IsOnline, dp_backend_is_online, provider->be_ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_dp_failover, + sssd_DataProvider_Failover, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_DataProvider_Failover, ListServices, dp_failover_list_services, provider->be_ctx), + SBUS_SYNC(METHOD, sssd_DataProvider_Failover, ListServers, dp_failover_list_servers, provider->be_ctx), + SBUS_SYNC(METHOD, sssd_DataProvider_Failover, ActiveServer, dp_failover_active_server, provider->be_ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_dp_access, + sssd_DataProvider_AccessControl, + SBUS_METHODS( + SBUS_ASYNC(METHOD, sssd_DataProvider_AccessControl, RefreshRules, dp_access_control_refresh_rules_send, dp_access_control_refresh_rules_recv, provider) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_autofs, + sssd_DataProvider_Autofs, + SBUS_METHODS( + SBUS_ASYNC(METHOD, sssd_DataProvider_Autofs, GetMap, dp_autofs_get_map_send, dp_autofs_get_map_recv, provider), + SBUS_ASYNC(METHOD, sssd_DataProvider_Autofs, GetEntry, dp_autofs_get_entry_send, dp_autofs_get_entry_recv, provider), + SBUS_ASYNC(METHOD, sssd_DataProvider_Autofs, Enumerate, dp_autofs_enumerate_send, dp_autofs_enumerate_recv, provider) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_dp, + sssd_dataprovider, + SBUS_METHODS( + SBUS_ASYNC(METHOD, sssd_dataprovider, pamHandler, dp_pam_handler_send, dp_pam_handler_recv, provider), + SBUS_ASYNC(METHOD, sssd_dataprovider, sudoHandler, dp_sudo_handler_send, dp_sudo_handler_recv, provider), + SBUS_ASYNC(METHOD, sssd_dataprovider, hostHandler, dp_host_handler_send, dp_host_handler_recv, provider), + SBUS_ASYNC(METHOD, sssd_dataprovider, resolverHandler, dp_resolver_handler_send, dp_resolver_handler_recv, provider), + SBUS_ASYNC(METHOD, sssd_dataprovider, getDomains, dp_subdomains_handler_send, dp_subdomains_handler_recv, provider), + SBUS_ASYNC(METHOD, sssd_dataprovider, getAccountInfo, dp_get_account_info_send, dp_get_account_info_recv, provider), + SBUS_ASYNC(METHOD, sssd_dataprovider, getAccountDomain, dp_get_account_domain_send, dp_get_account_domain_recv, provider) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + struct sbus_path paths[] = { + {SSS_BUS_PATH, &iface_dp_client}, + {SSS_BUS_PATH, &iface_dp_backend}, + {SSS_BUS_PATH, &iface_dp_failover}, + {SSS_BUS_PATH, &iface_dp_access}, + {SSS_BUS_PATH, &iface_dp}, + {SSS_BUS_PATH, &iface_autofs}, + {NULL, NULL} + }; + + ret = sbus_connection_add_path_map(provider->sbus_conn, paths); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to add paths [%d]: %s\n", + ret, sss_strerror(ret)); + } + + return ret; +} + +static int dp_destructor(struct data_provider *provider) +{ + enum dp_clients client; + + provider->terminating = true; + + dp_terminate_active_requests(provider); + + for (client = 0; client != DP_CLIENT_SENTINEL; client++) { + talloc_zfree(provider->clients[client]); + } + + return 0; +} + +struct dp_init_state { + struct be_ctx *be_ctx; + struct data_provider *provider; +}; + +static void dp_init_done(struct tevent_req *subreq); + +struct tevent_req * +dp_init_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + uid_t uid, + gid_t gid, + const char *sbus_name) +{ + struct dp_init_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + char *sbus_address; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_init_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + sbus_address = sss_iface_domain_address(state, be_ctx->domain); + if (sbus_address == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not get sbus backend address.\n"); + ret = ENOMEM; + goto done; + } + + state->provider = talloc_zero(be_ctx, struct data_provider); + if (state->provider == NULL) { + ret = ENOMEM; + goto done; + } + + state->be_ctx = be_ctx; + state->provider->ev = ev; + state->provider->uid = uid; + state->provider->gid = gid; + state->provider->be_ctx = be_ctx; + + /* Initialize data provider bus. Data provider can receive client + * registration and other D-Bus methods. However no data provider + * request will be executed as long as the modules and targets + * are not initialized. + */ + talloc_set_destructor(state->provider, dp_destructor); + + subreq = sbus_server_create_and_connect_send(state->provider, ev, + sbus_name, NULL, sbus_address, true, 1000, uid, gid, + (sbus_server_on_connection_cb)dp_client_init, + (sbus_server_on_connection_data)state->provider); + if (subreq == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_init_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_init_done(struct tevent_req *subreq) +{ + struct dp_init_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_init_state); + + ret = sbus_server_create_and_connect_recv(state->provider, subreq, + &state->provider->sbus_server, + &state->provider->sbus_conn); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* be_ctx->provider must be accessible from modules and targets */ + state->be_ctx->provider = talloc_steal(state->be_ctx, state->provider); + + ret = dp_init_modules(state->provider, &state->provider->modules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize DP modules " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = dp_init_targets(state->provider, state->provider->be_ctx, + state->provider, state->provider->modules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize DP targets " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = dp_init_interface(state->provider); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize DP interface " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + +done: + if (ret != EOK) { + talloc_zfree(state->be_ctx->provider); + tevent_req_error(req, ret); + } + + tevent_req_done(req); +} + +errno_t dp_init_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sbus_connection * +dp_sbus_conn(struct data_provider *provider) +{ + if (provider == NULL) { + return NULL; + } + + return provider->sbus_conn; +} + +struct sbus_server * +dp_sbus_server(struct data_provider *provider) +{ + if (provider == NULL) { + return NULL; + } + + return provider->sbus_server; +} diff --git a/src/providers/data_provider/dp.h b/src/providers/data_provider/dp.h new file mode 100644 index 0000000..ba4595f --- /dev/null +++ b/src/providers/data_provider/dp.h @@ -0,0 +1,228 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _DP_H_ +#define _DP_H_ + +#include +#include + +#include "providers/backend.h" +#include "providers/data_provider/dp_request.h" +#include "providers/data_provider/dp_custom_data.h" +#include "providers/data_provider/dp_flags.h" +#include "sbus/sbus.h" + +struct data_provider; +struct dp_method; + +/** + * Module constructor. + * + * It is possible to create a module data that is passed into all + * target initialization functions. + */ +typedef errno_t (*dp_module_init_fn)(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data); + +/** + * Target initialization function. + * + * Pointer to dp_method is unique for all targets. Make sure that + * dp_set_method is called in all targets even if you are reusing + * some existing context or initialization function. + */ +typedef errno_t (*dp_target_init_fn)(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods); + +enum dp_targets { + DPT_ID, + DPT_AUTH, + DPT_ACCESS, + DPT_CHPASS, + DPT_SUDO, + DPT_AUTOFS, + DPT_SELINUX, + DPT_HOSTID, + DPT_SUBDOMAINS, + DPT_SESSION, + DPT_RESOLVER, + + DP_TARGET_SENTINEL +}; + +enum dp_methods { + DPM_CHECK_ONLINE, + DPM_ACCOUNT_HANDLER, + DPM_AUTH_HANDLER, + DPM_ACCESS_HANDLER, + DPM_SELINUX_HANDLER, + DPM_SUDO_HANDLER, + DPM_HOSTID_HANDLER, + DPM_DOMAINS_HANDLER, + DPM_SESSION_HANDLER, + DPM_ACCT_DOMAIN_HANDLER, + DPM_RESOLVER_HOSTS_HANDLER, + DPM_RESOLVER_IP_NETWORK_HANDLER, + + DPM_REFRESH_ACCESS_RULES, + + DPM_AUTOFS_GET_MAP, + DPM_AUTOFS_GET_ENTRY, + DPM_AUTOFS_ENUMERATE, + + DP_METHOD_SENTINEL +}; + +/* Method handler. */ + +struct dp_req_params { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct sss_domain_info *domain; + enum dp_targets target; + enum dp_methods method; +}; + +typedef struct tevent_req * +(*dp_req_send_fn)(TALLOC_CTX *mem_ctx, void *method_data, void *request_data, + struct dp_req_params *params); + +typedef errno_t +(*dp_req_recv_fn)(TALLOC_CTX *mem_ctx, struct tevent_req *req, void *data); + +typedef char dp_no_output; + +/* Data provider initialization. */ + +struct tevent_req * +dp_init_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + uid_t uid, + gid_t gid, + const char *sbus_name); + +errno_t dp_init_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req); + +void dp_client_cancel_timeout(struct sbus_connection *conn); + +bool _dp_target_enabled(struct data_provider *provider, + const char *module_name, + ...); + +#define dp_target_enabled(provider, module_name, ...) \ + _dp_target_enabled(provider, module_name, ##__VA_ARGS__, DP_TARGET_SENTINEL) + +struct dp_module *dp_target_module(struct data_provider *provider, + enum dp_targets target); + +void *dp_get_module_data(struct dp_module *dp_module); + +void _dp_set_method(struct dp_method *methods, + enum dp_methods method, + dp_req_send_fn send_fn, + dp_req_recv_fn recv_fn, + void *method_data, + const char *method_dtype, + const char *request_dtype, + const char *output_dtype, + uint32_t output_size); + +/* We check function headers on compile time and data types on run time. This + * check requires that both method and request private data are talloc-created + * with talloc name set to data type name (which is done by talloc unless + * you use _size variations of talloc functions. + * + * This way we ensure that we always pass correct data and we can access them + * directly in request handler without the need to cast them explicitly + * from void pointer. */ +#define dp_set_method(methods, method, send_fn, recv_fn, method_data, \ + method_dtype, req_dtype, output_dtype) \ + do { \ + /* Check _send function parameter types. */ \ + struct tevent_req *(*__send_fn)(TALLOC_CTX *, method_dtype *, \ + req_dtype *, struct dp_req_params *params) = (send_fn); \ + \ + /* Check _recv function parameter types. */ \ + /* With output parameter. */ \ + errno_t (*__recv_fn)(TALLOC_CTX *, struct tevent_req *, \ + output_dtype *) = (recv_fn); \ + _dp_set_method(methods, method, (dp_req_send_fn)__send_fn, \ + (dp_req_recv_fn)__recv_fn, method_data, \ + #method_dtype, #req_dtype, \ + #output_dtype, sizeof(output_dtype)); \ + } while (0) + +bool dp_method_enabled(struct data_provider *provider, + enum dp_targets target, + enum dp_methods method); + +void dp_terminate_domain_requests(struct data_provider *provider, + const char *domain); + +void dp_sbus_domain_active(struct data_provider *provider, + struct sss_domain_info *dom); +void dp_sbus_domain_inconsistent(struct data_provider *provider, + struct sss_domain_info *dom); + +void dp_sbus_reset_users_ncache(struct data_provider *provider, + struct sss_domain_info *dom); +void dp_sbus_reset_groups_ncache(struct data_provider *provider, + struct sss_domain_info *dom); + +void dp_sbus_reset_users_memcache(struct data_provider *provider); +void dp_sbus_reset_groups_memcache(struct data_provider *provider); +void dp_sbus_reset_initgr_memcache(struct data_provider *provider); +void dp_sbus_invalidate_group_memcache(struct data_provider *provider, + gid_t gid); + +/* + * A dummy handler for DPM_ACCT_DOMAIN_HANDLER. + * + * Its purpose is to always return ERR_GET_ACCT_DOM_NOT_SUPPORTED + * which the responder should evaluate as "this back end does not + * support locating entries' domain" and never call + * DPM_ACCT_DOMAIN_HANDLER again + * + * This request cannot fail, except for critical errors like OOM. + */ +struct tevent_req * +default_account_domain_send(TALLOC_CTX *mem_ctx, + void *unused_ctx, + struct dp_get_acct_domain_data *data, + struct dp_req_params *params); +errno_t default_account_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +struct sbus_connection * +dp_sbus_conn(struct data_provider *provider); + +struct sbus_server * +dp_sbus_server(struct data_provider *provider); + +#endif /* _DP_H_ */ diff --git a/src/providers/data_provider/dp_builtin.c b/src/providers/data_provider/dp_builtin.c new file mode 100644 index 0000000..01d7f6d --- /dev/null +++ b/src/providers/data_provider/dp_builtin.c @@ -0,0 +1,118 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include "config.h" +#include "providers/data_provider/dp.h" +#include "providers/backend.h" +#include "util/util.h" + +struct dp_access_permit_handler_state { + struct pam_data *pd; +}; + +struct tevent_req * +dp_access_permit_handler_send(TALLOC_CTX *mem_ctx, + void *data, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct dp_access_permit_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct dp_access_permit_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + DEBUG(SSSDBG_TRACE_ALL, "Access permit, returning PAM_SUCCESS.\n"); + state->pd->pam_status = PAM_SUCCESS; + + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +errno_t +dp_access_permit_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct dp_access_permit_handler_state *state = NULL; + + state = tevent_req_data(req, struct dp_access_permit_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} + +struct dp_access_deny_handler_state { + struct pam_data *pd; +}; + +struct tevent_req * +dp_access_deny_handler_send(TALLOC_CTX *mem_ctx, + void *data, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct dp_access_deny_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct dp_access_deny_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + DEBUG(SSSDBG_TRACE_ALL, "Access deny, returning PAM_PERM_DENIED.\n"); + state->pd->pam_status = PAM_PERM_DENIED; + + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +errno_t +dp_access_deny_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct dp_access_deny_handler_state *state = NULL; + + state = tevent_req_data(req, struct dp_access_deny_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/data_provider/dp_builtin.h b/src/providers/data_provider/dp_builtin.h new file mode 100644 index 0000000..6bd0329 --- /dev/null +++ b/src/providers/data_provider/dp_builtin.h @@ -0,0 +1,50 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _DP_SPECIAL_H_ +#define _DP_SPECIAL_H_ + +#include +#include +#include "providers/data_provider/dp.h" + +struct tevent_req * +dp_access_permit_handler_send(TALLOC_CTX *mem_ctx, + void *data, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +dp_access_permit_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +struct tevent_req * +dp_access_deny_handler_send(TALLOC_CTX *mem_ctx, + void *data, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +dp_access_deny_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +#endif /* _DP_SPECIAL_H_ */ diff --git a/src/providers/data_provider/dp_client.c b/src/providers/data_provider/dp_client.c new file mode 100644 index 0000000..04e6425 --- /dev/null +++ b/src/providers/data_provider/dp_client.c @@ -0,0 +1,245 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/backend.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/data_provider/dp.h" +#include "sbus/sbus_request.h" +#include "util/util.h" + +struct dp_client { + struct data_provider *provider; + struct sbus_connection *conn; + struct tevent_timer *timeout; + const char *name; + bool initialized; +}; + +const char *dp_client_to_string(enum dp_clients client) +{ + switch (client) { + case DPC_NSS: + return "NSS"; + case DPC_PAM: + return "PAM"; + case DPC_IFP: + return "IFP"; + case DPC_PAC: + return "PAC"; + case DPC_SUDO: + return "SUDO"; + case DPC_HOST: + return "SSH"; + case DPC_AUTOFS: + return "autofs"; + case DP_CLIENT_SENTINEL: + return "Invalid"; + } + + return "Invalid"; +} + +static int dp_client_destructor(struct dp_client *dp_cli) +{ + struct data_provider *provider; + enum dp_clients client; + + if (dp_cli->provider == NULL) { + return 0; + } + + provider = dp_cli->provider; + + for (client = 0; client != DP_CLIENT_SENTINEL; client++) { + if (provider->clients[client] == dp_cli) { + provider->clients[client] = NULL; + DEBUG(SSSDBG_TRACE_FUNC, "Removed %s client\n", + dp_client_to_string(client)); + break; + } + } + + if (client == DP_CLIENT_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown client removed...\n"); + } + + return 0; +} + +errno_t +dp_client_register(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct data_provider *provider, + const char *name) +{ + struct sbus_connection *cli_conn; + struct dp_client *dp_cli; + enum dp_clients client; + + cli_conn = sbus_server_find_connection(dp_sbus_server(provider), + sbus_req->sender->name); + if (cli_conn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown client: %s\n", + sbus_req->sender->name); + return ENOENT; + } + + dp_cli = sbus_connection_get_data(cli_conn, struct dp_client); + + dp_cli->name = talloc_strdup(dp_cli, name); + if (dp_cli->name == NULL) { + talloc_free(dp_cli); + return ENOMEM; + } + + for (client = 0; client != DP_CLIENT_SENTINEL; client++) { + if (strcasecmp(name, dp_client_to_string(client)) == 0) { + provider->clients[client] = dp_cli; + break; + } + } + + if (client == DP_CLIENT_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown client! [%s]\n", name); + return ENOENT; + } + + talloc_set_destructor(dp_cli, dp_client_destructor); + + dp_cli->initialized = true; + DEBUG(SSSDBG_CONF_SETTINGS, "Added Frontend client [%s]\n", name); + DEBUG(SSSDBG_CONF_SETTINGS, "Cancel DP ID timeout [%p]\n", dp_cli->timeout); + talloc_zfree(dp_cli->timeout); + + return EOK; +} + +static void +dp_client_handshake_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, + void *ptr) +{ + struct sbus_connection *conn; + struct dp_client *dp_cli; + const char *be_name; + const char *name; + + dp_cli = talloc_get_type(ptr, struct dp_client); + conn = dp_cli->conn; + be_name = dp_cli->provider->be_ctx->sbus_name; + + talloc_set_destructor(dp_cli, NULL); + + name = sbus_connection_get_name(dp_cli->conn); + if (name != NULL && strcmp(name, be_name) == 0) { + /* This is the data provider connection. Just free the client record + * but keep the connection opened. */ + talloc_zfree(dp_cli); + return; + } + + DEBUG(SSSDBG_OP_FAILURE, + "Client [%s] timed out before identification [%p]!\n", + name == NULL ? "unknown" : name, te); + + /* Kill the connection. */ + talloc_zfree(dp_cli); + talloc_zfree(conn); +} + +void dp_client_cancel_timeout(struct sbus_connection *conn) +{ + struct dp_client *dp_cli; + + dp_cli = sbus_connection_get_data(conn, struct dp_client); + if (dp_cli != NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "Cancel DP client timeout [%p]\n", dp_cli->timeout); + talloc_zfree(dp_cli->timeout); + } +} + +errno_t +dp_client_init(struct sbus_connection *cli_conn, + struct data_provider *provider) +{ + struct dp_client *dp_cli; + struct timeval tv; + + /* When connection is lost we also free the client. */ + dp_cli = talloc_zero(cli_conn, struct dp_client); + if (dp_cli == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory, killing connection.\n"); + return ENOMEM; + } + + dp_cli->provider = provider; + dp_cli->conn = cli_conn; + dp_cli->initialized = false; + dp_cli->timeout = NULL; + + /* Setup timeout in case client fails to register himself in time. */ + tv = tevent_timeval_current_ofs(5, 0); + dp_cli->timeout = tevent_add_timer(provider->ev, dp_cli, tv, + dp_client_handshake_timeout, dp_cli); + if (dp_cli->timeout == NULL) { + /* Connection is closed in the caller. */ + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory, killing connection\n"); + return ENOMEM; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Set-up Backend ID timeout [%p]\n", dp_cli->timeout); + + sbus_connection_set_data(cli_conn, dp_cli); + + return EOK; +} + +struct data_provider * +dp_client_provider(struct dp_client *dp_cli) +{ + if (dp_cli == NULL) { + return NULL; + } + + return dp_cli->provider; +} + +struct be_ctx * +dp_client_be(struct dp_client *dp_cli) +{ + if (dp_cli == NULL || dp_cli->provider == NULL) { + return NULL; + } + + return dp_cli->provider->be_ctx; +} + +struct sbus_connection * +dp_client_conn(struct dp_client *dp_cli) +{ + if (dp_cli == NULL) { + return NULL; + } + + return dp_cli->conn; +} diff --git a/src/providers/data_provider/dp_custom_data.h b/src/providers/data_provider/dp_custom_data.h new file mode 100644 index 0000000..1ca85f7 --- /dev/null +++ b/src/providers/data_provider/dp_custom_data.h @@ -0,0 +1,88 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _DP_CUSTOM_DATA_H_ +#define _DP_CUSTOM_DATA_H_ + +#include "providers/data_provider/dp.h" + +/* Request handler private data. */ + +struct dp_sudo_data { + uint32_t type; + const char **rules; +}; + +struct dp_hostid_data { + const char *name; + const char *alias; +}; + +struct dp_autofs_data { + const char *mapname; + const char *entryname; +}; + +struct dp_subdomains_data { + const char *domain_hint; +}; + +struct dp_get_acct_domain_data { + uint32_t entry_type; + uint32_t filter_type; + const char *filter_value; +}; + +struct dp_id_data { + uint32_t entry_type; + uint32_t filter_type; + const char *filter_value; + const char *extra_value; + const char *domain; +}; + +struct dp_resolver_data { + uint32_t filter_type; + const char *filter_value; +}; + +/* Reply private data. */ + +struct dp_reply_std { + int dp_error; + int error; + const char *message; +}; + +void dp_reply_std_set(struct dp_reply_std *reply, + int dp_error, + int error, + const char *msg); + +void dp_req_reply_std(const char *request_name, + struct dp_reply_std *reply, + uint16_t *_dp_error, + uint32_t *_error, + const char **_message); + +/* Convert pair of ret and dp_error to single ret value. */ +errno_t dp_error_to_ret(errno_t ret, int dp_error); + +#endif /* _DP_CUSTOM_DATA_H_ */ diff --git a/src/providers/data_provider/dp_flags.h b/src/providers/data_provider/dp_flags.h new file mode 100644 index 0000000..52e666d --- /dev/null +++ b/src/providers/data_provider/dp_flags.h @@ -0,0 +1,29 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _DP_FLAGS_H_ +#define _DP_FLAGS_H_ + +/** + * If backend is offline, respond with ERR_OFFLINE immediately. + */ +#define DP_FAST_REPLY 0x0001 + +#endif /* _DP_FLAGS_H_ */ diff --git a/src/providers/data_provider/dp_iface.h b/src/providers/data_provider/dp_iface.h new file mode 100644 index 0000000..1ebd4f5 --- /dev/null +++ b/src/providers/data_provider/dp_iface.h @@ -0,0 +1,250 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef DP_IFACE_H_ +#define DP_IFACE_H_ + +#include "sbus/sbus_request.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp.h" + +struct tevent_req * +dp_get_account_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + uint32_t entry_type, + const char *filter, + const char *domain, + const char *extra, + uint32_t cli_id); + +errno_t +dp_get_account_info_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg); + +struct tevent_req * +dp_pam_handler_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + struct pam_data *pd); + +errno_t +dp_pam_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_pd); + +struct tevent_req * +dp_sudo_handler_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + DBusMessageIter *read_iter); + +errno_t +dp_sudo_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg); + +struct tevent_req * +dp_host_handler_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + const char *name, + const char *alias, + uint32_t cli_id); + +errno_t +dp_host_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg); + +struct tevent_req * +dp_autofs_handler_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + const char *mapname); + +errno_t +dp_autofs_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg); + +struct tevent_req * +dp_autofs_get_map_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + const char *mapname, + uint32_t cli_id); + +errno_t dp_autofs_get_map_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req); + +struct tevent_req * +dp_autofs_get_entry_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + const char *mapname, + const char *entryname, + uint32_t cli_id); + +errno_t dp_autofs_get_entry_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req); + +struct tevent_req * +dp_autofs_enumerate_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + const char *mapname, + uint32_t cli_id); + +errno_t dp_autofs_enumerate_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req); + +struct tevent_req * +dp_subdomains_handler_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + const char *domain_hint); + +errno_t +dp_subdomains_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg); + +struct tevent_req * +dp_resolver_handler_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + uint32_t entry_type, + uint32_t filter_type, + const char *filter_value, + uint32_t cli_id); + +errno_t +dp_resolver_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg); + +/* + * Return a domain the account belongs to. + * + * The request uses the dp_reply_std structure for reply, with the following + * semantics: + * - DP_ERR_OK - it is expected that the string message contains the domain name + * the entry was found in. A 'negative' reply where the + * request returns DP_ERR_OK, but no domain should be treated + * as authoritative, as if the entry does not exist. + * - DP_ERR_* - the string message contains error string that corresponds + * to the errno field in dp_reply_std(). + */ +struct tevent_req * +dp_get_account_domain_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + uint32_t entry_type, + const char *filter, + uint32_t cli_id); + +errno_t +dp_get_account_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg); + +/* sssd.DataProvider.Client */ +errno_t +dp_client_register(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct data_provider *provider, + const char *name); + +/* sssd.DataProvider.Backend */ +errno_t dp_backend_is_online(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx, + const char *domname, + bool *_is_online); + +/* sssd.DataProvider.Failover */ +errno_t +dp_failover_list_services(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx, + const char *domname, + const char ***_services); + +errno_t +dp_failover_active_server(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx, + const char *service_name, + const char **_server); + +errno_t +dp_failover_list_servers(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx, + const char *service_name, + const char ***_servers); + +/* sssd.DataProvider.AccessControl */ +struct tevent_req * +dp_access_control_refresh_rules_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider); + +errno_t +dp_access_control_refresh_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req); + + +errno_t +dp_add_sr_attribute(struct be_ctx *be_ctx); +#endif /* DP_IFACE_H_ */ diff --git a/src/providers/data_provider/dp_iface_backend.c b/src/providers/data_provider/dp_iface_backend.c new file mode 100644 index 0000000..8279189 --- /dev/null +++ b/src/providers/data_provider/dp_iface_backend.c @@ -0,0 +1,59 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "sbus/sbus_request.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/backend.h" +#include "util/util.h" + +errno_t +dp_backend_is_online(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx, + const char *domname, + bool *_is_online) +{ + struct sss_domain_info *domain; + + if (SBUS_REQ_STRING_IS_EMPTY(domname)) { + domain = be_ctx->domain; + } else { + domain = find_domain_by_name(be_ctx->domain, domname, false); + if (domain == NULL) { + return ERR_DOMAIN_NOT_FOUND; + } + } + + /** + * FIXME: https://github.com/SSSD/sssd/issues/4825 + * domain->state is set only for subdomains not for the main domain + */ + if (be_ctx->domain == domain) { + *_is_online = be_is_offline(be_ctx) == false; + } else { + *_is_online = domain->state == DOM_ACTIVE; + } + + return EOK; +} diff --git a/src/providers/data_provider/dp_iface_failover.c b/src/providers/data_provider/dp_iface_failover.c new file mode 100644 index 0000000..99d8ac0 --- /dev/null +++ b/src/providers/data_provider/dp_iface_failover.c @@ -0,0 +1,328 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "sbus/sbus_request.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/backend.h" +#include "util/util.h" + +static errno_t +dp_failover_list_services_ldap(struct be_ctx *be_ctx, + const char **services, + int *_count) +{ + struct be_svc_data *svc; + int count; + + count = 0; + DLIST_FOR_EACH(svc, be_ctx->be_fo->svcs) { + services[count] = talloc_strdup(services, svc->name); + if (services[count] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + return ENOMEM; + } + count++; + } + + *_count = count; + + return EOK; +} + +static errno_t +dp_failover_list_services_ad(struct be_ctx *be_ctx, + struct sss_domain_info *domain, + const char **services, + int *_count) +{ + char *fo_svc_name = NULL; + struct be_svc_data *svc; + errno_t ret; + int count; + + fo_svc_name = talloc_asprintf(services, "sd_%s", domain->name); + if (fo_svc_name == NULL) { + ret = ENOMEM; + goto done; + } + + count = 0; + DLIST_FOR_EACH(svc, be_ctx->be_fo->svcs) { + /* Drop each sd_gc_* since this service is not used with AD at all, + * we only connect to AD_GC for global catalog. */ + if (strncasecmp(svc->name, "sd_gc_", strlen("sd_gc_")) == 0) { + continue; + } + + /* Drop all subdomain services for different domain. */ + if (strncasecmp(svc->name, "sd_", strlen("sd_")) == 0) { + if (!IS_SUBDOMAIN(domain)) { + continue; + } + + if (strcasecmp(svc->name, fo_svc_name) != 0) { + continue; + } + } + + if (IS_SUBDOMAIN(domain)) { + /* Drop AD since we connect to subdomain.com for LDAP. */ + if (strcasecmp(svc->name, "AD") == 0) { + continue; + } + } + + services[count] = talloc_strdup(services, svc->name); + if (services[count] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + count++; + } + + *_count = count; + + ret = EOK; + +done: + talloc_free(fo_svc_name); + return ret; +} + +static errno_t +dp_failover_list_services_ipa(struct be_ctx *be_ctx, + struct sss_domain_info *domain, + const char **services, + int *_count) +{ + struct be_svc_data *svc; + char *fo_svc_name = NULL; + char *fo_gc_name = NULL; + errno_t ret; + int count; + + fo_svc_name = talloc_asprintf(services, "sd_%s", domain->name); + if (fo_svc_name == NULL) { + ret = ENOMEM; + goto done; + } + + fo_gc_name = talloc_asprintf(services, "sd_gc_%s", domain->name); + if (fo_gc_name == NULL) { + ret = ENOMEM; + goto done; + } + + count = 0; + DLIST_FOR_EACH(svc, be_ctx->be_fo->svcs) { + /* Drop all subdomain services for different domain. */ + if (strncasecmp(svc->name, "sd_", strlen("sd_")) == 0) { + if (!IS_SUBDOMAIN(domain)) { + continue; + } + + if (strcasecmp(svc->name, fo_svc_name) != 0 + && strcasecmp(svc->name, fo_gc_name) != 0) { + continue; + } + } + + services[count] = talloc_strdup(services, svc->name); + if (services[count] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + return ENOMEM; + } + count++; + } + + *_count = count; + + ret = EOK; + +done: + talloc_free(fo_svc_name); + talloc_free(fo_gc_name); + + return ret; +} + +enum dp_fo_svc_type { + DP_FO_SVC_LDAP = 0, + DP_FO_SVC_AD = 1, + DP_FO_SVC_IPA = 1 << 1, + DP_FO_SVC_MIXED = DP_FO_SVC_AD | DP_FO_SVC_IPA +}; + +errno_t +dp_failover_list_services(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx, + const char *domname, + const char ***_services) +{ + enum dp_fo_svc_type svc_type = DP_FO_SVC_LDAP; + struct sss_domain_info *domain; + struct be_svc_data *svc; + const char **services; + int num_services; + errno_t ret; + + if (SBUS_REQ_STRING_IS_EMPTY(domname)) { + domain = be_ctx->domain; + } else { + domain = find_domain_by_name(be_ctx->domain, domname, false); + if (domain == NULL) { + return ERR_DOMAIN_NOT_FOUND; + } + } + + /** + * Returning list of failover services is currently rather difficult + * since there is only one failover context for the whole backend. + * + * The list of services for the given domain depends on whether it is + * a master domain or a subdomain and whether we are using IPA, AD or + * LDAP backend. + * + * For LDAP we just return everything we have. + * For AD master domain we return AD, AD_GC. + * For AD subdomain we return subdomain.com, AD_GC. + * For IPA in client mode we return IPA. + * For IPA in server mode we return IPA for master domain and + * subdomain.com, gc_subdomain.com for subdomain. + * + * We also return everything else for all cases if any other service + * such as kerberos is configured separately. + */ + + /* Allocate enough space. */ + num_services = 0; + DLIST_FOR_EACH(svc, be_ctx->be_fo->svcs) { + num_services++; + + if (strcasecmp(svc->name, "AD") == 0) { + svc_type |= DP_FO_SVC_AD; + } else if (strcasecmp(svc->name, "IPA") == 0) { + svc_type |= DP_FO_SVC_IPA; + } + } + + services = talloc_zero_array(mem_ctx, const char *, num_services + 1); + if (services == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + return ENOMEM; + } + + /* Fill the list. */ + switch (svc_type) { + case DP_FO_SVC_LDAP: + case DP_FO_SVC_MIXED: + ret = dp_failover_list_services_ldap(be_ctx, services, &num_services); + break; + case DP_FO_SVC_AD: + ret = dp_failover_list_services_ad(be_ctx, domain, + services, &num_services); + break; + case DP_FO_SVC_IPA: + ret = dp_failover_list_services_ipa(be_ctx, domain, + services, &num_services); + break; + default: + ret = ERR_INTERNAL; + break; + } + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create service list [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(services); + return ret; + } + + *_services = services; + + return EOK; +} + +errno_t +dp_failover_active_server(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx, + const char *service_name, + const char **_server) +{ + struct be_svc_data *svc; + bool found = false; + + DLIST_FOR_EACH(svc, be_ctx->be_fo->svcs) { + if (strcmp(svc->name, service_name) == 0) { + found = true; + break; + } + } + + if (!found) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get server name\n"); + return ENOENT; + } + + *_server = svc->last_good_srv == NULL ? "" : svc->last_good_srv; + + return EOK; +} + +errno_t +dp_failover_list_servers(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx, + const char *service_name, + const char ***_servers) +{ + struct be_svc_data *svc; + const char **servers; + bool found = false; + size_t count; + + DLIST_FOR_EACH(svc, be_ctx->be_fo->svcs) { + if (strcmp(svc->name, service_name) == 0) { + found = true; + break; + } + } + + if (!found) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get server list\n"); + return ENOENT; + } + + servers = fo_svc_server_list(sbus_req, svc->fo_service, &count); + if (servers == NULL) { + return ENOMEM; + } + + *_servers = servers; + + return EOK; +} diff --git a/src/providers/data_provider/dp_methods.c b/src/providers/data_provider/dp_methods.c new file mode 100644 index 0000000..9e49c5f --- /dev/null +++ b/src/providers/data_provider/dp_methods.c @@ -0,0 +1,128 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "config.h" +#include "providers/data_provider/dp.h" +#include "providers/data_provider/dp_private.h" +#include "providers/backend.h" +#include "util/util.h" + +void _dp_set_method(struct dp_method *methods, + enum dp_methods method, + dp_req_send_fn send_fn, + dp_req_recv_fn recv_fn, + void *method_data, + const char *method_dtype, + const char *request_dtype, + const char *output_dtype, + uint32_t output_size) +{ + if (method >= DP_METHOD_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: invalid method %d\n", method); + return; + } + + /* Each method can be set only once, if we attempt to set it twice it + * is a bug in module initialization. */ + if (methods[method].send_fn != NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: method %d is already set!\n", method); + return; + } + + if (send_fn == NULL || recv_fn == NULL || method_dtype == NULL + || request_dtype == NULL || output_dtype == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: one or more required parameter was " + "not provided for method %d\n", method); + return; + } + + methods[method].send_fn = send_fn; + methods[method].recv_fn = recv_fn; + methods[method].method_data = method_data; + + methods[method].method_dtype = method_dtype; + methods[method].request_dtype = request_dtype; + methods[method].output_dtype = output_dtype; + methods[method].output_size = output_size; +} + +bool dp_method_enabled(struct data_provider *provider, + enum dp_targets target, + enum dp_methods method) +{ + struct dp_target *dp_target; + + if (target >= DP_TARGET_SENTINEL || method >= DP_METHOD_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: Invalid target or method ID\n"); + return false; + } + + if (provider == NULL || provider->targets == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Target %s is not yet initialized\n", + dp_target_to_string(target)); + return false; + } + + dp_target = provider->targets[target]; + if (dp_target == NULL || dp_target->initialized == false) { + DEBUG(SSSDBG_TRACE_FUNC, "Target %s is not configured\n", + dp_target_to_string(target)); + return false; + } + + if (dp_target->methods[method].send_fn == NULL) { + return false; + } + + return true; +} + +errno_t dp_find_method(struct data_provider *provider, + enum dp_targets target, + enum dp_methods method, + struct dp_method **_execute) +{ + struct dp_method *execute; + + if (target >= DP_TARGET_SENTINEL || method >= DP_METHOD_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: Invalid target or method ID\n"); + return ERR_INTERNAL; + } + + if (!dp_target_initialized(provider->targets, target)) { + DEBUG(SSSDBG_CONF_SETTINGS, "Target [%s] is not initialized\n", + dp_target_to_string(target)); + return ERR_MISSING_DP_TARGET; + } + + execute = &provider->targets[target]->methods[method]; + if (execute->send_fn == NULL || execute->recv_fn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Bug: Invalid combination of target [%s] and method [%d]\n", + dp_target_to_string(target), method); + return ERR_INTERNAL; + } + + *_execute = execute; + + return EOK; +} diff --git a/src/providers/data_provider/dp_modules.c b/src/providers/data_provider/dp_modules.c new file mode 100644 index 0000000..2e6e33d --- /dev/null +++ b/src/providers/data_provider/dp_modules.c @@ -0,0 +1,224 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include "config.h" +#include "providers/data_provider/dp.h" +#include "providers/data_provider/dp_private.h" +#include "providers/backend.h" +#include "util/util.h" + +/* There can be at most the same number of different modules loaded at + * one time as the maximum number of defined targets. */ +#define DP_MAX_MODULES DP_TARGET_SENTINEL + +#define DP_MODULE_PATH DATA_PROVIDER_PLUGINS_PATH "/libsss_%s.so" +#define DP_MODULE_INIT_FN "sssm_%s_init" + +static errno_t dp_module_open_lib(struct dp_module *module) +{ + char *libpath = NULL; + errno_t ret; + + libpath = talloc_asprintf(module, DP_MODULE_PATH, module->name); + if (libpath == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Loading module [%s] with path [%s]\n", + module->name, libpath); + + module->libhandle = dlopen(libpath, RTLD_NOW); + if (module->libhandle == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load module [%s] with path " + "[%s]: %s\n", module->name, libpath, dlerror()); + ret = ELIBACC; + goto done; + } + + ret = EOK; + +done: + talloc_free(libpath); + return ret; +} + +static errno_t dp_module_run_constructor(struct dp_module *module, + struct be_ctx *be_ctx, + struct data_provider *provider) +{ + char *fn_name; + dp_module_init_fn fn; + errno_t ret; + + fn_name = talloc_asprintf(module, DP_MODULE_INIT_FN, module->name); + if (fn_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + return ENOMEM; + } + + fn = (dp_module_init_fn)dlsym(module->libhandle, fn_name); + if (fn != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Executing module [%s] constructor.\n", + module->name); + + ret = fn(module, be_ctx, provider, module->name, &module->module_data); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Module [%s] constructor failed " + "[%d]: %s\n", module->name, ret, sss_strerror(ret)); + goto done; + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, "No constructor found for module [%s].\n", + module->name); + module->module_data = NULL; + ret = EOK; + goto done; + } + + ret = EOK; + +done: + talloc_free(fn_name); + return ret; +} + +static errno_t dp_module_find(struct dp_module **modules, + const char *name, + struct dp_module **_module, + unsigned int *_slot) +{ + unsigned int slot; + + for (slot = 0; modules[slot] != NULL; slot++) { + if (strcmp(modules[slot]->name, name) == 0) { + *_module = modules[slot]; + *_slot = slot; + + return EOK; + } + } + + if (slot == DP_MAX_MODULES) { + /* This should not happen. */ + DEBUG(SSSDBG_CRIT_FAILURE, "All module slots are taken.\n"); + + return ERR_INTERNAL; + } + + *_module = NULL; + *_slot = slot; + + return EOK; +} + +static struct dp_module *dp_module_create(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *name) +{ + struct dp_module *module; + errno_t ret; + + module = talloc_zero(mem_ctx, struct dp_module); + if (module == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n"); + ret = ENOMEM; + goto done; + } + + module->name = talloc_strdup(module, name); + if (module->name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = dp_module_open_lib(module); + if (ret != EOK) { + goto done; + } + + ret = dp_module_run_constructor(module, be_ctx, provider); + if (ret != EOK) { + goto done; + } + + module->initialized = true; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(module); + return NULL; + } + + return module; +} + +struct dp_module *dp_load_module(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_module **modules, + const char *name) +{ + struct dp_module *module; + unsigned int free_slot; + errno_t ret; + + ret = dp_module_find(modules, name, &module, &free_slot); + if (ret != EOK) { + return NULL; + } + + if (module != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Module [%s] is already loaded.\n", name); + return module; + } + + DEBUG(SSSDBG_TRACE_FUNC, "About to load module [%s].\n", name); + + module = dp_module_create(mem_ctx, be_ctx, provider, name); + if (module == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create DP module.\n"); + return NULL; + } + + modules[free_slot] = module; + + return module; +} + +errno_t dp_init_modules(TALLOC_CTX *mem_ctx, struct dp_module ***_modules) +{ + struct dp_module **modules; + + modules = talloc_zero_array(mem_ctx, struct dp_module *, + DP_MAX_MODULES + 1); + if (modules == NULL) { + return ENOMEM; + } + + *_modules = modules; + + return EOK; +} diff --git a/src/providers/data_provider/dp_private.h b/src/providers/data_provider/dp_private.h new file mode 100644 index 0000000..b86e8c3 --- /dev/null +++ b/src/providers/data_provider/dp_private.h @@ -0,0 +1,139 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _DP_PRIVATE_H_ +#define _DP_PRIVATE_H_ + +#include +#include + +#include "providers/data_provider/dp.h" +#include "util/util.h" + +#define DP_REQ_DEBUG(level, name, fmt, ...) \ + DEBUG(level, "DP Request [%s]: " fmt "\n", (name ?: "Unknown"), ##__VA_ARGS__) + +/* Tracing message, changing this can break log parsing tools */ +#define SSS_REQ_TRACE_CID_DP_REQ(level, name, fmt, ...) \ + DP_REQ_DEBUG(level, name, "REQ_TRACE: " fmt, ##__VA_ARGS__) + +enum dp_clients { + DPC_NSS, + DPC_PAM, + DPC_IFP, + DPC_PAC, + DPC_SUDO, + DPC_HOST, + DPC_AUTOFS, + + DP_CLIENT_SENTINEL +}; + +struct dp_req; +struct dp_client; + +struct dp_module { + bool initialized; + const char *name; + void *module_data; + void *libhandle; +}; + +struct dp_target { + const char *name; + const char *module_name; + bool explicitly_configured; + + bool initialized; + enum dp_targets target; + struct dp_module *module; + struct dp_method *methods; +}; + +struct dp_method { + dp_req_send_fn send_fn; + dp_req_recv_fn recv_fn; + void *method_data; + const char *method_dtype; + const char *request_dtype; + const char *output_dtype; + uint32_t output_size; +}; + +struct data_provider { + uid_t uid; + gid_t gid; + struct be_ctx *be_ctx; + struct tevent_context *ev; + struct sbus_server *sbus_server; + struct sbus_connection *sbus_conn; + struct dp_client *clients[DP_CLIENT_SENTINEL]; + bool terminating; + + struct { + /* Numeric identificator that will be assigned to next request. */ + uint32_t index; + + /* List of all ongoing requests. */ + uint32_t num_active; + struct dp_req *active; + } requests; + + struct dp_module **modules; + struct dp_target **targets; +}; + +errno_t dp_find_method(struct data_provider *provider, + enum dp_targets target, + enum dp_methods method, + struct dp_method **_execute); + +struct dp_module *dp_load_module(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_module **modules, + const char *name); + +errno_t dp_init_modules(TALLOC_CTX *mem_ctx, struct dp_module ***_modules); + +const char *dp_target_to_string(enum dp_targets target); + +bool dp_target_initialized(struct dp_target **targets, enum dp_targets type); + +errno_t dp_init_targets(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_module **modules); + +/* Data provider request. */ + +void dp_terminate_active_requests(struct data_provider *provider); + +/* Client shared functions. */ + +errno_t +dp_client_init(struct sbus_connection *cli_conn, + struct data_provider *provider); + +struct data_provider *dp_client_provider(struct dp_client *dp_cli); +struct be_ctx *dp_client_be(struct dp_client *dp_cli); +struct sbus_connection *dp_client_conn(struct dp_client *dp_cli); + +#endif /* _DP_PRIVATE_H_ */ diff --git a/src/providers/data_provider/dp_reply_std.c b/src/providers/data_provider/dp_reply_std.c new file mode 100644 index 0000000..74e8e04 --- /dev/null +++ b/src/providers/data_provider/dp_reply_std.c @@ -0,0 +1,150 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "providers/data_provider/dp_private.h" +#include "providers/backend.h" +#include "util/sss_utf8.h" +#include "util/util.h" + +static const char *dp_err_to_string(int dp_err_type) +{ + switch (dp_err_type) { + case DP_ERR_OK: + return "Success"; + case DP_ERR_OFFLINE: + return "Provider is Offline"; + case DP_ERR_TIMEOUT: + return "Request timed out"; + case DP_ERR_FATAL: + return "Internal Error"; + default: + break; + } + + return "Unknown Error"; +} + +static const char *safe_be_req_err_msg(const char *msg_in, + int dp_err_type) +{ + bool ok; + + if (msg_in == NULL) { + /* No custom error, just use default */ + return dp_err_to_string(dp_err_type); + } + + ok = sss_utf8_check((const uint8_t *) msg_in, + strlen(msg_in)); + if (!ok) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Back end message [%s] contains invalid non-UTF8 character, " \ + "using default\n", msg_in); + return dp_err_to_string(dp_err_type); + } + + return msg_in; +} + +void dp_req_reply_std(const char *request_name, + struct dp_reply_std *reply, + uint16_t *_dp_error, + uint32_t *_error, + const char **_message) +{ + const char *safe_err_msg; + + safe_err_msg = safe_be_req_err_msg(reply->message, reply->dp_error); + + DP_REQ_DEBUG(SSSDBG_TRACE_LIBS, request_name, "Returning [%s]: %d,%d,%s", + dp_err_to_string(reply->dp_error), reply->dp_error, + reply->error, reply->message); + + *_dp_error = reply->dp_error; + *_error = reply->error; + *_message = safe_err_msg; +} + +void dp_reply_std_set(struct dp_reply_std *reply, + int dp_error, + int error, + const char *msg) +{ + const char *def_msg; + + if (dp_error == DP_ERR_DECIDE) { + switch (error) { + case EOK: + dp_error = DP_ERR_OK; + break; + case ERR_OFFLINE: + dp_error = DP_ERR_OFFLINE; + break; + case ETIMEDOUT: + dp_error = DP_ERR_TIMEOUT; + break; + default: + dp_error = DP_ERR_FATAL; + break; + } + } + + switch (dp_error) { + case DP_ERR_OK: + def_msg = "Success"; + break; + case DP_ERR_OFFLINE: + def_msg = "Offline"; + break; + default: + def_msg = sss_strerror(error); + break; + } + + if (dp_error == DP_ERR_OK && error != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "DP Error is OK on failed request?\n"); + } + + reply->dp_error = dp_error; + reply->error = error; + reply->message = msg == NULL ? def_msg : msg; +} + +errno_t dp_error_to_ret(errno_t ret, int dp_error) +{ + if (ret != EOK) { + return ret; + } + + switch (dp_error) { + case DP_ERR_OK: + return EOK; + case DP_ERR_OFFLINE: + return ERR_OFFLINE; + case DP_ERR_TIMEOUT: + return ETIMEDOUT; + case DP_ERR_FATAL: + return EFAULT; + } + + return ERR_INTERNAL; +} diff --git a/src/providers/data_provider/dp_request.c b/src/providers/data_provider/dp_request.c new file mode 100644 index 0000000..9c0fcf1 --- /dev/null +++ b/src/providers/data_provider/dp_request.c @@ -0,0 +1,500 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "providers/data_provider/dp_private.h" +#include "providers/backend.h" +#include "util/dlinklist.h" +#include "util/util.h" +#include "util/probes.h" +#include "util/sss_chain_id.h" + +struct dp_req { + struct data_provider *provider; + uint32_t dp_flags; + + struct sss_domain_info *domain; + + enum dp_targets target; + enum dp_methods method; + struct dp_method *execute; + const char *name; + uint32_t num; + uint64_t start_time; + + struct tevent_req *req; + struct tevent_req *handler_req; + void *request_data; + + /* Active request list. */ + struct dp_req *prev; + struct dp_req *next; +}; + +static bool check_data_type(const char *expected, + const char *description, + void *ptr) +{ + void *tmp; + + /* If ptr is NULL we still return true since it is valid case. */ + tmp = talloc_check_name(ptr, expected); + if (tmp != NULL || ptr == NULL) { + return true; + } + + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid %s data type provided. Expected [%s], " + "got [%s].\n", description, expected, talloc_get_name(ptr)); + + return false; +} + +static bool check_method_data(struct dp_method *method, + void *request_data) +{ + if (!check_data_type(method->method_dtype, "method", method->method_data)) { + return false; + } + + if (!check_data_type(method->request_dtype, "request", request_data)) { + return false; + } + + return true; +} + +static int dp_req_destructor(struct dp_req *dp_req) +{ + DLIST_REMOVE(dp_req->provider->requests.active, dp_req); + + if (dp_req->provider->requests.num_active == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: there are no active requests!\n"); + return 0; + } + + dp_req->provider->requests.num_active--; + + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, dp_req->name, "Request removed."); + + DEBUG(SSSDBG_TRACE_FUNC, "Number of active DP request: %u\n", + dp_req->provider->requests.num_active); + + return 0; +} + +static errno_t dp_attach_req(struct dp_req *dp_req, + struct data_provider *provider, + const char *name, + uint32_t dp_flags, + uint32_t cli_id, + const char *sender_name) +{ + /* If we run out of numbers we simply overflow. Zero is a reserved value + * in debug chain id thus we need to skip it. */ + if (provider->requests.index == 0) { + provider->requests.index = 1; + } + dp_req->num = provider->requests.index++; + + /* Set the chain id for this request. */ + sss_chain_id_set(dp_req->num); + + dp_req->name = talloc_asprintf(dp_req, "%s #%u", name, dp_req->num); + if (dp_req->name == NULL) { + return ENOMEM; + } + + /* Attach this request to active request list. */ + DLIST_ADD(provider->requests.active, dp_req); + provider->requests.num_active++; + + talloc_set_destructor(dp_req, dp_req_destructor); + + if (cli_id > 0) { + SSS_REQ_TRACE_CID_DP_REQ(SSSDBG_TRACE_FUNC, dp_req->name, + "New request. [%s CID #%u] Flags [%#.4x].", + sender_name, cli_id, dp_flags); + if (be_is_offline(provider->be_ctx)) { + DEBUG(SSSDBG_TRACE_FUNC, "[CID #%u] Backend is offline! " \ + "Using cached data if available\n", cli_id); + } + } else { + SSS_REQ_TRACE_CID_DP_REQ(SSSDBG_TRACE_FUNC, dp_req->name, + "New request. Flags [%#.4x].", + dp_flags); + } + + DEBUG(SSSDBG_TRACE_FUNC, "Number of active DP request: %u\n", + provider->requests.num_active); + + return EOK; +} + +static errno_t +dp_req_new(TALLOC_CTX *mem_ctx, + struct data_provider *provider, + const char *domainname, + const char *name, + uint32_t cli_id, + const char *sender_name, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + struct tevent_req *req, + struct dp_req **_dp_req) +{ + struct dp_req *dp_req; + struct be_ctx *be_ctx; + errno_t ret; + + /* We set output even for error to simplify code flow in the caller. */ + *_dp_req = NULL; + + dp_req = talloc_zero(mem_ctx, struct dp_req); + if (dp_req == NULL) { + return ENOMEM; + } + + dp_req->provider = provider; + dp_req->dp_flags = dp_flags; + dp_req->target = target; + dp_req->method = method; + dp_req->request_data = request_data; + dp_req->req = req; + + ret = dp_attach_req(dp_req, provider, name, dp_flags, cli_id, sender_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create DP request " + "[%s] [%d]: %s\n", name, ret, sss_strerror(ret)); + talloc_free(dp_req); + return ret; + } + + dp_req->start_time = get_start_time(); + + /* Now the request is created. We will return it even in case of error + * so we can get better debug messages. */ + + talloc_steal(dp_req, dp_req->request_data); + *_dp_req = dp_req; + + be_ctx = provider->be_ctx; + dp_req->domain = be_ctx->domain; + if (domainname != NULL) { + dp_req->domain = find_domain_by_name(be_ctx->domain, domainname, true); + if (dp_req->domain == NULL) { + /* domain might be skipped by 'ad_enabled_domains' option */ + DEBUG(SSSDBG_CONF_SETTINGS, "Unknown domain: %s\n", domainname); + return ERR_DOMAIN_NOT_FOUND; + } + } + + ret = dp_find_method(provider, target, method, &dp_req->execute); + + return ret; +} + +static errno_t +file_dp_request(TALLOC_CTX *mem_ctx, + struct data_provider *provider, + const char *domainname, + const char *name, + uint32_t cli_id, + const char *sender_name, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + struct tevent_req *req, + struct dp_req **_dp_req) +{ + struct dp_req_params *dp_params; + dp_req_send_fn send_fn; + struct dp_req *dp_req; + struct be_ctx *be_ctx; + uint64_t old_chain_id; + errno_t ret; + + old_chain_id = sss_chain_id_get(); + be_ctx = provider->be_ctx; + + ret = dp_req_new(mem_ctx, provider, domainname, name, cli_id, sender_name, + target, method, dp_flags, request_data, req, &dp_req); + if (ret != EOK) { + *_dp_req = dp_req; + goto done; + } + + /* DP request is already created. We will always return it to get nice + * debug messages. */ + *_dp_req = dp_req; + + /* Check that provided data are of correct type. */ + + if (!check_method_data(dp_req->execute, dp_req->request_data)) { + ret = ERR_INVALID_DATA_TYPE; + goto done; + } + + /* Process data provider flags */ + + if (dp_flags & DP_FAST_REPLY && be_is_offline(be_ctx)) { + ret = ERR_OFFLINE; + goto done; + } + + /* File request */ + + dp_params = talloc_zero(dp_req, struct dp_req_params); + if (dp_params == NULL) { + ret = ENOMEM; + goto done; + } + + dp_params->ev = provider->ev; + dp_params->be_ctx = be_ctx; + dp_params->domain = dp_req->domain; + dp_params->target = dp_req->target; + dp_params->method = dp_req->method; + + send_fn = dp_req->execute->send_fn; + dp_req->handler_req = send_fn(dp_req, dp_req->execute->method_data, + dp_req->request_data, dp_params); + if (dp_req->handler_req == NULL) { + ret = ENOMEM; + goto done; + } + + *_dp_req = dp_req; + + ret = EOK; + +done: + /* Restore the chain id to its original value when leaving this request. */ + sss_chain_id_set(old_chain_id); + return ret; +} + +struct dp_req_state { + struct dp_req *dp_req; + dp_req_recv_fn recv_fn; + void *output_data; +}; + +static void dp_req_done(struct tevent_req *subreq); + +struct tevent_req *dp_req_send(TALLOC_CTX *mem_ctx, + struct data_provider *provider, + const char *domain, + const char *name, + uint32_t cli_id, + const char *sender_name, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + const char **_request_name) +{ + struct dp_req_state *state; + const char *request_name; + struct tevent_req *req; + struct dp_req *dp_req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_req_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ret = file_dp_request(state, provider, domain, name, cli_id, sender_name, + target, method, dp_flags, request_data, req, &dp_req); + + if (dp_req == NULL) { + /* An error occurred before request could be created. */ + if (_request_name != NULL) { + *_request_name = "Request Not Yet Created"; + } + + goto immediately; + } + + PROBE(DP_REQ_SEND, domain, dp_req->name, target, method); + state->dp_req = dp_req; + if (_request_name != NULL) { + request_name = talloc_strdup(mem_ctx, dp_req->name); + if (request_name == NULL) { + *_request_name = "Request Not Yet Created"; + ret = ENOMEM; + goto immediately; + } + *_request_name = request_name; + } + + if (ret != EOK) { + goto immediately; + } + + state->recv_fn = dp_req->execute->recv_fn; + state->output_data = talloc_zero_size(state, dp_req->execute->output_size); + if (state->output_data == NULL) { + ret = ENOMEM; + goto immediately; + } + + talloc_set_name_const(state->output_data, dp_req->execute->output_dtype); + + tevent_req_set_callback(dp_req->handler_req, dp_req_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, provider->ev); + + return req; +} + +static void dp_req_done(struct tevent_req *subreq) +{ + struct dp_req_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_req_state); + + ret = state->recv_fn(state->output_data, subreq, state->output_data); + + /* subreq is the same as dp_req->handler_req */ + talloc_zfree(subreq); + state->dp_req->handler_req = NULL; + + PROBE(DP_REQ_DONE, state->dp_req->name, state->dp_req->target, + state->dp_req->method, ret, sss_strerror(ret)); + + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->dp_req->name, + "Request handler finished [%d]: %s", ret, sss_strerror(ret)); + DP_REQ_DEBUG(SSSDBG_PERF_STAT, state->dp_req->name, + "Handling request took %s.", + sss_format_time(get_spend_time_us(state->dp_req->start_time))); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t _dp_req_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char *output_dtype, + void **_output_data) +{ + struct dp_req_state *state; + + state = tevent_req_data(req, struct dp_req_state); + + if (state->dp_req != NULL) { + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->dp_req->name, + "Receiving request data."); + } else { + /* dp_req may be NULL in case we error when filing request */ + DEBUG(SSSDBG_TRACE_FUNC, + "Receiving data of prematurely interrupted request!\n"); + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (!check_data_type(output_dtype, "output", state->output_data)) { + return ERR_INVALID_DATA_TYPE; + } + + if (_output_data != NULL) { + *_output_data = talloc_steal(mem_ctx, state->output_data); + } + + return EOK; +} + +static void dp_terminate_request(struct dp_req *dp_req) +{ + if (dp_req->handler_req == NULL) { + /* This may occur when the handler already finished but the caller + * of dp request did not yet received data/free dp_req. We just + * return here. */ + return; + } + + /* We will end the handler request and mark dp request as terminated. */ + + DP_REQ_DEBUG(SSSDBG_TRACE_ALL, dp_req->name, "Terminating."); + + talloc_zfree(dp_req->handler_req); + tevent_req_error(dp_req->req, ERR_TERMINATED); +} + +static void dp_terminate_request_list(struct data_provider *provider, + const char *domain) +{ + struct dp_req *next; + struct dp_req *cur; + + if (provider == NULL || provider->requests.active == NULL) { + return; + } + + for (cur = provider->requests.active; cur != NULL; cur = next) { + next = cur->next; + if (domain == NULL || strcmp(cur->domain->name, domain) == 0) { + dp_terminate_request(cur); + } + } +} + +void dp_terminate_active_requests(struct data_provider *provider) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Terminating active data provider requests\n"); + + dp_terminate_request_list(provider, NULL); +} + +void dp_terminate_domain_requests(struct data_provider *provider, + const char *domain) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Terminating active data provider requests " + "for domain [%s]\n", domain); + + if (domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: domain is NULL!\n"); + return; + } + + dp_terminate_request_list(provider, domain); +} diff --git a/src/providers/data_provider/dp_request.h b/src/providers/data_provider/dp_request.h new file mode 100644 index 0000000..aafcbd9 --- /dev/null +++ b/src/providers/data_provider/dp_request.h @@ -0,0 +1,86 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _DP_REQUEST_H_ +#define _DP_REQUEST_H_ + +#include + +#include "providers/data_provider/dp.h" + +struct data_provider; +enum dp_targets; +enum dp_methods; + +struct tevent_req *dp_req_send(TALLOC_CTX *mem_ctx, + struct data_provider *provider, + const char *domain, + const char *name, + uint32_t cli_id, + const char *sender_name, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + const char **_request_name); + +errno_t _dp_req_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char *data_type, + void **_data); + +/** + * Returns value of output data. + * + * @example + * struct dp_reply_std reply; + * ret = dp_req_recv(mem_ctx, req, struct dp_reply_std, &reply); + */ +#define dp_req_recv(mem_ctx, req, data_type, _data) \ +({ \ + data_type *__value = NULL; \ + errno_t __ret; \ + __ret = _dp_req_recv(mem_ctx, req, #data_type, (void**)&__value); \ + if (__ret == EOK) { \ + *(_data) = *__value; \ + } \ + __ret; \ +}) + +/** + * Returns pointer to output data type. + * + * @example + * struct dp_reply_std *reply; + * ret = dp_req_recv_ptr(mem_ctx, req, struct dp_reply_std, &reply); + */ +#define dp_req_recv_ptr(mem_ctx, req, data_type, _data) \ + _dp_req_recv(mem_ctx, req, #data_type, (void**)_data) + +/** + * Recieves data provider request errno code when no output data is set. + * + * @example + * ret = dp_req_recv_no_output(req); + */ +#define dp_req_recv_no_output(req) \ + _dp_req_recv(req, req, "dp_no_output", NULL) + +#endif /* _DP_REQUEST_H_ */ diff --git a/src/providers/data_provider/dp_resp_client.c b/src/providers/data_provider/dp_resp_client.c new file mode 100644 index 0000000..f9161d4 --- /dev/null +++ b/src/providers/data_provider/dp_resp_client.c @@ -0,0 +1,275 @@ +/* + SSSD + + Data Provider Responder client - DP calls responder interface + + 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 3 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; 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, see . +*/ + +#include "config.h" +#include +#include + +#include "confdb/confdb.h" +#include "providers/data_provider.h" +#include "providers/data_provider/dp_private.h" +#include "sss_iface/sss_iface_async.h" + + +/* List of DP clients that deal with users or groups */ +/* FIXME - it would be much cleaner to implement sbus signals + * and let the responder subscribe to these messages rather than + * keep a list here.. + * https://fedorahosted.org/sssd/ticket/2233 + */ +static const char *user_clients[] = { + SSS_BUS_NSS, + SSS_BUS_PAM, + SSS_BUS_IFP, + SSS_BUS_PAC, + SSS_BUS_SUDO, + NULL +}; + +static const char *all_clients[] = { + SSS_BUS_NSS, + SSS_BUS_PAM, + SSS_BUS_IFP, + SSS_BUS_PAC, + SSS_BUS_SUDO, + SSS_BUS_SSH, + SSS_BUS_AUTOFS, + NULL +}; + +void dp_sbus_domain_active(struct data_provider *provider, + struct sss_domain_info *dom) +{ + const char *bus; + struct tevent_req *subreq; + struct sbus_connection *conn; + int i; + + if (provider == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No provider pointer\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Ordering responders to enable domain %s\n", + dom->name); + + conn = provider->sbus_conn; + for (i = 0; all_clients[i] != NULL; i++) { + bus = all_clients[i]; + + subreq = sbus_call_resp_domain_SetActive_send(provider, conn, + bus, SSS_BUS_PATH, dom->name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return; + } + + tevent_req_set_callback(subreq, sbus_unwanted_reply, NULL); + } +} + +void dp_sbus_domain_inconsistent(struct data_provider *provider, + struct sss_domain_info *dom) +{ + const char *bus; + struct tevent_req *subreq; + struct sbus_connection *conn; + int i; + + if (provider == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No provider pointer\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Ordering responders to disable domain %s\n", + dom->name); + + conn = provider->sbus_conn; + for (i = 0; all_clients[i] != NULL; i++) { + bus = all_clients[i]; + subreq = sbus_call_resp_domain_SetInconsistent_send(provider, conn, + bus, SSS_BUS_PATH, dom->name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return; + } + + tevent_req_set_callback(subreq, sbus_unwanted_reply, NULL); + } +} + +void dp_sbus_reset_users_ncache(struct data_provider *provider, + struct sss_domain_info *dom) +{ + const char *bus; + struct tevent_req *subreq; + struct sbus_connection *conn; + int i; + + if (provider == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No provider pointer\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Ordering responders to reset user negative cache\n"); + + conn = provider->sbus_conn; + for (i = 0; user_clients[i] != NULL; i++) { + bus = user_clients[i]; + subreq = sbus_call_resp_negcache_ResetUsers_send(provider, conn, bus, + SSS_BUS_PATH); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return; + } + + tevent_req_set_callback(subreq, sbus_unwanted_reply, NULL); + } +} + +void dp_sbus_reset_groups_ncache(struct data_provider *provider, + struct sss_domain_info *dom) +{ + const char *bus; + struct tevent_req *subreq; + struct sbus_connection *conn; + int i; + + if (provider == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No provider pointer\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Ordering responders to reset group negative cache\n"); + + conn = provider->sbus_conn; + for (i = 0; user_clients[i] != NULL; i++) { + bus = user_clients[i]; + + subreq = sbus_call_resp_negcache_ResetGroups_send(provider, conn, bus, + SSS_BUS_PATH); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return; + } + + tevent_req_set_callback(subreq, sbus_unwanted_reply, NULL); + } +} + +void dp_sbus_reset_users_memcache(struct data_provider *provider) +{ + struct tevent_req *subreq; + + if (provider == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No provider pointer\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Ordering NSS responder to invalidate the users\n"); + + subreq = sbus_call_nss_memcache_InvalidateAllUsers_send(provider, + provider->sbus_conn, SSS_BUS_NSS, SSS_BUS_PATH); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return; + } + + tevent_req_set_callback(subreq, sbus_unwanted_reply, NULL); + + return; +} + +void dp_sbus_reset_groups_memcache(struct data_provider *provider) +{ + struct tevent_req *subreq; + + if (provider == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No provider pointer\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Ordering NSS responder to invalidate the groups\n"); + + subreq = sbus_call_nss_memcache_InvalidateAllGroups_send(provider, + provider->sbus_conn, SSS_BUS_NSS, SSS_BUS_PATH); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return; + } + + tevent_req_set_callback(subreq, sbus_unwanted_reply, NULL); + + return; +} + +void dp_sbus_reset_initgr_memcache(struct data_provider *provider) +{ + struct tevent_req *subreq; + + if (provider == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No provider pointer\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Ordering NSS responder to invalidate the initgroups\n"); + + subreq = sbus_call_nss_memcache_InvalidateAllInitgroups_send(provider, + provider->sbus_conn, SSS_BUS_NSS, SSS_BUS_PATH); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return; + } + + tevent_req_set_callback(subreq, sbus_unwanted_reply, NULL); + + return; +} + +void dp_sbus_invalidate_group_memcache(struct data_provider *provider, + gid_t gid) +{ + struct tevent_req *subreq; + + if (provider == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No provider pointer\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Ordering NSS responder to invalidate the group %"PRIu32" \n", + gid); + + subreq = sbus_call_nss_memcache_InvalidateGroupById_send(provider, + provider->sbus_conn, SSS_BUS_NSS, SSS_BUS_PATH, + (uint32_t)gid); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return; + } + + tevent_req_set_callback(subreq, sbus_unwanted_reply, NULL); + + return; +} diff --git a/src/providers/data_provider/dp_target_auth.c b/src/providers/data_provider/dp_target_auth.c new file mode 100644 index 0000000..5a205a3 --- /dev/null +++ b/src/providers/data_provider/dp_target_auth.c @@ -0,0 +1,345 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include + +#include "sbus/sbus_request.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/backend.h" +#include "util/sss_pam_data.h" +#include "util/util.h" + +static void choose_target(struct data_provider *provider, + struct pam_data *pd, + enum dp_targets *_target, + enum dp_methods *_method, + const char **_req_name) +{ + enum dp_targets target; + enum dp_methods method; + const char *name; + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + target = DPT_AUTH; + method = DPM_AUTH_HANDLER; + name = "PAM Authenticate"; + break; + case SSS_PAM_PREAUTH: + target = DPT_AUTH; + method = DPM_AUTH_HANDLER; + name = "PAM Preauth"; + break; + case SSS_PAM_ACCT_MGMT: + target = DPT_ACCESS; + method = DPM_ACCESS_HANDLER; + name = "PAM Account"; + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + target = DPT_CHPASS; + method = DPM_AUTH_HANDLER; + name = "PAM Chpass 1st"; + break; + case SSS_PAM_CHAUTHTOK: + target = DPT_CHPASS; + method = DPM_AUTH_HANDLER; + name = "PAM Chpass 2nd"; + break; + case SSS_PAM_OPEN_SESSION: + name = "PAM Open Session"; + if (dp_method_enabled(provider, DPT_SESSION, DPM_SESSION_HANDLER)) { + target = DPT_SESSION; + method = DPM_SESSION_HANDLER; + break; + } + + target = DP_TARGET_SENTINEL; + method = DP_METHOD_SENTINEL; + pd->pam_status = PAM_SUCCESS; + break; + case SSS_PAM_SETCRED: + target = DP_TARGET_SENTINEL; + method = DP_METHOD_SENTINEL; + name = "PAM Set Credentials"; + pd->pam_status = PAM_SUCCESS; + break; + case SSS_PAM_CLOSE_SESSION: + target = DP_TARGET_SENTINEL; + method = DP_METHOD_SENTINEL; + name = "PAM Close Session"; + pd->pam_status = PAM_SUCCESS; + break; + default: + DEBUG(SSSDBG_TRACE_LIBS, "Unsupported PAM command [%d].\n", + pd->cmd); + target = DP_TARGET_SENTINEL; + method = DP_METHOD_SENTINEL; + name = "PAM Unsupported"; + pd->pam_status = PAM_MODULE_UNKNOWN; + break; + } + + /* Check that target is configured. */ + if (target != DP_TARGET_SENTINEL + && !dp_target_enabled(provider, NULL, target)) { + target = DP_TARGET_SENTINEL; + method = DP_METHOD_SENTINEL; + pd->pam_status = PAM_MODULE_UNKNOWN; + } + + *_target = target; + *_method = method; + *_req_name = name; +} + +static bool should_invoke_selinux(struct data_provider *provider, + struct pam_data *pd) +{ + if (!dp_method_enabled(provider, DPT_SELINUX, DPM_SELINUX_HANDLER)) { + return false; + } + + if (pd->cmd == SSS_PAM_ACCT_MGMT && pd->pam_status == PAM_SUCCESS) { + return true; + } + + return false; +} + +struct dp_pam_handler_state { + struct data_provider *provider; + struct pam_data *pd; +}; + +static void dp_pam_handler_auth_done(struct tevent_req *subreq); +static void dp_pam_handler_done(struct tevent_req *subreq); + +struct tevent_req * +dp_pam_handler_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + struct pam_data *pd) +{ + struct dp_pam_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + enum dp_targets target; + enum dp_methods method; + const char *req_name; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_pam_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + pd->pam_status = PAM_SYSTEM_ERR; + if (pd->domain == NULL) { + pd->domain = talloc_strdup(pd, provider->be_ctx->domain->name); + if (pd->domain == NULL) { + ret = ENOMEM; + goto done; + } + } + + state->provider = provider; + state->pd = pd; + + DEBUG(SSSDBG_CONF_SETTINGS, "Got request with the following data\n"); + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + + choose_target(provider, pd, &target, &method, &req_name); + if (target == DP_TARGET_SENTINEL) { + ret = EOK; + goto done; + } + + subreq = dp_req_send(state, provider, pd->domain, req_name, + pd->client_id_num, sbus_req->sender->name, + target, method, 0, pd, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_pam_handler_auth_done, req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_pam_handler_auth_done(struct tevent_req *subreq) +{ + struct dp_pam_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_pam_handler_state); + + ret = dp_req_recv(state, subreq, struct pam_data *, &state->pd); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (!should_invoke_selinux(state->provider, state->pd)) { + tevent_req_done(req); + return; + } + + subreq = dp_req_send(state, state->provider, state->pd->domain, + "PAM SELinux", state->pd->client_id_num, + "sssd.pam", DPT_SELINUX, + DPM_SELINUX_HANDLER, 0, state->pd, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, dp_pam_handler_done, req); +} + +static void dp_pam_handler_done(struct tevent_req *subreq) +{ + struct dp_pam_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_pam_handler_state); + + ret = dp_req_recv(state, subreq, struct pam_data *, &state->pd); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +dp_pam_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_pd) +{ + struct dp_pam_handler_state *state; + state = tevent_req_data(req, struct dp_pam_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_pd = talloc_steal(mem_ctx, state->pd); + + return EOK; +} + +struct dp_access_control_refresh_rules_state { + void *reply; +}; + +static void dp_access_control_refresh_rules_done(struct tevent_req *subreq); + +struct tevent_req * +dp_access_control_refresh_rules_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider) +{ + struct dp_access_control_refresh_rules_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_access_control_refresh_rules_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + subreq = dp_req_send(state, provider, NULL, "Refresh Access Control Rules", + 0, sbus_req->sender->name, DPT_ACCESS, DPM_REFRESH_ACCESS_RULES, + 0, NULL, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_access_control_refresh_rules_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_access_control_refresh_rules_done(struct tevent_req *subreq) +{ + struct dp_access_control_refresh_rules_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_access_control_refresh_rules_state); + + ret = dp_req_recv(state, subreq, void *, &state->reply); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +dp_access_control_refresh_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/data_provider/dp_target_autofs.c b/src/providers/data_provider/dp_target_autofs.c new file mode 100644 index 0000000..eff0bd2 --- /dev/null +++ b/src/providers/data_provider/dp_target_autofs.c @@ -0,0 +1,275 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "sbus/sbus_request.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/backend.h" +#include "util/util.h" + +struct dp_autofs_get_map_state { + struct dp_autofs_data *data; +}; + +static void dp_autofs_get_map_done(struct tevent_req *subreq); + +struct tevent_req * +dp_autofs_get_map_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + const char *mapname, + uint32_t cli_id) +{ + struct dp_autofs_get_map_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_autofs_get_map_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->data = talloc_zero(state, struct dp_autofs_data); + if (state->data == NULL) { + ret = ENOMEM; + goto done; + } + + state->data->mapname = mapname; + + subreq = dp_req_send(state, provider, NULL, "AutoFS", cli_id, + sbus_req->sender->name, DPT_AUTOFS, DPM_AUTOFS_GET_MAP, + dp_flags, state->data, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_autofs_get_map_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_autofs_get_map_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = dp_req_recv_no_output(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t dp_autofs_get_map_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct dp_autofs_get_entry_state { + struct dp_autofs_data *data; +}; + +static void dp_autofs_get_entry_done(struct tevent_req *subreq); + +struct tevent_req * +dp_autofs_get_entry_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + const char *mapname, + const char *entryname, + uint32_t cli_id) +{ + struct dp_autofs_get_entry_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_autofs_get_entry_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->data = talloc_zero(state, struct dp_autofs_data); + if (state->data == NULL) { + ret = ENOMEM; + goto done; + } + + state->data->mapname = mapname; + state->data->entryname = entryname; + + subreq = dp_req_send(state, provider, NULL, "AutoFS", cli_id, + sbus_req->sender->name, DPT_AUTOFS, + DPM_AUTOFS_GET_ENTRY, dp_flags, state->data, + NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_autofs_get_entry_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_autofs_get_entry_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = dp_req_recv_no_output(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t dp_autofs_get_entry_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct dp_autofs_enumerate_state { + struct dp_autofs_data *data; +}; + +static void dp_autofs_enumerate_done(struct tevent_req *subreq); + +struct tevent_req * +dp_autofs_enumerate_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + const char *mapname, + uint32_t cli_id) +{ + struct dp_autofs_enumerate_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_autofs_enumerate_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->data = talloc_zero(state, struct dp_autofs_data); + if (state->data == NULL) { + ret = ENOMEM; + goto done; + } + + state->data->mapname = mapname; + + subreq = dp_req_send(state, provider, NULL, "AutoFS", cli_id, + sbus_req->sender->name, DPT_AUTOFS, + DPM_AUTOFS_ENUMERATE, dp_flags, state->data, + NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_autofs_enumerate_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_autofs_enumerate_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = dp_req_recv_no_output(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t dp_autofs_enumerate_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/data_provider/dp_target_hostid.c b/src/providers/data_provider/dp_target_hostid.c new file mode 100644 index 0000000..ea8f290 --- /dev/null +++ b/src/providers/data_provider/dp_target_hostid.c @@ -0,0 +1,126 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "sbus/sbus_request.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/backend.h" +#include "util/util.h" + +struct dp_host_handler_state { + struct dp_hostid_data *data; + struct dp_reply_std reply; + const char *request_name; +}; + +static void dp_host_handler_done(struct tevent_req *subreq); + +struct tevent_req * +dp_host_handler_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + const char *name, + const char *alias, + uint32_t cli_id) +{ + struct dp_host_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_host_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->data = talloc_zero(state, struct dp_hostid_data); + if (state->data == NULL) { + ret = ENOMEM; + goto done; + } + + state->data->name = name; + state->data->alias = SBUS_REQ_STRING(alias); + + subreq = dp_req_send(state, provider, NULL, "HostID", cli_id, + sbus_req->sender->name, DPT_HOSTID, DPM_HOSTID_HANDLER, + dp_flags, state->data, &state->request_name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_host_handler_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_host_handler_done(struct tevent_req *subreq) +{ + struct dp_host_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_host_handler_state); + + ret = dp_req_recv(state, subreq, struct dp_reply_std, &state->reply); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +dp_host_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg) +{ + struct dp_host_handler_state *state; + state = tevent_req_data(req, struct dp_host_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + dp_req_reply_std(state->request_name, &state->reply, + _dp_error, _error, _err_msg); + + return EOK; +} diff --git a/src/providers/data_provider/dp_target_id.c b/src/providers/data_provider/dp_target_id.c new file mode 100644 index 0000000..83641c3 --- /dev/null +++ b/src/providers/data_provider/dp_target_id.c @@ -0,0 +1,1084 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "sbus/sbus_request.h" +#include "sss_iface/sss_iface_async.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/backend.h" +#include "util/util.h" + +#define FILTER_TYPE(str, type) {str "=", sizeof(str "=") - 1, type} + +static bool check_and_parse_filter(struct dp_id_data *data, + const char *filter, + const char *extra) +{ + /* We will use sizeof() to determine the length of a string so we don't + * call strlen over and over again with each request. Not a bottleneck, + * but unnecessary and simple to avoid. */ + static struct { + const char *name; + size_t lenght; + uint32_t type; + } types[] = {FILTER_TYPE("name", BE_FILTER_NAME), + FILTER_TYPE("idnumber", BE_FILTER_IDNUM), + FILTER_TYPE(DP_SEC_ID, BE_FILTER_SECID), + FILTER_TYPE(DP_CERT, BE_FILTER_CERT), + FILTER_TYPE(DP_WILDCARD, BE_FILTER_WILDCARD), + {0, 0, 0}}; + int i; + + if (SBUS_REQ_STRING_IS_EMPTY(filter)) { + return false; + } + + for (i = 0; types[i].name != NULL; i++) { + if (strncmp(filter, types[i].name, types[i].lenght) == 0) { + data->filter_type = types[i].type; + data->filter_value = SBUS_REQ_STRING(&filter[types[i].lenght]); + data->extra_value = SBUS_REQ_STRING(extra); + return true; + } + } + + if (strcmp(filter, ENUM_INDICATOR) == 0) { + data->filter_type = BE_FILTER_ENUM; + data->filter_value = NULL; + data->extra_value = NULL; + return true; + } + + return false; +} + +struct dp_initgr_ctx { + const char *domain; + struct sss_domain_info *domain_info; + const char *filter_value; + const char *username; + uint32_t gnum; + uint32_t *groups; +}; + +static errno_t +dp_create_initgroups_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct dp_id_data *data, + struct dp_initgr_ctx **_ctx) +{ + struct sss_domain_info *domain; + struct dp_initgr_ctx *ctx; + struct ldb_result *res; + const char *username; + unsigned int i; + errno_t ret; + + if (data->domain == NULL) { + domain = be_ctx->domain; + } else { + domain = find_domain_by_name(be_ctx->domain, data->domain, true); + if (domain == NULL) { + return ERR_DOMAIN_NOT_FOUND; + } + } + + ctx = talloc_zero(mem_ctx, struct dp_initgr_ctx); + if (ctx == NULL) { + return ENOMEM; + } + + ctx->domain = data->domain; + ctx->filter_value = data->filter_value; + ctx->domain_info = domain; + + ret = sysdb_initgroups(ctx, domain, data->filter_value, &res); + if (ret == ENOENT || (ret == EOK && res->count == 0)) { + *_ctx = ctx; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get initgroups [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Copy original username */ + username = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, NULL); + if (username == NULL) { + ret = EINVAL; + goto done; + } + + ctx->username = talloc_strdup(ctx, username); + if (ctx->username == NULL) { + ret = ENOMEM; + goto done; + } + + /* Copy group IDs */ + ctx->groups = talloc_zero_array(mem_ctx, uint32_t, res->count + 1); + if (ctx->groups == NULL) { + ret = ENOMEM; + goto done; + } + + /* The first GID is the primary so it might be duplicated + * later in the list. */ + for (ctx->gnum = 0, i = 0; i < res->count; i++) { + ctx->groups[ctx->gnum] = ldb_msg_find_attr_as_uint(res->msgs[i], + SYSDB_GIDNUM, 0); + /* If 0 it may be a non-POSIX group, so we skip it. */ + if (ctx->groups[ctx->gnum] != 0) { + ctx->gnum++; + } + } + + *_ctx = ctx; + talloc_free(res); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(ctx); + } + + return ret; +} + +static void dp_req_initgr_pp_sr_overlay(struct data_provider *provider, + struct dp_initgr_ctx *ctx) +{ + bool enabled = false; + struct be_ctx *be = provider->be_ctx; + struct ldb_result *res; + struct ldb_message *msg; + const char *name; + char *output_name; + char **conf_user; + char **conf_group; + char **conf_exclude_user; + char **conf_exclude_group; + size_t i; + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + struct ldb_message_element el = { 0, SYSDB_SESSION_RECORDING, 0, NULL }; + struct sysdb_attrs del_attrs = { 1, &el }; + struct sysdb_attrs *add_attrs; + + /* Only proceed if scope is applicable: 'some' or 'all' */ + if (be->sr_conf.scope == SESSION_RECORDING_SCOPE_NONE) { + goto done; + } + + /* Default to enabled when scope is 'all' */ + enabled = be->sr_conf.scope == SESSION_RECORDING_SCOPE_ALL ? true : false; + + /* Allocate temporary talloc context */ + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed creating temporary talloc context\n"); + goto done; + } + + /* Get updated initgroups data with overrides */ + ret = sysdb_initgroups_with_views(tmp_ctx, ctx->domain_info, + ctx->filter_value, &res); + if (ret == ENOENT || (ret == EOK && res->count == 0)) { + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get initgroups: %s\n", + sss_strerror(ret)); + goto done; + } + + /* Delete sessionRecording attribute so we know when we failed */ + ret = sysdb_set_entry_attr(ctx->domain_info->sysdb, res->msgs[0]->dn, + &del_attrs, SYSDB_MOD_DEL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed removing %s attribute: %s\n", + SYSDB_SESSION_RECORDING, sss_strerror(ret)); + goto done; + } + + /* Format output username */ + name = sss_get_name_from_msg(ctx->domain_info, res->msgs[0]); + ret = sss_output_fqname(tmp_ctx, ctx->domain_info, name, + be->override_space, &output_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed formatting output username from \"%s\": %s\n", + name, sss_strerror(ret)); + goto done; + } + + /* For each user name in session recording config */ + conf_user = be->sr_conf.users; + if (conf_user != NULL) { + for (; *conf_user != NULL && !enabled; conf_user++) { + /* If it matches the requested user name */ + if (strcmp(*conf_user, output_name) == 0) { + enabled = true; + } + } + } + + /* For each exclude user name in session recording config */ + conf_exclude_user = be->sr_conf.exclude_users; + if (conf_exclude_user != NULL && + be->sr_conf.scope == SESSION_RECORDING_SCOPE_ALL) { + for (; *conf_exclude_user != NULL && enabled; conf_exclude_user++) { + if (strcmp(*conf_exclude_user, output_name) == 0) { + enabled = false; + } + } + } + + /* If we have groups in config and are not yet enabled */ + if ((be->sr_conf.scope == SESSION_RECORDING_SCOPE_SOME && + be->sr_conf.groups != NULL && + be->sr_conf.groups[0] != NULL && + !enabled) || + /* Or if we have exclude_groups in config and are enabled */ + (be->sr_conf.scope == SESSION_RECORDING_SCOPE_ALL && + be->sr_conf.exclude_groups != NULL && + be->sr_conf.exclude_groups[0] != NULL && + enabled)) { + /* For each group in response */ + for (i = 0; i < res->count; i++) { + /* Get the group msg */ + if (i == 0) { + gid_t gid; + struct ldb_result *group_res; + + /* Get the primary group */ + gid = sss_view_ldb_msg_find_attr_as_uint64(ctx->domain_info, + res->msgs[i], + SYSDB_GIDNUM, 0); + if (gid == 0) { + continue; + } + ret = sysdb_getgrgid_with_views(tmp_ctx, ctx->domain_info, + gid, &group_res); + if (ret == ENOENT) { + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed retrieving group #%llu: %s\n", + (unsigned long long)gid, sss_strerror(ret)); + goto done; + } else if (group_res->count == 0) { + continue; + } + msg = group_res->msgs[0]; + } else { + msg = res->msgs[i]; + } + /* Get the group's output name */ + name = sss_get_name_from_msg(ctx->domain_info, msg); + if (name == NULL) { + continue; + } + ret = sss_output_fqname(tmp_ctx, ctx->domain_info, + name, be->override_space, + &output_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed formatting output group name from \"%s\": %s\n", + name, sss_strerror(ret)); + goto done; + } + /* For each group in configuration */ + if (be->sr_conf.scope == SESSION_RECORDING_SCOPE_SOME) { + for (conf_group = be->sr_conf.groups; + *conf_group != NULL && !enabled; + conf_group++) { + if (strcmp(*conf_group, output_name) == 0) { + enabled = true; + } + } + /* For each exclude group in configuration */ + } else if (be->sr_conf.scope == SESSION_RECORDING_SCOPE_ALL) { + for (conf_exclude_group = be->sr_conf.exclude_groups; + *conf_exclude_group != NULL && enabled; + conf_exclude_group++) { + if (strcmp(*conf_exclude_group, output_name) == 0) { + enabled = false; + } + } + } + + /* Found a matched group */ + if ((be->sr_conf.scope == SESSION_RECORDING_SCOPE_SOME + && enabled) || + (be->sr_conf.scope == SESSION_RECORDING_SCOPE_ALL + && !enabled)) { + break; + } + } + } + + /* Set sessionRecording attribute to enabled value */ + add_attrs = sysdb_new_attrs(tmp_ctx); + if (add_attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed creating attributes\n"); + goto done; + } + ret = sysdb_attrs_add_bool(add_attrs, SYSDB_SESSION_RECORDING, enabled); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed setting %s attribute: %s\n", + SYSDB_SESSION_RECORDING, sss_strerror(ret)); + goto done; + } + ret = sysdb_set_entry_attr(ctx->domain_info->sysdb, res->msgs[0]->dn, + add_attrs, SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed storing %s attribute: %s\n", + SYSDB_SESSION_RECORDING, sss_strerror(ret)); + goto done; + } + +done: + talloc_free(tmp_ctx); +} + +errno_t dp_add_sr_attribute(struct be_ctx *be_ctx) +{ + int ret; + struct dp_initgr_ctx *dp_initgr_ctx = NULL; + TALLOC_CTX *tmp_ctx = NULL; + struct dp_id_data *data; + size_t msgs_count; + struct ldb_message **msgs = NULL; + const char *attrs[] = {SYSDB_NAME, NULL}; + size_t c; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = sysdb_search_users(tmp_ctx, be_ctx->domain, "("SYSDB_NAME "=*)", attrs, + &msgs_count, &msgs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_users failed.\n"); + goto done; + } + + data = talloc_zero(tmp_ctx, struct dp_id_data); + if (data == NULL) { + ret = ENOMEM; + goto done; + } + + data->entry_type = BE_REQ_INITGROUPS; + data->filter_type = BE_FILTER_NAME; + data->filter_value = NULL; + data->extra_value = NULL; + data->domain = be_ctx->domain->name; + + for (c = 0; c < msgs_count; c++) { + data->filter_value = ldb_msg_find_attr_as_string(msgs[c], SYSDB_NAME, + NULL); + if (data->filter_value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cache object [%s] does not have a name, skipping.\n", + ldb_dn_get_linearized(msgs[c]->dn)); + continue; + } + + talloc_free(dp_initgr_ctx); + ret = dp_create_initgroups_ctx(tmp_ctx, be_ctx, data, &dp_initgr_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "dp_create_initgroups_ctx failed.\n"); + goto done; + } + + dp_req_initgr_pp_sr_overlay(be_ctx->provider, dp_initgr_ctx); + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static void dp_req_initgr_pp_set_initgr_timestamp(struct dp_initgr_ctx *ctx, + struct dp_reply_std *reply) +{ + errno_t ret; + + if (reply->dp_error != DP_ERR_OK || reply->error != EOK) { + /* Only bump the timestamp on successful lookups */ + return; + } + + ret = sysdb_set_initgr_expire_timestamp(ctx->domain_info, + ctx->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to set initgroups expiration for [%s]\n", + ctx->filter_value); + } +} + + +struct dp_sr_resolve_groups_state { + struct data_provider *provider; + struct dp_initgr_ctx *initgroups_ctx; + struct dp_reply_std reply; + + uint32_t *resolve_gids; /* Groups needing resolution */ + int resolve_gnum; + int num_iter; + uint32_t gnum; +}; + +static errno_t dp_sr_resolve_groups_check(struct dp_sr_resolve_groups_state *state); +static errno_t dp_sr_resolve_groups_next(struct tevent_req *req); +static void dp_sr_resolve_groups_done(struct tevent_req *subreq); + +struct tevent_req * +dp_sr_resolve_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dp_reply_std reply, + struct data_provider *provider, + struct dp_initgr_ctx *initgr_ctx) +{ + + struct dp_sr_resolve_groups_state *state; + struct tevent_req *req; + int ret; + struct session_recording_conf sr_conf; + + req = tevent_req_create(mem_ctx, &state, struct dp_sr_resolve_groups_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + if (initgr_ctx->username == NULL) { + ret = EOK; + goto done; + } + + sr_conf = provider->be_ctx->sr_conf; + + /* Only proceed if scope is applicable: 'some' or 'all' with groups to resolve */ + if ((sr_conf.scope == SESSION_RECORDING_SCOPE_SOME && sr_conf.groups != NULL) + || (sr_conf.scope == SESSION_RECORDING_SCOPE_ALL && sr_conf.exclude_groups != NULL)) { + state->provider = provider; + state->initgroups_ctx = initgr_ctx; + state->reply = reply; + state->gnum = initgr_ctx->gnum; + + /* Check if group is intermediate(has gidNumber and isPosix == False) */ + state->resolve_gids = talloc_zero_array(state, uint32_t, initgr_ctx->gnum + 1); + if (state->resolve_gids == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dp_sr_resolve_groups_check(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed checking groups to resolve\n"); + goto done; + } + + state->num_iter = 0; + ret = dp_sr_resolve_groups_next(req); + if (ret == EAGAIN) { + /* async processing */ + return req; + } + } else { + ret = EOK; + goto done; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t dp_sr_resolve_groups_next(struct tevent_req *req) +{ + struct dp_sr_resolve_groups_state *state; + struct tevent_req *subreq; + struct dp_id_data *ar; + uint32_t gid; + + state = tevent_req_data(req, struct dp_sr_resolve_groups_state); + + if (state->num_iter >= state->resolve_gnum) { + return EOK; + } + + gid = state->resolve_gids[state->num_iter]; + + ar = talloc_zero(state, struct dp_id_data); + if (ar == NULL) { + return ENOMEM; + } + + ar->entry_type = BE_REQ_GROUP; + ar->filter_type = BE_FILTER_IDNUM; + ar->filter_value = talloc_asprintf(ar, "%llu", (unsigned long long) gid); + ar->domain = talloc_strdup(ar, state->initgroups_ctx->domain_info->name); + if (!ar->domain || !ar->filter_value) { + return ENOMEM; + } + + subreq = dp_req_send(state, state->provider, ar->domain, + "DP Resolve Group", 0, NULL, + DPT_ID, DPM_ACCOUNT_HANDLER, 0, ar, NULL); + if (!subreq) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, dp_sr_resolve_groups_done, req); + + state->num_iter++; + return EAGAIN; +} + +static void dp_sr_resolve_groups_done(struct tevent_req *subreq) +{ + struct dp_sr_resolve_groups_state *state; + struct tevent_req *req; + struct dp_reply_std *reply; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_sr_resolve_groups_state); + + ret = dp_req_recv_ptr(state, subreq, struct dp_reply_std, &reply); + talloc_free(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Try next group */ + ret = dp_sr_resolve_groups_next(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +errno_t dp_sr_resolve_groups_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req) +{ + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t +dp_sr_resolve_groups_check(struct dp_sr_resolve_groups_state *state) +{ + errno_t ret; + struct ldb_message *group; + struct ldb_result *res; + struct sss_domain_info *domain_info; + const char *group_attrs[] = { SYSDB_NAME, SYSDB_POSIX, + SYSDB_GIDNUM, NULL }; + uint32_t gid; + const char *name; + const char *val; + + domain_info = state->initgroups_ctx->domain_info; + + ret = sysdb_initgroups(state, domain_info, state->initgroups_ctx->username, &res); + if (ret == ENOENT || (ret == EOK && res->count == 0)) { + return EOK; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get initgroups [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + /* Get GID */ + for (int i = 0; i < res->count; i++) { + gid = sss_view_ldb_msg_find_attr_as_uint64(domain_info, + res->msgs[i], + SYSDB_GIDNUM, 0); + if (gid == 0) { + continue; + } + DEBUG(SSSDBG_TRACE_ALL, "Checking if group needs to be resolved: [%d]\n", + gid); + + /* Check the cache by GID again and fetch the name */ + ret = sysdb_search_group_by_gid(state, state->initgroups_ctx->domain_info, gid, + group_attrs, &group); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not look up group by gid [%"SPRIgid"]: [%d][%s]\n", + gid, ret, sss_strerror(ret)); + continue; + } + + name = ldb_msg_find_attr_as_string(group, SYSDB_NAME, NULL); + if (!name) { + DEBUG(SSSDBG_OP_FAILURE, "No group name\n"); + continue; + } + + val = ldb_msg_find_attr_as_string(group, SYSDB_POSIX, NULL); + + /* Group needs to be resolved */ + if ((strcasecmp(val, "FALSE") == 0) && gid > 0) { + state->resolve_gids[state->resolve_gnum] = gid; + state->resolve_gnum++; + } + } + + return EOK; +} + + +struct dp_get_account_info_state { + const char *request_name; + bool initgroups; + + struct tevent_context *ev; + struct data_provider *provider; + struct dp_id_data *data; + struct dp_reply_std reply; + struct dp_initgr_ctx *initgr_ctx; +}; + +static void dp_get_account_info_request_done(struct tevent_req *subreq); +static errno_t dp_get_account_info_initgroups_step(struct tevent_req *req); +static void dp_get_account_info_initgroups_resolv_done(struct tevent_req *subreq); +static void dp_get_account_info_done(struct tevent_req *subreq); + +struct tevent_req * +dp_get_account_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + uint32_t entry_type, + const char *filter, + const char *domain, + const char *extra, + uint32_t cli_id) +{ + struct dp_get_account_info_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_get_account_info_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->data = talloc_zero(state, struct dp_id_data); + if (state->data == NULL) { + ret = ENOMEM; + goto done; + } + + state->ev = ev; + state->provider = provider; + state->request_name = "Account"; + state->initgroups = false; + state->data->entry_type = entry_type; + state->data->domain = domain; + + if (!check_and_parse_filter(state->data, filter, extra)) { + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Got request for [%#"PRIx32"][%s][%s]\n", + state->data->entry_type, be_req2str(state->data->entry_type), + filter); + + if ((state->data->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_INITGROUPS) { + state->request_name = "Initgroups"; + state->initgroups = true; + + ret = dp_create_initgroups_ctx(state, provider->be_ctx, state->data, + &state->initgr_ctx); + if (ret != EOK) { + goto done; + } + } + + subreq = dp_req_send(state, provider, domain, state->request_name, + cli_id, sbus_req->sender->name, DPT_ID, + DPM_ACCOUNT_HANDLER, dp_flags, state->data, + &state->request_name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_get_account_info_request_done, req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_get_account_info_request_done(struct tevent_req *subreq) +{ + struct dp_get_account_info_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_get_account_info_state); + + ret = dp_req_recv(state, subreq, struct dp_reply_std, &state->reply); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = dp_get_account_info_initgroups_step(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static errno_t dp_get_account_info_initgroups_step(struct tevent_req *req) +{ + struct dp_get_account_info_state *state; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct dp_get_account_info_state); + + if (state->initgroups == false) { + return EOK; + } + + /* Create subrequest to handle SR data */ + subreq = dp_sr_resolve_groups_send(state, state->ev, state->reply, + state->provider, state->initgr_ctx); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, dp_get_account_info_initgroups_resolv_done, req); + + return EAGAIN; +} + + +static void dp_get_account_info_initgroups_resolv_done(struct tevent_req *subreq) +{ + struct dp_get_account_info_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_get_account_info_state); + + ret = dp_sr_resolve_groups_recv(state, subreq); + talloc_free(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + dp_req_initgr_pp_set_initgr_timestamp(state->initgr_ctx, &state->reply); + dp_req_initgr_pp_sr_overlay(state->provider, state->initgr_ctx); + + if (state->initgr_ctx->username != NULL) { + /* There is no point in contacting NSS responder if user did + * not exist before this request. */ + DEBUG(SSSDBG_TRACE_FUNC, + "Ordering NSS responder to update memory cache\n"); + + subreq = sbus_call_nss_memcache_UpdateInitgroups_send(state, + state->provider->sbus_conn, SSS_BUS_NSS, SSS_BUS_PATH, + state->initgr_ctx->username, state->initgr_ctx->domain, + state->initgr_ctx->groups); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + tevent_req_error(req, ret); + return; + } + + tevent_req_set_callback(subreq, dp_get_account_info_done, req); + } else { + tevent_req_done(req); + } +} + +static void dp_get_account_info_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sbus_call_nss_memcache_UpdateInitgroups_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Error sending sbus message [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +dp_get_account_info_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg) +{ + struct dp_get_account_info_state *state; + state = tevent_req_data(req, struct dp_get_account_info_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + dp_req_reply_std(state->request_name, &state->reply, + _dp_error, _error, _err_msg); + + return EOK; +} + +static bool +check_and_parse_acct_domain_filter(struct dp_get_acct_domain_data *data, + const char *filter) +{ + /* We will use sizeof() to determine the length of a string so we don't + * call strlen over and over again with each request. Not a bottleneck, + * but unnecessary and simple to avoid. */ + static struct { + const char *name; + size_t lenght; + uint32_t type; + } types[] = {FILTER_TYPE("idnumber", BE_FILTER_IDNUM), + FILTER_TYPE(DP_SEC_ID, BE_FILTER_SECID), + {0, 0, 0}}; + int i; + + if (SBUS_REQ_STRING_IS_EMPTY(filter)) { + return false; + } + + for (i = 0; types[i].name != NULL; i++) { + if (strncmp(filter, types[i].name, types[i].lenght) == 0) { + data->filter_type = types[i].type; + data->filter_value = SBUS_REQ_STRING(&filter[types[i].lenght]); + return true; + } + } + + if (strcmp(filter, ENUM_INDICATOR) == 0) { + data->filter_type = BE_FILTER_ENUM; + data->filter_value = NULL; + return true; + } + + return false; +} + +struct dp_get_account_domain_state { + struct dp_get_acct_domain_data *data; + struct dp_reply_std reply; + const char *request_name; +}; + +static void dp_get_account_domain_done(struct tevent_req *subreq); + +struct tevent_req * +dp_get_account_domain_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + uint32_t entry_type, + const char *filter, + uint32_t cli_id) +{ + struct dp_get_account_domain_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_get_account_domain_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->data = talloc_zero(state, struct dp_get_acct_domain_data); + if (state->data == NULL) { + ret = ENOMEM; + goto done; + } + state->data->entry_type = entry_type; + + if (!check_and_parse_acct_domain_filter(state->data, filter)) { + ret = EINVAL; + goto done; + } + + subreq = dp_req_send(state, provider, NULL, "AccountDomain", cli_id, + sbus_req->sender->name, DPT_ID, DPM_ACCT_DOMAIN_HANDLER, + dp_flags, state->data, &state->request_name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_get_account_domain_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_get_account_domain_done(struct tevent_req *subreq) +{ + struct dp_get_account_domain_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_get_account_domain_state); + + ret = dp_req_recv(state, subreq, struct dp_reply_std, &state->reply); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +dp_get_account_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg) +{ + struct dp_get_account_domain_state *state; + state = tevent_req_data(req, struct dp_get_account_domain_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + dp_req_reply_std(state->request_name, &state->reply, + _dp_error, _error, _err_msg); + + return EOK; +} + +struct default_account_domain_state { + struct dp_reply_std reply; +}; + +struct tevent_req * +default_account_domain_send(TALLOC_CTX *mem_ctx, + void *unused_ctx, + struct dp_get_acct_domain_data *data, + struct dp_req_params *params) +{ + struct default_account_domain_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct default_account_domain_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + dp_reply_std_set(&state->reply, + DP_ERR_DECIDE, ERR_GET_ACCT_DOM_NOT_SUPPORTED, + NULL); + tevent_req_done(req); + tevent_req_post(req, params->ev); + return req; +} + +errno_t default_account_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct default_account_domain_state *state = NULL; + + state = tevent_req_data(req, struct default_account_domain_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/data_provider/dp_target_resolver.c b/src/providers/data_provider/dp_target_resolver.c new file mode 100644 index 0000000..7dd24d3 --- /dev/null +++ b/src/providers/data_provider/dp_target_resolver.c @@ -0,0 +1,148 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "sbus/sbus_request.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/backend.h" +#include "util/util.h" + +struct dp_resolver_handler_state { + struct dp_resolver_data *data; + struct dp_reply_std reply; + const char *request_name; +}; + +static void dp_resolver_handler_done(struct tevent_req *subreq); + +struct tevent_req * +dp_resolver_handler_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + uint32_t dp_flags, + uint32_t entry_type, + uint32_t filter_type, + const char *filter_value, + uint32_t cli_id) +{ + struct dp_resolver_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Received request, flags [%d], " + "entry type [%#x:%s], filter [%#x:%s]\n", dp_flags, + entry_type, be_req2str(entry_type), filter_type, filter_value); + + req = tevent_req_create(mem_ctx, &state, struct dp_resolver_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->data = talloc_zero(state, struct dp_resolver_data); + if (state->data == NULL) { + ret = ENOMEM; + goto done; + } + + state->data->filter_type = filter_type; + state->data->filter_value = filter_value; + + switch (entry_type) { + case BE_REQ_HOST: + subreq = dp_req_send(state, provider, NULL, "Resolver", cli_id, + sbus_req->sender->name, DPT_RESOLVER, + DPM_RESOLVER_HOSTS_HANDLER, dp_flags, + state->data, &state->request_name); + break; + case BE_REQ_IP_NETWORK: + subreq = dp_req_send(state, provider, NULL, "Resolver", cli_id, + sbus_req->sender->name, DPT_RESOLVER, + DPM_RESOLVER_IP_NETWORK_HANDLER, dp_flags, + state->data, &state->request_name); + break; + default: + ret = EINVAL; + goto done; + } + + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_resolver_handler_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_resolver_handler_done(struct tevent_req *subreq) +{ + struct dp_resolver_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_resolver_handler_state); + + ret = dp_req_recv(state, subreq, struct dp_reply_std, &state->reply); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +dp_resolver_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg) +{ + struct dp_resolver_handler_state *state; + state = tevent_req_data(req, struct dp_resolver_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + dp_req_reply_std(state->request_name, &state->reply, + _dp_error, _error, _err_msg); + + return EOK; +} diff --git a/src/providers/data_provider/dp_target_subdomains.c b/src/providers/data_provider/dp_target_subdomains.c new file mode 100644 index 0000000..e405d8c --- /dev/null +++ b/src/providers/data_provider/dp_target_subdomains.c @@ -0,0 +1,122 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "sbus/sbus_request.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/backend.h" +#include "util/util.h" + +struct dp_subdomains_handler_state { + struct dp_subdomains_data *data; + struct dp_reply_std reply; + const char *request_name; +}; + +static void dp_subdomains_handler_done(struct tevent_req *subreq); + +struct tevent_req * +dp_subdomains_handler_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + const char *domain_hint) +{ + struct dp_subdomains_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_subdomains_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->data = talloc_zero(state, struct dp_subdomains_data); + if (state->data == NULL) { + ret = ENOMEM; + goto done; + } + + state->data->domain_hint = domain_hint; + + subreq = dp_req_send(state, provider, NULL, "Subdomains", 0, + sbus_req->sender->name, DPT_SUBDOMAINS, DPM_DOMAINS_HANDLER, + 0, state->data, &state->request_name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_subdomains_handler_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_subdomains_handler_done(struct tevent_req *subreq) +{ + struct dp_subdomains_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_subdomains_handler_state); + + ret = dp_req_recv(state, subreq, struct dp_reply_std, &state->reply); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +dp_subdomains_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg) +{ + struct dp_subdomains_handler_state *state; + state = tevent_req_data(req, struct dp_subdomains_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + dp_req_reply_std(state->request_name, &state->reply, + _dp_error, _error, _err_msg); + + return EOK; +} diff --git a/src/providers/data_provider/dp_target_sudo.c b/src/providers/data_provider/dp_target_sudo.c new file mode 100644 index 0000000..f90589f --- /dev/null +++ b/src/providers/data_provider/dp_target_sudo.c @@ -0,0 +1,205 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "sbus/sbus_request.h" +#include "sbus/interface/sbus_iterator_readers.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/backend.h" +#include "util/util.h" + +static errno_t dp_sudo_parse_message(TALLOC_CTX *mem_ctx, + DBusMessageIter *read_iter, + uint32_t *_dp_flags, + uint32_t *_sudo_type, + const char ***_rules) +{ + uint32_t dp_flags; + uint32_t sudo_type; + uint32_t num_rules; + const char **rules; + errno_t ret; + + ret = sbus_iterator_read_u(read_iter, &dp_flags); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse the message (flags)!\n"); + return ret; + } + + ret = sbus_iterator_read_u(read_iter, &sudo_type); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse the message (type)!\n"); + return ret; + } + + /* get additional arguments according to the request type */ + switch (sudo_type) { + case BE_REQ_SUDO_FULL: + /* no arguments required */ + rules = NULL; + break; + case BE_REQ_SUDO_RULES: + /* additional arguments: + * rules_num + * rules[rules_num] + */ + /* read rules_num */ + ret = sbus_iterator_read_u(read_iter, &num_rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse the message (num rules)!\n"); + return ret; + } + + ret = sbus_iterator_read_as(mem_ctx, read_iter, &rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse the message (rules)!\n"); + return ret; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request type %d\n", sudo_type); + return EINVAL; + } + + *_dp_flags = dp_flags; + *_sudo_type = sudo_type; + *_rules = rules; + + return EOK; +} + +static const char *dp_sudo_get_name(uint32_t type) +{ + switch (type) { + case BE_REQ_SUDO_FULL: + return "SUDO Full Refresh"; + case BE_REQ_SUDO_RULES: + return "SUDO Rules Refresh"; + } + + return NULL; +} + +struct dp_sudo_handler_state { + struct dp_sudo_data *data; + struct dp_reply_std reply; + const char *request_name; +}; + +static void dp_sudo_handler_done(struct tevent_req *subreq); + +struct tevent_req * +dp_sudo_handler_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct data_provider *provider, + DBusMessageIter *read_iter) +{ + struct dp_sudo_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + uint32_t dp_flags; + const char *name; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_sudo_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->data = talloc_zero(state, struct dp_sudo_data); + if (state->data == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dp_sudo_parse_message(state, read_iter, &dp_flags, + &state->data->type, &state->data->rules); + if (ret != EOK) { + goto done; + } + + name = dp_sudo_get_name(state->data->type); + + subreq = dp_req_send(state, provider, NULL, name, 0, sbus_req->sender->name, + DPT_SUDO, DPM_SUDO_HANDLER, dp_flags, state->data, + &state->request_name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, dp_sudo_handler_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void dp_sudo_handler_done(struct tevent_req *subreq) +{ + struct dp_sudo_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_sudo_handler_state); + + ret = dp_req_recv(state, subreq, struct dp_reply_std, &state->reply); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +dp_sudo_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_err_msg) +{ + struct dp_sudo_handler_state *state; + state = tevent_req_data(req, struct dp_sudo_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + dp_req_reply_std(state->request_name, &state->reply, + _dp_error, _error, _err_msg); + + return EOK; +} diff --git a/src/providers/data_provider/dp_targets.c b/src/providers/data_provider/dp_targets.c new file mode 100644 index 0000000..75cb47d --- /dev/null +++ b/src/providers/data_provider/dp_targets.c @@ -0,0 +1,485 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "config.h" +#include "providers/data_provider/dp.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_builtin.h" +#include "providers/backend.h" +#include "util/util.h" + +#define DP_TARGET_INIT_FN "sssm_%s_%s_init" + +#define DP_PROVIDER_OPT "%s_provider" +#define DP_ACCESS_PERMIT "permit" +#define DP_ACCESS_DENY "deny" +#define DP_NO_PROVIDER "none" + +bool _dp_target_enabled(struct data_provider *provider, + const char *module_name, + ...) +{ + struct dp_target *target; + enum dp_targets type; + va_list ap; + bool bret; + + if (provider == NULL || provider->targets == NULL) { + return false; + } + + bret = false; + va_start(ap, module_name); + while ((type = va_arg(ap, enum dp_targets)) != DP_TARGET_SENTINEL) { + target = provider->targets[type]; + if (target == NULL || target->module_name == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Uninitialized target %s\n", + dp_target_to_string(type)); + continue; + } + + if (module_name == NULL) { + bret = true; + goto done; + } + + if (strcmp(target->module_name, module_name) == 0) { + bret = true; + goto done; + } + } + +done: + va_end(ap); + return bret; +} + +struct dp_module *dp_target_module(struct data_provider *provider, + enum dp_targets target) +{ + if (provider == NULL || provider->targets == NULL) { + return NULL; + } + + if (target >= DP_TARGET_SENTINEL || provider->targets[target] == NULL) { + return NULL; + } + + return provider->targets[target]->module; +} + +void *dp_get_module_data(struct dp_module *dp_module) +{ + return dp_module == NULL ? NULL : dp_module->module_data; +} + +const char *dp_target_to_string(enum dp_targets target) +{ + switch (target) { + case DPT_ID: + return "id"; + case DPT_AUTH: + return "auth"; + case DPT_ACCESS: + return "access"; + case DPT_CHPASS: + return "chpass"; + case DPT_SUDO: + return "sudo"; + case DPT_AUTOFS: + return "autofs"; + case DPT_SELINUX: + return "selinux"; + case DPT_HOSTID: + return "hostid"; + case DPT_SUBDOMAINS: + return "subdomains"; + case DPT_SESSION: + return "session"; + case DPT_RESOLVER: + return "resolver"; + case DP_TARGET_SENTINEL: + return NULL; + } + + return NULL; +} + +bool dp_target_initialized(struct dp_target **targets, enum dp_targets type) +{ + if (targets == NULL || targets[type] == NULL) { + return false; + } + + return targets[type]->initialized; +} + +static const char *dp_target_module_name(struct dp_target **targets, + enum dp_targets type) +{ + if (targets[type] == NULL) { + return NULL; + } + + return targets[type]->module_name; +} + +static const char *dp_target_default_module(struct dp_target **targets, + enum dp_targets target) +{ + switch (target) { + case DPT_ID: + return NULL; + case DPT_ACCESS: + return "permit"; + case DPT_CHPASS: + return dp_target_module_name(targets, DPT_AUTH); + case DP_TARGET_SENTINEL: + return NULL; + default: + return dp_target_module_name(targets, DPT_ID); + } +} + +static errno_t dp_target_run_constructor(struct dp_target *target, + struct be_ctx *be_ctx) +{ + char *fn_name = NULL; + dp_target_init_fn fn; + char *error; + errno_t ret; + + fn_name = talloc_asprintf(target, DP_TARGET_INIT_FN, + target->module->name, target->name); + if (fn_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + return ENOMEM; + } + + dlerror(); /* clear any error */ + fn = (dp_target_init_fn)dlsym(target->module->libhandle, fn_name); + if (fn != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Executing target [%s] constructor\n", + target->name); + + ret = fn(target, be_ctx, target->module->module_data, target->methods); + if (ret != EOK) { + if (ret != ENOTSUP) { + DEBUG(SSSDBG_FATAL_FAILURE, "Target [%s] constructor failed " + "[%d]: %s\n", target->name, ret, sss_strerror(ret)); + } + goto done; + } + } else { + error = dlerror(); + if (error == NULL || !target->explicitly_configured) { + /* Not found. */ + ret = ELIBBAD; + goto done; + } else { + /* Error. */ + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load target [%s] " + "constructor: %s\n", target->name, error); + ret = ELIBBAD; + goto done; + } + } + + target->initialized = true; + ret = EOK; + +done: + talloc_free(fn_name); + return ret; +} + +static errno_t dp_target_special(struct be_ctx *be_ctx, + struct dp_target *target, + const char *module_name) +{ + if (strcasecmp(module_name, DP_NO_PROVIDER) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Target [%s] is explicitly disabled.\n", + target->name); + target->initialized = false; + target->module = NULL; + return EOK; + } + + if (target->target == DPT_ACCESS) { + if (strcmp(module_name, DP_ACCESS_PERMIT) == 0) { + dp_set_method(target->methods, DPM_ACCESS_HANDLER, + dp_access_permit_handler_send, dp_access_permit_handler_recv, NULL, + void, struct pam_data, struct pam_data *); + target->module = NULL; + target->initialized = true; + return EOK; + } + + if (strcmp(module_name, DP_ACCESS_DENY) == 0) { + dp_set_method(target->methods, DPM_ACCESS_HANDLER, + dp_access_deny_handler_send, dp_access_deny_handler_recv, NULL, + void, struct pam_data, struct pam_data *); + target->module = NULL; + target->initialized = true; + return EOK; + } + } + + return EAGAIN; +} + +static errno_t dp_target_init(struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_module **modules, + struct dp_target *target) +{ + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Initializing target [%s] with module [%s]\n", + target->name, target->module_name); + + /* We have already name, module name and target set. We just load + * the module and initialize it. */ + + target->methods = talloc_zero_array(target, struct dp_method, + DP_METHOD_SENTINEL + 1); + if (target->methods == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + /* Handle special cases that do not require opening a module. */ + ret = dp_target_special(be_ctx, target, target->module_name); + if (ret == EOK || ret != EAGAIN) { + goto done; + } + + /* Load module first. Memory context is modules, not target here. */ + target->module = dp_load_module(modules, be_ctx, provider, modules, + target->module_name); + if (target->module == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load module %s\n", + target->module_name); + ret = ELIBBAD; + goto done; + } + + /* Run constructor. */ + ret = dp_target_run_constructor(target, be_ctx); + if (!target->explicitly_configured && (ret == ELIBBAD || ret == ENOTSUP)) { + /* Target not found but it wasn't explicitly + * configured so we shall just continue. */ + DEBUG(SSSDBG_CONF_SETTINGS, "Target [%s] is not supported by " + "module [%s].\n", target->name, target->module_name); + + /* Target is not initialized in this case so we can free + * its resources. However this is not an error so we return EOK. */ + talloc_zfree(target->methods); + target->initialized = false; + + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(target->methods); + } + + return ret; +} + +static char *dp_get_module_name(TALLOC_CTX *mem_ctx, + struct confdb_ctx *confdb_ctx, + const char *conf_path, + struct dp_target **targets, + enum dp_targets type, + bool *_is_default) +{ + const char *name; + const char *default_module; + char *module; + char *option; + errno_t ret; + + name = dp_target_to_string(type); + if (name == NULL) { + return NULL; + } + + option = talloc_asprintf(mem_ctx, DP_PROVIDER_OPT, name); + if (option == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + return NULL; + } + + ret = confdb_get_string(confdb_ctx, mem_ctx, conf_path, + option, NULL, &module); + talloc_free(option); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read provider value " + "[%d]: %s\n", ret, sss_strerror(ret)); + return NULL; + } + + if (module != NULL) { + *_is_default = false; + return module; + } + + *_is_default = true; + default_module = dp_target_default_module(targets, type); + + return talloc_strdup(mem_ctx, default_module); +} + +static errno_t dp_load_configuration(struct confdb_ctx *cdb, + const char *conf_path, + struct dp_target **targets) +{ + enum dp_targets type; + const char *name; + bool is_default; + char *module; + errno_t ret; + + for (type = 0; type < DP_TARGET_SENTINEL; type++) { + name = dp_target_to_string(type); + if (name == NULL) { + ret = ERR_INTERNAL; + goto done; + } + + module = dp_get_module_name(NULL, cdb, conf_path, targets, + type, &is_default); + if (module == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No provider is specified for" + " [%s]\n", name); + continue; +#ifndef BUILD_FILES_PROVIDER + } else if (strcasecmp(module, "files") == 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "'files' provider is configured for '%s'," + " but support wasn't built\n", name); + sss_log(SSS_LOG_CRIT, + "Unsupported provider 'files' is used in SSSD config."); + ret = ERR_INVALID_CONFIG; + goto done; +#endif + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "Using [%s] provider for [%s]\n", + module, name); + } + + targets[type]->explicitly_configured = is_default == false; + targets[type]->name = name; + targets[type]->target = type; + targets[type]->module_name = talloc_steal(targets[type], module); + } + + ret = EOK; + +done: + return ret; +} + +static errno_t dp_load_targets(struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_target **targets, + struct dp_module **modules) +{ + enum dp_targets type; + errno_t ret; + + /* We load the configuration first and store module name to each target. + * This way we ensure that we have this information available during + * module initialization. */ + + ret = dp_load_configuration(be_ctx->cdb, be_ctx->conf_path, targets); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to load DP configuration " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + for (type = 0; type < DP_TARGET_SENTINEL; type++) { + ret = dp_target_init(be_ctx, provider, modules, targets[type]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to load target [%s] " + "[%d]: %s.\n", targets[type]->name, ret, sss_strerror(ret)); + ret = ERR_INTERNAL; + goto done; + } + } + + ret = EOK; + +done: + return ret; +} + +errno_t dp_init_targets(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_module **modules) +{ + struct dp_target **targets; + enum dp_targets type; + errno_t ret; + + /* Even though we know the exact number of targets we will allocate + * them all dynamically so we can have correct talloc hierarchy where + * all private data are attached to the target they belong to. */ + + targets = talloc_zero_array(mem_ctx, struct dp_target *, + DP_TARGET_SENTINEL + 1); + if (targets == NULL) { + ret = ENOMEM; + goto done; + } + + for (type = 0; type != DP_TARGET_SENTINEL; type++) { + targets[type] = talloc_zero(targets, struct dp_target); + if (targets[type] == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* We want this to be already available. */ + provider->targets = targets; + + ret = dp_load_targets(be_ctx, provider, targets, modules); + +done: + if (ret != EOK) { + provider->targets = NULL; + talloc_free(targets); + } + + return ret; +} diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c new file mode 100644 index 0000000..9e961fa --- /dev/null +++ b/src/providers/data_provider_be.c @@ -0,0 +1,894 @@ +/* + SSSD + + Data Provider Process + + Copyright (C) Simo Sorce 2008 + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "util/util.h" +#include "util/sss_utf8.h" +#include "confdb/confdb.h" +#include "db/sysdb.h" +#include "providers/backend.h" +#include "providers/fail_over.h" +#include "providers/be_refresh.h" +#include "providers/be_ptask.h" +#include "util/child_common.h" +#include "util/file_watch.h" +#include "resolv/async_resolv.h" +#include "sss_iface/sss_iface_async.h" + +#define RESOLV_CONF_PATH "/etc/resolv.conf" + +#define ONLINE_CB_RETRY 3 +#define ONLINE_CB_RETRY_MAX_DELAY 4 + +#define OFFLINE_TIMEOUT_RANDOM_OFFSET_DEFAULT 30 +#define OFFLINE_TIMEOUT_DEFAULT 60 +#define OFFLINE_TIMEOUT_MAX_DEFAULT 3600 + +/* sssd.service */ +static errno_t +data_provider_go_offline(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx); + +static errno_t +data_provider_reset_offline(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx); + +static errno_t +data_provider_logrotate(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx); + +bool be_is_offline(struct be_ctx *ctx) +{ + return ctx->offline; +} + +static void check_if_online(struct be_ctx *be_ctx, int delay); + +static errno_t +try_to_go_online(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *be_ctx_void) +{ + struct be_ctx *ctx = (struct be_ctx*) be_ctx_void; + + check_if_online(ctx, 0); + return EOK; +} + +static int get_offline_timeout(struct be_ctx *ctx) +{ + errno_t ret; + int offline_timeout; + + ret = confdb_get_int(ctx->cdb, ctx->conf_path, + CONFDB_DOMAIN_OFFLINE_TIMEOUT, + OFFLINE_TIMEOUT_DEFAULT, + &offline_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get offline_timeout from confdb. " + "Will use %d seconds.\n", OFFLINE_TIMEOUT_DEFAULT); + offline_timeout = OFFLINE_TIMEOUT_DEFAULT; + } + + return offline_timeout; +} + +static int get_offline_timeout_max(struct be_ctx *ctx) +{ + int offline_timeout_max; + errno_t ret; + + ret = confdb_get_int(ctx->cdb, ctx->conf_path, + CONFDB_DOMAIN_OFFLINE_TIMEOUT_MAX, + OFFLINE_TIMEOUT_MAX_DEFAULT, + &offline_timeout_max); + if (ret != EOK) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Failed to get offline_timeout_max from confdb. " + "Will use %d seconds.\n", OFFLINE_TIMEOUT_MAX_DEFAULT); + offline_timeout_max = OFFLINE_TIMEOUT_MAX_DEFAULT; + } + + return offline_timeout_max; +} + +static int get_offline_timeout_random_offset(struct be_ctx *ctx) +{ + int offline_timeout_random_offset; + errno_t ret; + + ret = confdb_get_int(ctx->cdb, ctx->conf_path, + CONFDB_DOMAIN_OFFLINE_TIMEOUT_RANDOM_OFFSET, + OFFLINE_TIMEOUT_RANDOM_OFFSET_DEFAULT, + &offline_timeout_random_offset); + if (ret != EOK) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Failed to get refresh_max_random_offset from confdb. " + "Will use %d seconds.\n", OFFLINE_TIMEOUT_RANDOM_OFFSET_DEFAULT); + offline_timeout_random_offset = OFFLINE_TIMEOUT_RANDOM_OFFSET_DEFAULT; + } + + return offline_timeout_random_offset; +} + +void be_mark_offline(struct be_ctx *ctx) +{ + int offline_timeout; + int offline_timeout_max; + int offline_timeout_random_offset; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Going offline!\n"); + + ctx->offline = true; + ctx->run_online_cb = true; + + if (ctx->check_if_online_ptask == NULL) { + /* This is the first time we go offline - create a periodic task + * to check if we can switch to online. */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Initialize check_if_online_ptask.\n"); + + offline_timeout = get_offline_timeout(ctx); + offline_timeout_max = get_offline_timeout_max(ctx); + offline_timeout_random_offset = get_offline_timeout_random_offset(ctx); + + ret = be_ptask_create_sync(ctx, + ctx, + offline_timeout, + offline_timeout, + offline_timeout, + offline_timeout_random_offset, + offline_timeout, + offline_timeout_max, + try_to_go_online, + ctx, "Check if online (periodic)", + BE_PTASK_OFFLINE_EXECUTE, + &ctx->check_if_online_ptask); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "be_ptask_create_sync failed [%d]: %s\n", + ret, sss_strerror(ret)); + } + } else { + /* Periodic task was already created. Just enable it. */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Enable check_if_online_ptask.\n"); + be_ptask_enable(ctx->check_if_online_ptask); + } + + be_run_offline_cb(ctx); +} + +static void be_subdom_reset_status(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct sss_domain_info *subdom = talloc_get_type(pvt, + struct sss_domain_info); + + DEBUG(SSSDBG_TRACE_LIBS, "Resetting subdomain %s\n", subdom->name); + subdom->state = DOM_ACTIVE; +} + +static void be_mark_subdom_offline(struct sss_domain_info *subdom, + struct be_ctx *be_ctx) +{ + struct timeval tv; + struct tevent_timer *timeout = NULL; + int reset_status_timeout; + + reset_status_timeout = get_offline_timeout(be_ctx); + tv = tevent_timeval_current_ofs(reset_status_timeout, 0); + + switch (subdom->state) { + case DOM_INCONSISTENT: + case DOM_DISABLED: + DEBUG(SSSDBG_MINOR_FAILURE, + "Won't touch disabled or inconsistent subdomain\n"); + return; + case DOM_INACTIVE: + DEBUG(SSSDBG_TRACE_ALL, "Subdomain already inactive\n"); + return; + case DOM_ACTIVE: + DEBUG(SSSDBG_TRACE_LIBS, + "Marking subdomain %s as inactive\n", subdom->name); + break; + } + + timeout = tevent_add_timer(be_ctx->ev, be_ctx, tv, + be_subdom_reset_status, subdom); + if (timeout == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot create timer\n"); + return; + } + + subdom->state = DOM_INACTIVE; +} + +void be_mark_dom_offline(struct sss_domain_info *dom, struct be_ctx *ctx) +{ + if (IS_SUBDOMAIN(dom) == false) { + DEBUG(SSSDBG_TRACE_LIBS, "Marking back end offline\n"); + be_mark_offline(ctx); + } else { + DEBUG(SSSDBG_TRACE_LIBS, "Marking subdomain %s offline\n", dom->name); + be_mark_subdom_offline(dom, ctx); + } +} + +static void reactivate_subdoms(struct sss_domain_info *head) +{ + struct sss_domain_info *dom; + + DEBUG(SSSDBG_TRACE_LIBS, "Resetting all subdomains\n"); + + for (dom = head; dom; dom = get_next_domain(dom, true)) { + if (sss_domain_get_state(dom) == DOM_INACTIVE) { + sss_domain_set_state(dom, DOM_ACTIVE); + } + } +} + +static void be_reset_offline(struct be_ctx *ctx) +{ + ctx->offline = false; + ctx->run_offline_cb = true; + + reactivate_subdoms(ctx->domain); + + be_ptask_disable(ctx->check_if_online_ptask); + be_run_online_cb(ctx); +} + +static void be_check_online_done(struct tevent_req *req); + +static errno_t be_check_online_request(struct be_ctx *be_ctx) +{ + struct tevent_req *req; + + reset_fo(be_ctx); + + req = dp_req_send(be_ctx, be_ctx->provider, NULL, "Online Check", + 0, NULL, DPT_ID, DPM_CHECK_ONLINE, 0, NULL, NULL); + if (req == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(req, be_check_online_done, be_ctx); + + return EOK; +} + +static void check_if_online_delayed(struct tevent_context *ev, + struct tevent_timer *tim, + struct timeval current_time, + void *private_data) +{ + errno_t ret; + struct be_ctx *be_ctx = talloc_get_type(private_data, struct be_ctx); + + be_run_unconditional_online_cb(be_ctx); + + if (!be_is_offline(be_ctx)) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Backend is already online, nothing to do.\n"); + be_ctx->check_online_ref_count = 0; + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Trying to go back online!\n"); + + ret = be_check_online_request(be_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create check online req.\n"); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, "Check online req created.\n"); + } +} + +static void be_check_online_done(struct tevent_req *req) +{ + struct be_ctx *be_ctx; + struct dp_reply_std *reply; + struct tevent_timer *time_event; + struct timeval schedule; + errno_t ret; + + be_ctx = tevent_req_callback_data(req, struct be_ctx); + + ret = dp_req_recv_ptr(be_ctx, req, struct dp_reply_std, &reply); + talloc_zfree(req); + if (ret != EOK) { + reply = NULL; + goto done; + } + + switch (reply->dp_error) { + case DP_ERR_OK: + if (be_ctx->last_dp_state != DP_ERR_OK) { + be_ctx->last_dp_state = DP_ERR_OK; + sss_log(SSS_LOG_INFO, "Backend is online\n"); + } + DEBUG(SSSDBG_TRACE_FUNC, "Backend is online\n"); + break; + case DP_ERR_OFFLINE: + if (be_ctx->last_dp_state != DP_ERR_OFFLINE) { + be_ctx->last_dp_state = DP_ERR_OFFLINE; + sss_log(SSS_LOG_INFO, "Backend is offline\n"); + } + DEBUG(SSSDBG_TRACE_FUNC, "Backend is offline\n"); + break; + default: + DEBUG(SSSDBG_TRACE_FUNC, "Error during online check [%d]: %s\n", + ret, sss_strerror(ret)); + break; + } + + be_ctx->check_online_ref_count--; + + if (reply->dp_error != DP_ERR_OK && be_ctx->check_online_ref_count > 0) { + be_ctx->check_online_retry_delay *= 2; + if (be_ctx->check_online_retry_delay > ONLINE_CB_RETRY_MAX_DELAY) { + be_ctx->check_online_retry_delay = ONLINE_CB_RETRY_MAX_DELAY; + } + + schedule = tevent_timeval_current_ofs(be_ctx->check_online_retry_delay, + 0); + time_event = tevent_add_timer(be_ctx->ev, be_ctx, schedule, + check_if_online_delayed, be_ctx); + + if (time_event == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to schedule online check\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Schedule check_if_online_delayed in %ds.\n", + be_ctx->check_online_retry_delay); + return; + } + +done: + be_ctx->check_online_ref_count = 0; + if (reply && reply->dp_error != DP_ERR_OFFLINE) { + if (reply->dp_error != DP_ERR_OK) { + reset_fo(be_ctx); + } + be_reset_offline(be_ctx); + } +} + +static void check_if_online(struct be_ctx *be_ctx, int delay) +{ + struct tevent_timer *time_event; + struct timeval schedule; + + be_ctx->check_online_ref_count++; + + if (be_ctx->check_online_ref_count != 1) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "There is an online check already running.\n"); + /* Do not have more than ONLINE_CB_RETRY retries in the queue */ + if (be_ctx->check_online_ref_count > ONLINE_CB_RETRY) { + be_ctx->check_online_ref_count--; + } + return; + } + + if (!dp_method_enabled(be_ctx->provider, DPT_ID, DPM_CHECK_ONLINE)) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "ID providers does not provide a check_online method.\n"); + goto failed; + } + + schedule = tevent_timeval_current_ofs(delay, 0); + time_event = tevent_add_timer(be_ctx->ev, be_ctx, schedule, + check_if_online_delayed, be_ctx); + + if (time_event == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Scheduling check_if_online_delayed failed.\n"); + goto failed; + } + + be_ctx->check_online_ref_count = ONLINE_CB_RETRY; + be_ctx->check_online_retry_delay = 1; + DEBUG(SSSDBG_TRACE_INTERNAL, + "Schedule check_if_online_delayed in %ds.\n", delay); + return; + +failed: + be_ctx->check_online_ref_count--; + + if (be_ctx->check_online_ref_count == 0) { + reset_fo(be_ctx); + be_reset_offline(be_ctx); + } + + return; +} + +static void signal_be_offline(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + struct be_ctx *ctx = talloc_get_type(private_data, struct be_ctx); + be_mark_offline(ctx); +} + +static void signal_be_reset_offline(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + struct be_ctx *ctx = talloc_get_type(private_data, struct be_ctx); + check_if_online(ctx, 0); +} + +static errno_t +be_register_monitor_iface(struct sbus_connection *conn, struct be_ctx *be_ctx) +{ + SBUS_INTERFACE(iface_service, + sssd_service, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_service, goOffline, data_provider_go_offline, be_ctx), + SBUS_SYNC(METHOD, sssd_service, resetOffline, data_provider_reset_offline, be_ctx), + SBUS_SYNC(METHOD, sssd_service, rotateLogs, data_provider_logrotate, be_ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES( + SBUS_SYNC(GETTER, sssd_service, debug_level, generic_get_debug_level, NULL), + SBUS_SYNC(SETTER, sssd_service, debug_level, generic_set_debug_level, NULL) + ) + ); + + struct sbus_path paths[] = { + {SSS_BUS_PATH, &iface_service}, + {NULL, NULL} + }; + + return sbus_connection_add_path_map(be_ctx->mon_conn, paths); +} + +static void dp_initialized(struct tevent_req *req); + +errno_t be_process_init(TALLOC_CTX *mem_ctx, + const char *be_domain, + uid_t uid, + gid_t gid, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct tevent_req *req; + struct be_ctx *be_ctx; + char *str = NULL; + errno_t ret; + + be_ctx = talloc_zero(mem_ctx, struct be_ctx); + if (be_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "talloc_zero() failed\n"); + return ENOMEM; + } + + be_ctx->ev = ev; + be_ctx->cdb = cdb; + be_ctx->uid = uid; + be_ctx->gid = gid; + be_ctx->identity = talloc_asprintf(be_ctx, "%%BE_%s", be_domain); + be_ctx->conf_path = talloc_asprintf(be_ctx, CONFDB_DOMAIN_PATH_TMPL, be_domain); + if (be_ctx->identity == NULL || be_ctx->conf_path == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!?\n"); + ret = ENOMEM; + goto done; + } + be_ctx->last_dp_state = -1; + + ret = be_init_failover(be_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize failover\n"); + goto done; + } + + ret = sssd_domain_init(be_ctx, cdb, be_domain, DB_PATH, &be_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize domain\n"); + goto done; + } + + ret = sysdb_master_domain_update(be_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to update master domain information!\n"); + goto done; + } + + /* We need this for subdomains support, as they have to store fully + * qualified user and group names for now. */ + ret = sss_names_init(be_ctx->domain, cdb, be_ctx->domain->name, + &be_ctx->domain->names); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to setup fully qualified name " + "format for %s\n", be_ctx->domain->name); + goto done; + } + + /* Read the global override_space option, for output name formatting */ + ret = confdb_get_string(cdb, be_ctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_OVERRIDE_SPACE, NULL, + &str); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the space substitution character [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + if (str != NULL) { + if (strlen(str) > 1) { + DEBUG(SSSDBG_MINOR_FAILURE, "Option %s is longer than 1 character " + "only the first character %c will be used\n", + CONFDB_MONITOR_OVERRIDE_SPACE, str[0]); + } + + be_ctx->override_space = str[0]; + } + + /* Read session_recording section */ + ret = session_recording_conf_load(be_ctx, cdb, &be_ctx->sr_conf); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed loading session recording configuration: %s\n", + strerror(ret)); + goto done; + } + + be_ctx->sbus_name = sss_iface_domain_bus(be_ctx, be_ctx->domain); + if (be_ctx->sbus_name == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not get sbus backend name.\n"); + ret = ENOMEM; + goto done; + } + + req = dp_init_send(be_ctx, be_ctx->ev, be_ctx, be_ctx->uid, be_ctx->gid, + be_ctx->sbus_name); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, dp_initialized, be_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(be_ctx); + } + + return ret; +} + +static void watch_update_resolv(const char *filename, void *arg) +{ + int ret; + struct be_ctx *be_ctx = (struct be_ctx *) arg; + + DEBUG(SSSDBG_TRACE_FUNC, "Reloading %s.\n", filename); + resolv_reread_configuration(be_ctx->be_res->resolv); + ret = res_init(); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reload %s.\n", filename); + return; + } + check_if_online(be_ctx, 1); +} + +static int watch_config_files(struct be_ctx *ctx) +{ + int ret; + bool monitor_resolv_conf; + bool use_inotify; + + /* Watch for changes to the DNS resolv.conf */ + ret = confdb_get_bool(ctx->cdb, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_RESOLV_CONF, + true, &monitor_resolv_conf); + if (ret != EOK) { + return ret; + } + + ret = confdb_get_bool(ctx->cdb, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_TRY_INOTIFY, + true, &use_inotify); + if (ret != EOK) { + return ret; + } + + if (monitor_resolv_conf) { + ctx->file_ctx = fw_watch_file(ctx, ctx->ev, RESOLV_CONF_PATH, + use_inotify, watch_update_resolv, ctx); + if (ctx->file_ctx == NULL) { + return ENOMEM; + } + + } else { + DEBUG(SSS_LOG_NOTICE, "%s watching is disabled\n", RESOLV_CONF_PATH); + } + + return EOK; +} + +static void fix_child_log_permissions(uid_t uid, gid_t gid) +{ + int ret; + const char *child_names[] = { "krb5_child", + "ldap_child", + "selinux_child", + "ad_gpo_child", + "proxy_child", + NULL }; + size_t c; + + for (c = 0; child_names[c] != NULL; c++) { + ret = chown_debug_file(child_names[c], uid, gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot chown the [%s] debug file, " + "debugging might not work!\n", child_names[c]); + } + } +} + +static void dp_initialized(struct tevent_req *req) +{ + struct tevent_signal *tes; + struct be_ctx *be_ctx; + errno_t ret; + + be_ctx = tevent_req_callback_data(req, struct be_ctx); + + ret = dp_init_recv(be_ctx, req); + talloc_zfree(req); + if (ret != EOK) { + goto done; + } + + ret = sss_monitor_service_init(be_ctx, be_ctx->ev, be_ctx->sbus_name, + be_ctx->identity, DATA_PROVIDER_VERSION, + MT_SVC_PROVIDER, NULL, &be_ctx->mon_conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize monitor connection\n"); + goto done; + } + + ret = be_register_monitor_iface(be_ctx->mon_conn, be_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to register monitor interface " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Handle SIGUSR1 to force offline behavior */ + BlockSignals(false, SIGUSR1); + tes = tevent_add_signal(be_ctx->ev, be_ctx, SIGUSR1, 0, + signal_be_offline, be_ctx); + if (tes == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to setup SIGUSR1 handler\n"); + ret = EIO; + goto done; + } + + /* Handle SIGUSR2 to force going online */ + BlockSignals(false, SIGUSR2); + tes = tevent_add_signal(be_ctx->ev, be_ctx, SIGUSR2, 0, + signal_be_reset_offline, be_ctx); + if (tes == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to setup SIGUSR2 handler\n"); + ret = EIO; + goto done; + } + + ret = chown_debug_file(NULL, be_ctx->uid, be_ctx->gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot chown the debug files, debugging might not work!\n"); + } + + fix_child_log_permissions(be_ctx->uid, be_ctx->gid); + + /* Set up watchers for system config files */ + ret = watch_config_files(be_ctx); + if (ret != EOK) { + goto done; + } + + ret = become_user(be_ctx->uid, be_ctx->gid); + if (ret != EOK) { + DEBUG(SSSDBG_FUNC_DATA, + "Cannot become user [%"SPRIuid"][%"SPRIgid"].\n", + be_ctx->uid, be_ctx->gid); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Backend provider (%s) started!\n", + be_ctx->domain->name); + + ret = EOK; + +done: + if (ret != EOK) { + exit(3); + } +} + +#ifndef UNIT_TESTING +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + char *be_domain = NULL; + char *srv_name = NULL; + struct main_context *main_ctx; + char *confdb_path; + int ret; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + {"domain", 0, POPT_ARG_STRING, &be_domain, 0, + _("Domain of the information provider (mandatory)"), NULL }, + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + if (be_domain == NULL) { + fprintf(stderr, "\nMissing option, --domain is a mandatory option.\n\n"); + poptPrintUsage(pc, stderr, 0); + return 1; + } + if (!is_valid_domain_name(be_domain)) { + fprintf(stderr, "\nInvalid --domain option.\n\n"); + return 1; + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = talloc_asprintf(NULL, "sssd_%s", be_domain); + if (!debug_log_file) return 2; + DEBUG_INIT(debug_level, opt_logger); + + srv_name = talloc_asprintf(NULL, "be[%s]", be_domain); + if (!srv_name) return 2; + + confdb_path = talloc_asprintf(NULL, CONFDB_DOMAIN_PATH_TMPL, be_domain); + if (!confdb_path) return 2; + + ret = server_setup(srv_name, false, 0, 0, 0, confdb_path, &main_ctx, false); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not set up mainloop [%d]\n", ret); + return 2; + } + + ret = setenv(SSS_DOM_ENV, be_domain, 1); + if (ret != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "Setting "SSS_DOM_ENV" failed, journald " + "logging might not work as expected\n"); + } + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, + "Could not set up to exit when parent process does\n"); + } + + ret = be_process_init(main_ctx, + be_domain, uid, gid, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not initialize backend [%d]\n", ret); + return 3; + } + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} +#endif + +static errno_t +data_provider_go_offline(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx) +{ + be_mark_offline(be_ctx); + + return EOK; +} + +static errno_t +data_provider_reset_offline(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx) +{ + check_if_online(be_ctx, 1); + + return EOK; +} + +static errno_t +data_provider_logrotate(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct be_ctx *be_ctx) +{ + return server_common_rotate_logs(be_ctx->cdb, be_ctx->conf_path); +} diff --git a/src/providers/data_provider_callbacks.c b/src/providers/data_provider_callbacks.c new file mode 100644 index 0000000..24e125e --- /dev/null +++ b/src/providers/data_provider_callbacks.c @@ -0,0 +1,306 @@ +/* + SSSD + + Data Provider Process - Callback + + Authors: + + Stephen Gallagher + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/backend.h" + +struct be_cb { + struct be_cb *prev; + struct be_cb *next; + + be_callback_t cb; + void *pvt; + + struct be_cb **list; + struct be_ctx *be; +}; + +struct be_cb_ctx { + struct be_ctx *be; + struct be_cb *callback; +}; + +static int cb_destructor(TALLOC_CTX *ptr) +{ + struct be_cb *cb = talloc_get_type(ptr, struct be_cb); + DLIST_REMOVE(*(cb->list), cb); + return 0; +} + +static int be_add_cb(TALLOC_CTX *mem_ctx, struct be_ctx *ctx, + be_callback_t cb, void *pvt, struct be_cb **cb_list, + struct be_cb **return_cb) +{ + struct be_cb *new_cb; + + if (!ctx || !cb) { + return EINVAL; + } + + new_cb = talloc(mem_ctx, struct be_cb); + if (!new_cb) { + return ENOMEM; + } + + new_cb->cb = cb; + new_cb->pvt = pvt; + new_cb->list = cb_list; + new_cb->be = ctx; + + DLIST_ADD(*cb_list, new_cb); + + talloc_set_destructor((TALLOC_CTX *) new_cb, cb_destructor); + + if (return_cb) { + *return_cb = new_cb; + } + + return EOK; +} + +static void be_run_cb_step(struct tevent_context *ev, struct tevent_timer *te, + struct timeval current_time, void *pvt) +{ + struct be_cb_ctx *cb_ctx = talloc_get_type(pvt, struct be_cb_ctx); + struct be_cb *next_cb; + struct tevent_timer *tev; + struct timeval soon; + + /* Store next callback in case this callback frees itself */ + next_cb = cb_ctx->callback->next; + + /* Call the callback */ + cb_ctx->callback->cb(cb_ctx->callback->pvt); + + if (next_cb) { + cb_ctx->callback = next_cb; + + /* Delay 30ms so we don't block any other events */ + soon = tevent_timeval_current_ofs(0, 30000); + tev = tevent_add_timer(cb_ctx->be->ev, cb_ctx, soon, + be_run_cb_step, + cb_ctx); + if (!tev) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Out of memory. Could not invoke callbacks\n"); + goto final; + } + return; + } + +final: + /* Steal the timer event onto the be_ctx so it doesn't + * get freed with the cb_ctx + */ + talloc_steal(cb_ctx->be, te); + talloc_free(cb_ctx); +} + +static errno_t be_run_cb(struct be_ctx *be, struct be_cb *cb_list) +{ + struct timeval soon; + struct tevent_timer *te; + struct be_cb_ctx *cb_ctx; + + if (cb_list == NULL) { + return EOK; + } + + cb_ctx = talloc(be, struct be_cb_ctx); + if (!cb_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Out of memory. Could not invoke callbacks\n"); + return ENOMEM; + } + cb_ctx->be = be; + cb_ctx->callback = cb_list; + + /* Delay 30ms so we don't block any other events */ + soon = tevent_timeval_current_ofs(0, 30000); + te = tevent_add_timer(be->ev, cb_ctx, soon, + be_run_cb_step, + cb_ctx); + if (!te) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Out of memory. Could not invoke callbacks\n"); + talloc_free(cb_ctx); + return ENOMEM; + } + + return EOK; +} + +int be_add_reconnect_cb(TALLOC_CTX *mem_ctx, struct be_ctx *ctx, be_callback_t cb, + void *pvt, struct be_cb **reconnect_cb) +{ + int ret; + + ret = be_add_cb(mem_ctx, ctx, cb, pvt, &ctx->reconnect_cb_list, reconnect_cb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_add_cb failed.\n"); + return ret; + } + + return EOK; +} + +void be_run_reconnect_cb(struct be_ctx *be) +{ + struct be_cb *callback = be->reconnect_cb_list; + struct be_cb *next_cb; + + if (callback) { + DEBUG(SSSDBG_TRACE_FUNC, "Reconnecting. Running callbacks.\n"); + + /** + * Call the callback: we have to call this right away + * so the provider doesn't go into offline even for + * a little while + */ + do { + /* Store next callback in case this callback frees itself */ + next_cb = callback->next; + + callback->cb(callback->pvt); + callback = next_cb; + } while(callback != NULL); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, "Reconnect call back list is empty, nothing to do.\n"); + } +} + +int be_add_online_cb(TALLOC_CTX *mem_ctx, struct be_ctx *ctx, be_callback_t cb, + void *pvt, struct be_cb **online_cb) +{ + int ret; + + ret = be_add_cb(mem_ctx, ctx, cb, pvt, &ctx->online_cb_list, online_cb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_add_cb failed.\n"); + return ret; + } + + /* Make sure we run the callback for the first + * connection after startup. + */ + ctx->run_online_cb = true; + + return EOK; +} + +void be_run_online_cb(struct be_ctx *be) { + int ret; + + if (be->run_online_cb) { + /* Reset the flag. We only want to run these + * callbacks when transitioning to online + */ + be->run_online_cb = false; + + if (be->online_cb_list) { + DEBUG(SSSDBG_MINOR_FAILURE, "Going online. Running callbacks.\n"); + + ret = be_run_cb(be, be->online_cb_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_run_cb failed.\n"); + } + + } else { + DEBUG(SSSDBG_TRACE_ALL, + "Online call back list is empty, nothing to do.\n"); + } + } +} + +int be_add_unconditional_online_cb(TALLOC_CTX *mem_ctx, struct be_ctx *ctx, + be_callback_t cb, void *pvt, + struct be_cb **unconditional_online_cb) +{ + return be_add_cb(mem_ctx, ctx, cb, pvt, &ctx->unconditional_online_cb_list, + unconditional_online_cb); +} + +void be_run_unconditional_online_cb(struct be_ctx *be) +{ + int ret; + + if (be->unconditional_online_cb_list) { + DEBUG(SSSDBG_TRACE_FUNC, "Running unconditional online callbacks.\n"); + + ret = be_run_cb(be, be->unconditional_online_cb_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "be_run_cb failed.\n"); + } + + } else { + DEBUG(SSSDBG_TRACE_ALL, + "List of unconditional online callbacks is empty, " \ + "nothing to do.\n"); + } +} + +int be_add_offline_cb(TALLOC_CTX *mem_ctx, struct be_ctx *ctx, be_callback_t cb, + void *pvt, struct be_cb **offline_cb) +{ + int ret; + + ret = be_add_cb(mem_ctx, ctx, cb, pvt, &ctx->offline_cb_list, offline_cb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_add_cb failed.\n"); + return ret; + } + + /* Make sure we run the callback when SSSD goes offline */ + ctx->run_offline_cb = true; + + return EOK; +} + +void be_run_offline_cb(struct be_ctx *be) { + int ret; + + if (be->run_offline_cb) { + /* Reset the flag, we only want to run these callbacks once when going + * offline */ + be->run_offline_cb = false; + + if (be->offline_cb_list) { + DEBUG(SSSDBG_MINOR_FAILURE, "Going offline. Running callbacks.\n"); + + ret = be_run_cb(be, be->offline_cb_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_run_cb failed.\n"); + } + + } else { + DEBUG(SSSDBG_TRACE_ALL, + "Offline call back list is empty, nothing to do.\n"); + } + } else { + DEBUG(SSSDBG_TRACE_ALL, + "Flag indicates that offline callback were already called.\n"); + } +} diff --git a/src/providers/data_provider_fo.c b/src/providers/data_provider_fo.c new file mode 100644 index 0000000..b0aed54 --- /dev/null +++ b/src/providers/data_provider_fo.c @@ -0,0 +1,945 @@ +/* + SSSD + + Data Provider Helpers + + Copyright (C) Simo Sorce 2009 + + 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 3 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; 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, see . +*/ + +#include +#include +#include "providers/backend.h" +#include "resolv/async_resolv.h" + +struct be_svc_callback { + struct be_svc_callback *prev; + struct be_svc_callback *next; + + struct be_svc_data *svc; + + be_svc_callback_fn_t *fn; + void *private_data; +}; + +static const char *proto_table[] = { FO_PROTO_TCP, FO_PROTO_UDP, NULL }; + +int be_fo_is_srv_identifier(const char *server) +{ + return server && strcasecmp(server, BE_SRV_IDENTIFIER) == 0; +} + +static int be_fo_get_options(struct be_ctx *ctx, + struct fo_options *opts) +{ + opts->service_resolv_timeout = dp_opt_get_int(ctx->be_res->opts, + DP_RES_OPT_RESOLVER_TIMEOUT); + opts->use_search_list = dp_opt_get_bool(ctx->be_res->opts, + DP_RES_OPT_RESOLVER_USE_SEARCH_LIST); + opts->retry_timeout = 30; + opts->srv_retry_neg_timeout = 15; + opts->family_order = ctx->be_res->family_order; + + return EOK; +} + +int be_init_failover(struct be_ctx *ctx) +{ + int ret; + struct fo_options fopts; + + if (ctx->be_fo != NULL) { + return EOK; + } + + ctx->be_fo = talloc_zero(ctx, struct be_failover_ctx); + if (ctx->be_fo == NULL) { + return ENOMEM; + } + + ret = be_res_init(ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error initializing resolver context\n"); + talloc_zfree(ctx->be_fo); + return ret; + } + ctx->be_fo->be_res = ctx->be_res; + + ret = be_fo_get_options(ctx, &fopts); + if (ret != EOK) { + talloc_zfree(ctx->be_fo); + return ret; + } + + ctx->be_fo->fo_ctx = fo_context_init(ctx->be_fo, &fopts); + if (!ctx->be_fo->fo_ctx) { + talloc_zfree(ctx->be_fo); + return ENOMEM; + } + + return EOK; +} + +static int be_svc_data_destroy(void *memptr) +{ + struct be_svc_data *svc; + + svc = talloc_get_type(memptr, struct be_svc_data); + + while (svc->callbacks) { + /* callbacks removes themselves from the list, + * so this while will freem them all and then terminate */ + talloc_free(svc->callbacks); + } + + return 0; +} + +/* + * Find registered be_svc_data by service name. + */ +static struct be_svc_data *be_fo_find_svc_data(struct be_ctx *ctx, + const char *service_name) +{ + struct be_svc_data *svc; + + if (!ctx || !ctx->be_fo) { + return 0; + } + + DLIST_FOR_EACH(svc, ctx->be_fo->svcs) { + if (strcmp(svc->name, service_name) == 0) { + return svc; + } + } + + return 0; +} + +int be_fo_add_service(struct be_ctx *ctx, const char *service_name, + datacmp_fn user_data_cmp) +{ + struct fo_service *service; + struct be_svc_data *svc; + int ret; + + svc = be_fo_find_svc_data(ctx, service_name); + if (svc) { + DEBUG(SSSDBG_TRACE_FUNC, "Failover service already initialized!\n"); + /* we already have a service up and configured, + * can happen when using both id and auth provider + */ + return EOK; + } + + /* if not in the be service list, try to create new one */ + + ret = fo_new_service(ctx->be_fo->fo_ctx, service_name, user_data_cmp, + &service); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create failover service!\n"); + return ret; + } + + svc = talloc_zero(ctx->be_fo, struct be_svc_data); + if (!svc) { + return ENOMEM; + } + talloc_set_destructor((TALLOC_CTX *)svc, be_svc_data_destroy); + + svc->name = talloc_strdup(svc, service_name); + if (!svc->name) { + talloc_zfree(svc); + return ENOMEM; + } + svc->fo_service = service; + + DLIST_ADD(ctx->be_fo->svcs, svc); + + return EOK; +} + +static int be_svc_callback_destroy(void *memptr) +{ + struct be_svc_callback *callback; + + callback = talloc_get_type(memptr, struct be_svc_callback); + + if (callback->svc) { + DLIST_REMOVE(callback->svc->callbacks, callback); + } + + return 0; +} + +int be_fo_service_add_callback(TALLOC_CTX *memctx, + struct be_ctx *ctx, const char *service_name, + be_svc_callback_fn_t *fn, void *private_data) +{ + struct be_svc_callback *callback; + struct be_svc_data *svc; + + svc = be_fo_find_svc_data(ctx, service_name); + if (NULL == svc) { + return ENOENT; + } + + callback = talloc_zero(memctx, struct be_svc_callback); + if (!callback) { + return ENOMEM; + } + talloc_set_destructor((TALLOC_CTX *)callback, be_svc_callback_destroy); + + callback->svc = svc; + callback->fn = fn; + callback->private_data = private_data; + + DLIST_ADD(svc->callbacks, callback); + + return EOK; +} + +void be_fo_set_srv_lookup_plugin(struct be_ctx *ctx, + fo_srv_lookup_plugin_send_t send_fn, + fo_srv_lookup_plugin_recv_t recv_fn, + void *pvt, + const char *plugin_name) +{ + bool bret; + + DEBUG(SSSDBG_TRACE_FUNC, "Trying to set SRV lookup plugin to %s\n", + plugin_name); + + bret = fo_set_srv_lookup_plugin(ctx->be_fo->fo_ctx, send_fn, recv_fn, pvt); + if (bret) { + DEBUG(SSSDBG_TRACE_FUNC, "SRV lookup plugin is now %s\n", + plugin_name); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to set SRV lookup plugin, " + "another plugin may be already in place\n"); + } +} + +errno_t be_fo_set_dns_srv_lookup_plugin(struct be_ctx *be_ctx, + const char *hostname) +{ + struct fo_resolve_srv_dns_ctx *srv_ctx = NULL; + char resolved_hostname[HOST_NAME_MAX + 1]; + errno_t ret; + + if (hostname == NULL) { + ret = gethostname(resolved_hostname, sizeof(resolved_hostname)); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "gethostname() failed: [%d]: %s\n", ret, strerror(ret)); + return ret; + } + resolved_hostname[HOST_NAME_MAX] = '\0'; + hostname = resolved_hostname; + } + + srv_ctx = fo_resolve_srv_dns_ctx_init(be_ctx, be_ctx->be_res->resolv, + be_ctx->be_res->family_order, + default_host_dbs, hostname, + be_ctx->domain->name); + if (srv_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n"); + return ENOMEM; + } + + be_fo_set_srv_lookup_plugin(be_ctx, fo_resolve_srv_dns_send, + fo_resolve_srv_dns_recv, srv_ctx, "DNS"); + + return EOK; +} + +int be_fo_add_srv_server(struct be_ctx *ctx, + const char *service_name, + const char *query_service, + const char *default_discovery_domain, + enum be_fo_protocol proto, + bool proto_fallback, void *user_data) +{ + struct be_svc_data *svc; + const char *domain; + int ret; + int i; + + svc = be_fo_find_svc_data(ctx, service_name); + if (NULL == svc) { + return ENOENT; + } + + domain = dp_opt_get_string(ctx->be_res->opts, DP_RES_OPT_DNS_DOMAIN); + if (!domain) { + domain = default_discovery_domain; + } + + /* Add the first protocol as the primary lookup */ + ret = fo_add_srv_server(svc->fo_service, query_service, + domain, ctx->domain->name, + proto_table[proto], user_data); + if (ret && ret != EEXIST) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add SRV lookup reference to failover service " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + if (proto_fallback) { + i = (proto + 1) % BE_FO_PROTO_SENTINEL; + /* All the rest as fallback */ + while (i != proto) { + ret = fo_add_srv_server(svc->fo_service, query_service, + domain, ctx->domain->name, + proto_table[i], user_data); + if (ret && ret != EEXIST) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add SRV lookup reference to failover " + "service [%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + i = (i + 1) % BE_FO_PROTO_SENTINEL; + } + } + + return EOK; +} + +int be_fo_get_server_count(struct be_ctx *ctx, const char *service_name) +{ + struct be_svc_data *svc_data; + + svc_data = be_fo_find_svc_data(ctx, service_name); + if (!svc_data) { + return 0; + } + + return fo_get_server_count(svc_data->fo_service); +} + +int be_fo_add_server(struct be_ctx *ctx, const char *service_name, + const char *server, int port, void *user_data, + bool primary) +{ + struct be_svc_data *svc; + int ret; + + svc = be_fo_find_svc_data(ctx, service_name); + if (NULL == svc) { + return ENOENT; + } + + ret = fo_add_server(svc->fo_service, server, port, + user_data, primary); + if (ret && ret != EEXIST) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add server to failover service [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +struct be_resolve_server_state { + struct tevent_context *ev; + struct be_ctx *ctx; + + struct be_svc_data *svc; + int attempts; + + struct fo_server *srv; + bool first_try; +}; + +struct be_primary_server_ctx { + struct be_ctx *bctx; + struct tevent_context *ev; + + struct be_svc_data *svc; + unsigned long timeout; + + int attempts; +}; + +errno_t be_resolve_server_process(struct tevent_req *subreq, + struct be_resolve_server_state *state, + struct tevent_req **new_subreq); +static void be_primary_server_done(struct tevent_req *subreq); +static errno_t +be_primary_server_timeout_activate(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *bctx, + struct be_svc_data *svc, + const unsigned long timeout_seconds); + +static void +be_primary_server_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct be_primary_server_ctx *ctx = talloc_get_type(pvt, struct be_primary_server_ctx); + struct tevent_req *subreq; + + ctx->bctx->be_fo->primary_server_handler = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Looking for primary server!\n"); + subreq = fo_resolve_service_send(ctx->bctx, ctx->ev, + ctx->bctx->be_fo->be_res->resolv, + ctx->bctx->be_fo->fo_ctx, + ctx->svc->fo_service); + if (subreq == NULL) { + return; + } + tevent_req_set_callback(subreq, be_primary_server_done, ctx); +} + +static void be_primary_server_done(struct tevent_req *subreq) +{ + errno_t ret; + struct be_primary_server_ctx *ctx; + struct be_resolve_server_state *resolve_state; + struct tevent_req *new_subreq; + + ctx = tevent_req_callback_data(subreq, struct be_primary_server_ctx); + + resolve_state = talloc_zero(ctx->bctx, struct be_resolve_server_state); + if (resolve_state == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n"); + return; + } + + resolve_state->attempts = ctx->attempts; + resolve_state->ctx = ctx->bctx; + resolve_state->ev = ctx->ev; + resolve_state->first_try = true; + resolve_state->srv = NULL; + resolve_state->svc = ctx->svc; + + ret = be_resolve_server_process(subreq, resolve_state, &new_subreq); + talloc_free(subreq); + if (ret == EAGAIN) { + ctx->attempts++; + tevent_req_set_callback(new_subreq, be_primary_server_done, ctx); + return; + } else if (ret == EIO || (ret == EOK && + !fo_is_server_primary(resolve_state->srv))) { + + /* Schedule another lookup + * (either no server could be found or it was not primary) + */ + ret = be_primary_server_timeout_activate(ctx->bctx, ctx->ev, ctx->bctx, + ctx->svc, ctx->timeout); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not schedule primary server lookup [%d]: %s\n", + ret, sss_strerror(ret)); + } + } else if (ret == EOK) { + be_run_reconnect_cb(ctx->bctx); + } + talloc_zfree(ctx); + + /* If an error occurred just end the routine */ +} + +static errno_t +be_primary_server_timeout_activate(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *bctx, + struct be_svc_data *svc, + const unsigned long timeout_seconds) +{ + struct timeval tv; + struct be_primary_server_ctx *ctx; + struct be_failover_ctx *fo_ctx = bctx->be_fo; + + if (fo_ctx->primary_server_handler != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "The primary server reconnection " + "is already scheduled\n"); + return EOK; + } + + ctx = talloc_zero(mem_ctx, struct be_primary_server_ctx); + if (ctx == NULL) { + return ENOMEM; + } + + ctx->bctx = bctx; + ctx->ev = ev; + ctx->svc = svc; + ctx->timeout = timeout_seconds; + + tv = tevent_timeval_current(); + tv = tevent_timeval_add(&tv, timeout_seconds, 0); + fo_ctx->primary_server_handler = tevent_add_timer(ev, bctx, tv, + be_primary_server_timeout, ctx); + if (fo_ctx->primary_server_handler == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + talloc_free(ctx); + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Primary server reactivation timeout set " + "to %lu seconds\n", timeout_seconds); + return EOK; +} + + +static void be_resolve_server_done(struct tevent_req *subreq); + +struct tevent_req *be_resolve_server_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct be_ctx *ctx, + const char *service_name, + bool first_try) +{ + struct tevent_req *req, *subreq; + struct be_resolve_server_state *state; + struct be_svc_data *svc; + + req = tevent_req_create(memctx, &state, struct be_resolve_server_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + + svc = be_fo_find_svc_data(ctx, service_name); + if (NULL == svc) { + tevent_req_error(req, EINVAL); + tevent_req_post(req, ev); + return req; + } + + state->svc = svc; + state->attempts = 0; + state->first_try = first_try; + + subreq = fo_resolve_service_send(state, ev, + ctx->be_fo->be_res->resolv, + ctx->be_fo->fo_ctx, + svc->fo_service); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, be_resolve_server_done, req); + + return req; +} + +static void be_resolve_server_done(struct tevent_req *subreq) +{ + struct tevent_req *new_subreq; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct be_resolve_server_state *state = tevent_req_data(req, + struct be_resolve_server_state); + time_t timeout = fo_get_service_retry_timeout(state->svc->fo_service) + 1; + int ret; + + ret = be_resolve_server_process(subreq, state, &new_subreq); + talloc_zfree(subreq); + if (ret == EAGAIN) { + tevent_req_set_callback(new_subreq, be_resolve_server_done, req); + return; + } else if (ret != EOK) { + goto fail; + } + + if (!fo_is_server_primary(state->srv)) { + /* FIXME: make the timeout configurable */ + ret = be_primary_server_timeout_activate(state->ctx, state->ev, + state->ctx, state->svc, + timeout); + if (ret != EOK) { + goto fail; + } + } + + tevent_req_done(req); + return; + +fail: + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_LIBS, + "Server resolution failed: [%d]: All servers down\n", ret); + } else if (ret == EFAULT || ret == EIO || ret == EPERM) { + DEBUG(SSSDBG_TRACE_LIBS, + "Server [%s] resolution failed: [%d]: %s\n", + state->srv ? fo_get_server_name(state->srv) : "NULL", + ret, sss_strerror(ret)); + + } else { + DEBUG(SSSDBG_TRACE_LIBS, + "Server resolution failed: [%d]: %s\n", ret, sss_strerror(ret)); + } + state->svc->first_resolved = NULL; + tevent_req_error(req, ret); +} + +static void dump_be_svc_data(const struct be_svc_data *svc) +{ + DEBUG(SSSDBG_OP_FAILURE, "be_svc_data: name=[%s] last_good_srv=[%s] " + "last_good_port=[%d] last_status_change=[%"SPRItime"]\n", + svc->name, svc->last_good_srv, svc->last_good_port, + svc->last_status_change); +} + +errno_t be_resolve_server_process(struct tevent_req *subreq, + struct be_resolve_server_state *state, + struct tevent_req **new_subreq) +{ + errno_t ret; + time_t srv_status_change; + struct be_svc_callback *callback; + char *srvname; + + ret = fo_resolve_service_recv(subreq, state, &state->srv); + switch (ret) { + case EOK: + if (!state->srv) { + return EFAULT; + } + break; + + case ENOENT: + /* all servers have been tried and none + * was found good, go offline */ + return EIO; + + default: + /* mark server as bad and retry */ + if (!state->srv) { + return EFAULT; + } + DEBUG(SSSDBG_MINOR_FAILURE, + "Couldn't resolve server (%s), resolver returned [%d]: %s\n", + fo_get_server_str_name(state->srv), ret, sss_strerror(ret)); + + state->attempts++; + if (state->attempts >= 10) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to find a server after 10 attempts\n"); + return EIO; + } + + /* now try next one */ + DEBUG(SSSDBG_TRACE_LIBS, "Trying with the next one!\n"); + subreq = fo_resolve_service_send(state, state->ev, + state->ctx->be_fo->be_res->resolv, + state->ctx->be_fo->fo_ctx, + state->svc->fo_service); + if (!subreq) { + return ENOMEM; + } + + if (new_subreq) { + *new_subreq = subreq; + } + + return EAGAIN; + } + + /* all fine we got the server */ + if (state->svc->first_resolved == NULL || state->first_try == true) { + DEBUG(SSSDBG_TRACE_LIBS, "Saving the first resolved server\n"); + state->svc->first_resolved = state->srv; + } else if (state->svc->first_resolved == state->srv) { + DEBUG(SSSDBG_OP_FAILURE, + "The fail over cycled through all available servers\n"); + return ENOENT; + } + + if (fo_get_server_name(state->srv)) { + struct resolv_hostent *srvaddr; + srvaddr = fo_get_server_hostent(state->srv); + if (!srvaddr) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No hostent available for server (%s)\n", + fo_get_server_str_name(state->srv)); + return EFAULT; + } + + if (!srvaddr->addr_list[0]) { + DEBUG(SSSDBG_FUNC_DATA, "Found socket for server %s: [%s]\n", + fo_get_server_str_name(state->srv), srvaddr->name); + } + else { + char ipaddr[128]; + inet_ntop(srvaddr->family, srvaddr->addr_list[0]->ipaddr, + ipaddr, 128); + + DEBUG(SSSDBG_FUNC_DATA, "Found address for server %s: [%s] TTL %d\n", + fo_get_server_str_name(state->srv), ipaddr, + srvaddr->addr_list[0]->ttl); + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing server name.\n"); + dump_be_svc_data(state->svc); + dump_fo_server(state->srv); + dump_fo_server_list(state->srv); + return ENOENT; + } + + srv_status_change = fo_get_server_hostname_last_change(state->srv); + + /* now call all svc callbacks if server changed or if it is explicitly + * requested or if the server is the same but changed status since last time*/ + if (state->svc->last_good_srv == NULL || + strcmp(fo_get_server_name(state->srv), state->svc->last_good_srv) != 0 || + fo_get_server_port(state->srv) != state->svc->last_good_port || + state->svc->run_callbacks || + srv_status_change > state->svc->last_status_change) { + state->svc->last_status_change = srv_status_change; + state->svc->run_callbacks = false; + + srvname = talloc_strdup(state->svc, fo_get_server_name(state->srv)); + if (srvname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to copy server name\n"); + return ENOMEM; + } + + talloc_free(state->svc->last_good_srv); + state->svc->last_good_srv = srvname; + state->svc->last_good_port = fo_get_server_port(state->srv); + + DLIST_FOR_EACH(callback, state->svc->callbacks) { + callback->fn(callback->private_data, state->srv); + } + } + + return EOK; +} + +int be_resolve_server_recv(struct tevent_req *req, + TALLOC_CTX *ref_ctx, + struct fo_server **srv) +{ + struct be_resolve_server_state *state = tevent_req_data(req, + struct be_resolve_server_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (srv) { + fo_ref_server(ref_ctx, state->srv); + *srv = state->srv; + } + + return EOK; +} + +void be_fo_try_next_server(struct be_ctx *ctx, const char *service_name) +{ + struct be_svc_data *svc; + + svc = be_fo_find_svc_data(ctx, service_name); + if (svc) { + fo_try_next_server(svc->fo_service); + } +} + +const char *be_fo_get_active_server_name(struct be_ctx *ctx, + const char *service_name) +{ + struct be_svc_data *svc; + struct fo_server *server; + + svc = be_fo_find_svc_data(ctx, service_name); + if (svc != NULL) { + server = fo_get_active_server(svc->fo_service); + if (server != NULL) { + return fo_get_server_name(server); + } + } + + return NULL; +} + +int be_fo_run_callbacks_at_next_request(struct be_ctx *ctx, + const char *service_name) +{ + struct be_svc_data *svc; + + svc = be_fo_find_svc_data(ctx, service_name); + if (NULL == svc) { + return ENOENT; + } + + svc->run_callbacks = true; + + return EOK; +} + +void reset_fo(struct be_ctx *be_ctx) +{ + fo_reset_services(be_ctx->be_fo->fo_ctx); +} + +void be_fo_reset_svc(struct be_ctx *be_ctx, + const char *svc_name) +{ + struct fo_service *service; + int ret; + + DEBUG(SSSDBG_TRACE_LIBS, + "Resetting all servers in service %s\n", svc_name); + + ret = fo_get_service(be_ctx->be_fo->fo_ctx, svc_name, &service); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot retrieve service [%s]\n", svc_name); + return; + } + + fo_reset_servers(service); +} + +void _be_fo_set_port_status(struct be_ctx *ctx, + const char *service_name, + struct fo_server *server, + enum port_status status, + int line, + const char *file, + const char *function) +{ + struct be_svc_data *be_svc; + + /* Print debug info */ + switch (status) { + case PORT_NEUTRAL: + DEBUG(SSSDBG_BE_FO, + "Setting status: PORT_NEUTRAL. Called from: %s: %s: %d\n", + file, function, line); + break; + case PORT_WORKING: + DEBUG(SSSDBG_BE_FO, + "Setting status: PORT_WORKING. Called from: %s: %s: %d\n", + file, function, line); + break; + case PORT_NOT_WORKING: + DEBUG(SSSDBG_BE_FO, + "Setting status: PORT_NOT_WORKING. Called from: %s: %s: %d\n", + file, function, line); + break; + } + + be_svc = be_fo_find_svc_data(ctx, service_name); + if (be_svc == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "No service associated with name %s\n", service_name); + return; + } + + if (!fo_svc_has_server(be_svc->fo_service, server)) { + DEBUG(SSSDBG_OP_FAILURE, + "The server %p is not valid anymore, cannot set its status\n", + server); + return; + } + + /* Now we know that the server is valid */ + fo_set_port_status(server, status); + + if (status == PORT_WORKING) { + /* We were successful in connecting to the server. Cycle through all + * available servers next time */ + be_svc->first_resolved = NULL; + } +} + +/* Resolver back end interface */ +static struct dp_option dp_res_default_opts[] = { + { "lookup_family_order", DP_OPT_STRING, { "ipv4_first" }, NULL_STRING }, + { "dns_resolver_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "dns_resolver_op_timeout", DP_OPT_NUMBER, { .number = 3 }, NULL_NUMBER }, + { "dns_resolver_server_timeout", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER }, + { "dns_resolver_use_search_list", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "dns_discovery_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + DP_OPTION_TERMINATOR +}; + +static errno_t be_res_get_opts(struct be_resolv_ctx *res_ctx, + struct confdb_ctx *cdb, + const char *conf_path) +{ + errno_t ret; + const char *str_family; + + ret = dp_get_options(res_ctx, cdb, conf_path, + dp_res_default_opts, + DP_RES_OPTS, + &res_ctx->opts); + if (ret != EOK) { + return ret; + } + + str_family = dp_opt_get_string(res_ctx->opts, DP_RES_OPT_FAMILY_ORDER); + DEBUG(SSSDBG_CONF_SETTINGS, "Lookup order: %s\n", str_family); + + if (strcasecmp(str_family, "ipv4_first") == 0) { + res_ctx->family_order = IPV4_FIRST; + } else if (strcasecmp(str_family, "ipv4_only") == 0) { + res_ctx->family_order = IPV4_ONLY; + } else if (strcasecmp(str_family, "ipv6_first") == 0) { + res_ctx->family_order = IPV6_FIRST; + } else if (strcasecmp(str_family, "ipv6_only") == 0) { + res_ctx->family_order = IPV6_ONLY; + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unknown value for option %s: %s\n", + dp_res_default_opts[DP_RES_OPT_FAMILY_ORDER].opt_name, str_family); + return EINVAL; + } + + return EOK; +} + +errno_t be_res_init(struct be_ctx *ctx) +{ + errno_t ret; + + if (ctx->be_res != NULL) { + return EOK; + } + + ctx->be_res = talloc_zero(ctx, struct be_resolv_ctx); + if (ctx->be_res == NULL) { + return ENOMEM; + } + + ret = be_res_get_opts(ctx->be_res, ctx->cdb, ctx->conf_path); + if (ret != EOK) { + talloc_zfree(ctx->be_res); + return ret; + } + + ret = resolv_init(ctx, ctx->ev, + dp_opt_get_int(ctx->be_res->opts, + DP_RES_OPT_RESOLVER_OP_TIMEOUT), + dp_opt_get_int(ctx->be_res->opts, + DP_RES_OPT_RESOLVER_SERVER_TIMEOUT), + dp_opt_get_bool(ctx->be_res->opts, + DP_RES_OPT_RESOLVER_USE_SEARCH_LIST), + &ctx->be_res->resolv); + if (ret != EOK) { + talloc_zfree(ctx->be_res); + return ret; + } + + return EOK; +} diff --git a/src/providers/data_provider_opts.c b/src/providers/data_provider_opts.c new file mode 100644 index 0000000..30833fb --- /dev/null +++ b/src/providers/data_provider_opts.c @@ -0,0 +1,482 @@ +/* + SSSD + + Data Provider Helpers + + Copyright (C) Simo Sorce 2009 + + 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 3 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; 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, see . +*/ + +#include "data_provider.h" + +/* =Copy-Option-From-Subdomain-If-Allowed================================= */ +void dp_option_inherit_match(char **inherit_opt_list, + int option, + struct dp_option *parent_opts, + struct dp_option *subdom_opts) +{ + bool inherit_option; + + inherit_option = string_in_list(parent_opts[option].opt_name, + inherit_opt_list, false); + if (inherit_option == false) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Option %s is not set up to be inherited\n", + parent_opts[option].opt_name); + return; + } + + dp_option_inherit(option, parent_opts, subdom_opts); +} + +void dp_option_inherit(int option, + struct dp_option *parent_opts, + struct dp_option *subdom_opts) +{ + errno_t ret; + + DEBUG(SSSDBG_CONF_SETTINGS, + "Inheriting option %s\n", parent_opts[option].opt_name); + switch (parent_opts[option].type) { + case DP_OPT_NUMBER: + ret = dp_opt_set_int(subdom_opts, + option, + dp_opt_get_int(parent_opts, + option)); + break; + case DP_OPT_STRING: + ret = dp_opt_set_string(subdom_opts, + option, + dp_opt_get_string(parent_opts, + option)); + break; + case DP_OPT_BLOB: + ret = dp_opt_set_blob(subdom_opts, + option, + dp_opt_get_blob(parent_opts, + option)); + break; + case DP_OPT_BOOL: + ret = dp_opt_set_bool(subdom_opts, + option, + dp_opt_get_bool(parent_opts, + option)); + break; + default: + ret = EINVAL; + break; + } + + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to inherit option %s\n", parent_opts[option].opt_name); + /* Not fatal */ + } +} + +/* =Retrieve-Options====================================================== */ + +int dp_get_options(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct dp_option *def_opts, + int num_opts, + struct dp_option **_opts) +{ + struct dp_option *opts; + int i, ret; + + opts = talloc_zero_array(memctx, struct dp_option, num_opts); + if (!opts) return ENOMEM; + + for (i = 0; i < num_opts; i++) { + char *tmp; + + opts[i].opt_name = def_opts[i].opt_name; + opts[i].type = def_opts[i].type; + opts[i].def_val = def_opts[i].def_val; + + switch (def_opts[i].type) { + case DP_OPT_STRING: + ret = confdb_get_string(cdb, opts, conf_path, + opts[i].opt_name, + opts[i].def_val.cstring, + &opts[i].val.string); + if (ret != EOK || + ((opts[i].def_val.string != NULL) && + (opts[i].val.string == NULL))) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to retrieve value for option (%s)\n", + opts[i].opt_name); + if (ret == EOK) ret = EINVAL; + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s has%s value %s\n", + opts[i].opt_name, + opts[i].val.cstring ? "" : " no", + opts[i].val.cstring ? opts[i].val.cstring : ""); + break; + + case DP_OPT_BLOB: + ret = confdb_get_string(cdb, opts, conf_path, + opts[i].opt_name, + NULL, &tmp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to retrieve value for option (%s)\n", + opts[i].opt_name); + goto done; + } + + if (tmp) { + opts[i].val.blob.data = (uint8_t *)tmp; + opts[i].val.blob.length = strlen(tmp); + } else if (opts[i].def_val.blob.data != NULL) { + opts[i].val.blob.data = opts[i].def_val.blob.data; + opts[i].val.blob.length = opts[i].def_val.blob.length; + } else { + opts[i].val.blob.data = NULL; + opts[i].val.blob.length = 0; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s has %s binary value.\n", + opts[i].opt_name, opts[i].val.blob.length?"a":"no"); + break; + + case DP_OPT_NUMBER: + ret = confdb_get_int(cdb, conf_path, + opts[i].opt_name, + opts[i].def_val.number, + &opts[i].val.number); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to retrieve value for option (%s)\n", + opts[i].opt_name); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s has value %d\n", + opts[i].opt_name, opts[i].val.number); + break; + + case DP_OPT_BOOL: + ret = confdb_get_bool(cdb, conf_path, + opts[i].opt_name, + opts[i].def_val.boolean, + &opts[i].val.boolean); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to retrieve value for option (%s)\n", + opts[i].opt_name); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s is %s\n", + opts[i].opt_name, opts[i].val.boolean?"TRUE":"FALSE"); + break; + } + } + + ret = EOK; + *_opts = opts; + +done: + if (ret != EOK) talloc_zfree(opts); + return ret; +} + +/* =Basic-Option-Helpers================================================== */ +static int dp_copy_options_ex(TALLOC_CTX *memctx, + bool copy_values, + struct dp_option *src_opts, + int num_opts, + struct dp_option **_opts) +{ + struct dp_option *opts; + int i, ret = EOK; + + opts = talloc_zero_array(memctx, struct dp_option, num_opts); + if (!opts) return ENOMEM; + + for (i = 0; i < num_opts; i++) { + opts[i].opt_name = src_opts[i].opt_name; + opts[i].type = src_opts[i].type; + opts[i].def_val = src_opts[i].def_val; + ret = EOK; + + switch (src_opts[i].type) { + case DP_OPT_STRING: + if (copy_values) { + ret = dp_opt_set_string(opts, i, src_opts[i].val.string); + } else { + ret = dp_opt_set_string(opts, i, src_opts[i].def_val.string); + } + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to copy value for option (%s)\n", + opts[i].opt_name); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s has%s value %s\n", + opts[i].opt_name, + opts[i].val.cstring ? "" : " no", + opts[i].val.cstring ? opts[i].val.cstring : ""); + break; + + case DP_OPT_BLOB: + if (copy_values) { + ret = dp_opt_set_blob(opts, i, src_opts[i].val.blob); + } else { + ret = dp_opt_set_blob(opts, i, src_opts[i].def_val.blob); + } + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to copy value for option (%s)\n", + opts[i].opt_name); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s has %s binary value.\n", + opts[i].opt_name, opts[i].val.blob.length?"a":"no"); + break; + + case DP_OPT_NUMBER: + if (copy_values) { + ret = dp_opt_set_int(opts, i, src_opts[i].val.number); + } else { + ret = dp_opt_set_int(opts, i, src_opts[i].def_val.number); + } + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to copy value for option (%s)\n", + opts[i].opt_name); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s has value %d\n", + opts[i].opt_name, opts[i].val.number); + break; + + case DP_OPT_BOOL: + if (copy_values) { + ret = dp_opt_set_bool(opts, i, src_opts[i].val.boolean); + } else { + ret = dp_opt_set_bool(opts, i, src_opts[i].def_val.boolean); + } + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to copy value for option (%s)\n", + opts[i].opt_name); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s is %s\n", + opts[i].opt_name, opts[i].val.boolean?"TRUE":"FALSE"); + break; + } + } + + *_opts = opts; + +done: + if (ret != EOK) talloc_zfree(opts); + return ret; +} + +int dp_copy_options(TALLOC_CTX *memctx, + struct dp_option *src_opts, + int num_opts, + struct dp_option **_opts) +{ + return dp_copy_options_ex(memctx, true, src_opts, num_opts, _opts); +} + +int dp_copy_defaults(TALLOC_CTX *memctx, + struct dp_option *src_opts, + int num_opts, + struct dp_option **_opts) +{ + return dp_copy_options_ex(memctx, false, src_opts, num_opts, _opts); +} + +static const char *dp_opt_type_to_string(enum dp_opt_type type) +{ + switch (type) { + case DP_OPT_STRING: + return "String"; + case DP_OPT_BLOB: + return "Blob"; + case DP_OPT_NUMBER: + return "Number"; + case DP_OPT_BOOL: + return "Boolean"; + } + return NULL; +} + +/* Getters */ +const char *_dp_opt_get_cstring(struct dp_option *opts, + int id, const char *location) +{ + if (opts[id].type != DP_OPT_STRING) { + DEBUG(SSSDBG_FATAL_FAILURE, + "[%s] Requested type 'String' for option '%s'" + " but value is of type '%s'!\n", + location, opts[id].opt_name, + dp_opt_type_to_string(opts[id].type)); + return NULL; + } + return opts[id].val.cstring; +} + +char *_dp_opt_get_string(struct dp_option *opts, + int id, const char *location) +{ + if (opts[id].type != DP_OPT_STRING) { + DEBUG(SSSDBG_FATAL_FAILURE, + "[%s] Requested type 'String' for option '%s'" + " but value is of type '%s'!\n", + location, opts[id].opt_name, + dp_opt_type_to_string(opts[id].type)); + return NULL; + } + return opts[id].val.string; +} + +struct dp_opt_blob _dp_opt_get_blob(struct dp_option *opts, + int id, const char *location) +{ + struct dp_opt_blob null_blob = { NULL, 0 }; + if (opts[id].type != DP_OPT_BLOB) { + DEBUG(SSSDBG_FATAL_FAILURE, "[%s] Requested type 'Blob' for option '%s'" + " but value is of type '%s'!\n", + location, opts[id].opt_name, + dp_opt_type_to_string(opts[id].type)); + return null_blob; + } + return opts[id].val.blob; +} + +int _dp_opt_get_int(struct dp_option *opts, + int id, const char *location) +{ + if (opts[id].type != DP_OPT_NUMBER) { + DEBUG(SSSDBG_FATAL_FAILURE, + "[%s] Requested type 'Number' for option '%s'" + " but value is of type '%s'!\n", + location, opts[id].opt_name, + dp_opt_type_to_string(opts[id].type)); + return 0; + } + return opts[id].val.number; +} + +bool _dp_opt_get_bool(struct dp_option *opts, + int id, const char *location) +{ + if (opts[id].type != DP_OPT_BOOL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "[%s] Requested type 'Boolean' for option '%s'" + " but value is of type '%s'!\n", + location, opts[id].opt_name, + dp_opt_type_to_string(opts[id].type)); + return false; + } + return opts[id].val.boolean; +} + +/* Setters */ +int _dp_opt_set_string(struct dp_option *opts, int id, + const char *s, const char *location) +{ + if (opts[id].type != DP_OPT_STRING) { + DEBUG(SSSDBG_FATAL_FAILURE, + "[%s] Requested type 'String' for option '%s'" + " but type is '%s'!\n", + location, opts[id].opt_name, + dp_opt_type_to_string(opts[id].type)); + return EINVAL; + } + + if (opts[id].val.string) { + talloc_zfree(opts[id].val.string); + } + if (s) { + opts[id].val.string = talloc_strdup(opts, s); + if (!opts[id].val.string) { + DEBUG(SSSDBG_FATAL_FAILURE, "talloc_strdup() failed!\n"); + return ENOMEM; + } + } + + return EOK; +} + +int _dp_opt_set_blob(struct dp_option *opts, int id, + struct dp_opt_blob b, const char *location) +{ + if (opts[id].type != DP_OPT_BLOB) { + DEBUG(SSSDBG_FATAL_FAILURE, "[%s] Requested type 'Blob' for option '%s'" + " but type is '%s'!\n", + location, opts[id].opt_name, + dp_opt_type_to_string(opts[id].type)); + return EINVAL; + } + + if (opts[id].val.blob.data) { + talloc_zfree(opts[id].val.blob.data); + opts[id].val.blob.length = 0; + } + if (b.data) { + opts[id].val.blob.data = talloc_memdup(opts, b.data, b.length); + if (!opts[id].val.blob.data) { + DEBUG(SSSDBG_FATAL_FAILURE, "talloc_memdup() failed!\n"); + return ENOMEM; + } + } + opts[id].val.blob.length = b.length; + + return EOK; +} + +int _dp_opt_set_int(struct dp_option *opts, int id, + int i, const char *location) +{ + if (opts[id].type != DP_OPT_NUMBER) { + DEBUG(SSSDBG_FATAL_FAILURE, + "[%s] Requested type 'Number' for option '%s'" + " but type is '%s'!\n", + location, opts[id].opt_name, + dp_opt_type_to_string(opts[id].type)); + return EINVAL; + } + + opts[id].val.number = i; + + return EOK; +} + +int _dp_opt_set_bool(struct dp_option *opts, int id, + bool b, const char *location) +{ + if (opts[id].type != DP_OPT_BOOL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "[%s] Requested type 'Boolean' for option '%s'" + " but type is '%s'!\n", + location, opts[id].opt_name, + dp_opt_type_to_string(opts[id].type)); + return EINVAL; + } + + opts[id].val.boolean = b; + + return EOK; +} + diff --git a/src/providers/data_provider_req.c b/src/providers/data_provider_req.c new file mode 100644 index 0000000..b7f4d15 --- /dev/null +++ b/src/providers/data_provider_req.c @@ -0,0 +1,59 @@ +/* + SSSD + + Data Provider -- backend request + + Copyright (C) Petr Cech 2015 + + 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 3 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; 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, see . +*/ + +#include "providers/data_provider_req.h" + +#define be_req_to_str(be_req_t) #be_req_t + +const char *be_req2str(dbus_uint32_t req_type) +{ + switch (req_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: + return be_req_to_str(BE_REQ_USER); + case BE_REQ_GROUP: + return be_req_to_str(BE_REQ_GROUP); + case BE_REQ_INITGROUPS: + return be_req_to_str(BE_REQ_INITGROUPS); + case BE_REQ_NETGROUP: + return be_req_to_str(BE_REQ_NETGROUP); + case BE_REQ_SERVICES: + return be_req_to_str(BE_REQ_SERVICES); + case BE_REQ_SUDO_FULL: + return be_req_to_str(BE_REQ_SUDO_FULL); + case BE_REQ_SUDO_RULES: + return be_req_to_str(BE_REQ_SUDO_RULES); + case BE_REQ_HOST: + return be_req_to_str(BE_REQ_HOST); + case BE_REQ_IP_NETWORK: + return be_req_to_str(BE_REQ_IP_NETWORK); + case BE_REQ_SUBID_RANGES: + return be_req_to_str(BE_REQ_SUBID_RANGES); + case BE_REQ_BY_SECID: + return be_req_to_str(BE_REQ_BY_SECID); + case BE_REQ_USER_AND_GROUP: + return be_req_to_str(BE_REQ_USER_AND_GROUP); + case BE_REQ_BY_UUID: + return be_req_to_str(BE_REQ_BY_UUID); + case BE_REQ_BY_CERT: + return be_req_to_str(BE_REQ_BY_CERT); + } + return "UNKNOWN_REQ"; +} diff --git a/src/providers/data_provider_req.h b/src/providers/data_provider_req.h new file mode 100644 index 0000000..4c6ab5a --- /dev/null +++ b/src/providers/data_provider_req.h @@ -0,0 +1,54 @@ +/* + SSSD + + Data Provider -- backend request + + Copyright (C) Petr Cech 2015 + + 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 3 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; 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, see . +*/ + +#ifndef __DATA_PROVIDER_REQ__ +#define __DATA_PROVIDER_REQ__ + +#include + +/* When changing these constants, also please change sssd_functions.stp + */ +#define BE_REQ_USER 0x0001 +#define BE_REQ_GROUP 0x0002 +#define BE_REQ_INITGROUPS 0x0003 +#define BE_REQ_NETGROUP 0x0004 +#define BE_REQ_SERVICES 0x0005 +#define BE_REQ_SUDO_FULL 0x0006 +#define BE_REQ_SUDO_RULES 0x0007 +#define BE_REQ_HOST 0x0008 +#define BE_REQ_IP_NETWORK 0x0009 +#define BE_REQ_SUBID_RANGES 0x0010 +#define BE_REQ_BY_SECID 0x0011 +#define BE_REQ_USER_AND_GROUP 0x0012 +#define BE_REQ_BY_UUID 0x0013 +#define BE_REQ_BY_CERT 0x0014 +#define BE_REQ__LAST BE_REQ_BY_CERT /* must be equal to max REQ number */ +#define BE_REQ_TYPE_MASK 0x00FF + +/** + * @brief Convert request type to string for logging purpose. + * + * @param[in] req_type Type of request. + * @return Pointer to string with request type. There could be 'fast' flag. + */ +const char *be_req2str(dbus_uint32_t req_type); + +#endif /* __DATA_PROVIDER_REQ__ */ diff --git a/src/providers/fail_over.c b/src/providers/fail_over.c new file mode 100644 index 0000000..7cb6424 --- /dev/null +++ b/src/providers/fail_over.c @@ -0,0 +1,1861 @@ +/* + SSSD + + Fail over helper functions. + + Authors: + Martin Nagy + Jakub Hrozek + + Copyright (C) Red Hat, Inc 2010 + + 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 3 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; 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, see . +*/ + +#include + +#include +#include +#include +#include +#include + +#include "util/dlinklist.h" +#include "util/refcount.h" +#include "util/util.h" +#include "providers/fail_over.h" +#include "resolv/async_resolv.h" + +#define STATUS_DIFF(p, now) ((now).tv_sec - (p)->last_status_change.tv_sec) +#define SERVER_NAME(s) ((s)->common ? (s)->common->name : "(no name)") + +#define DEFAULT_PORT_STATUS PORT_NEUTRAL +#define DEFAULT_SERVER_STATUS SERVER_NAME_NOT_RESOLVED +#define DEFAULT_SRV_STATUS SRV_NEUTRAL + +enum srv_lookup_status { + SRV_NEUTRAL, /* We didn't try this SRV lookup yet */ + SRV_RESOLVED, /* This SRV lookup is resolved */ + SRV_RESOLVE_ERROR, /* Could not resolve this SRV lookup */ + SRV_EXPIRED /* Need to refresh the SRV query */ +}; + +struct fo_ctx { + struct fo_service *service_list; + struct server_common *server_common_list; + + struct fo_options *opts; + + fo_srv_lookup_plugin_send_t srv_send_fn; + fo_srv_lookup_plugin_recv_t srv_recv_fn; + void *srv_pvt; +}; + +struct fo_service { + struct fo_service *prev; + struct fo_service *next; + + struct fo_ctx *ctx; + char *name; + struct fo_server *active_server; + struct fo_server *last_tried_server; + struct fo_server *server_list; + + /* Function pointed by user_data_cmp returns 0 if user_data is equal + * or nonzero value if not. Set to NULL if no user data comparison + * is needed in fail over duplicate servers detection. + */ + datacmp_fn user_data_cmp; +}; + +struct fo_server { + REFCOUNT_COMMON; + + struct fo_server *prev; + struct fo_server *next; + + bool primary; + void *user_data; + int port; + enum port_status port_status; + struct srv_data *srv_data; + struct fo_service *service; + struct timeval last_status_change; + struct server_common *common; + + TALLOC_CTX *fo_internal_owner; +}; + +struct server_common { + REFCOUNT_COMMON; + + struct fo_ctx *ctx; + + struct server_common *prev; + struct server_common *next; + + char *name; + struct resolv_hostent *rhostent; + struct resolve_service_request *request_list; + enum server_status server_status; + struct timeval last_status_change; +}; + +struct srv_data { + char *dns_domain; + char *discovery_domain; + char *sssd_domain; + char *proto; + char *srv; + + struct fo_server *meta; + + int srv_lookup_status; + int ttl; + struct timeval last_status_change; +}; + +struct resolve_service_request { + struct resolve_service_request *prev; + struct resolve_service_request *next; + + struct server_common *server_common; + struct tevent_req *req; + struct tevent_context *ev; +}; + +struct status { + int value; + struct timeval last_change; +}; + +struct fo_ctx * +fo_context_init(TALLOC_CTX *mem_ctx, struct fo_options *opts) +{ + struct fo_ctx *ctx; + + ctx = talloc_zero(mem_ctx, struct fo_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No memory\n"); + return NULL; + } + ctx->opts = talloc_zero(ctx, struct fo_options); + if (ctx->opts == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No memory\n"); + return NULL; + } + + ctx->opts->srv_retry_neg_timeout = opts->srv_retry_neg_timeout; + ctx->opts->retry_timeout = opts->retry_timeout; + ctx->opts->family_order = opts->family_order; + ctx->opts->service_resolv_timeout = opts->service_resolv_timeout; + ctx->opts->use_search_list = opts->use_search_list; + + DEBUG(SSSDBG_TRACE_FUNC, + "Created new fail over context, retry timeout is %"SPRItime"\n", + ctx->opts->retry_timeout); + return ctx; +} + +static const char * +str_port_status(enum port_status status) +{ + switch (status) { + case PORT_NEUTRAL: + return "neutral"; + case PORT_WORKING: + return "working"; + case PORT_NOT_WORKING: + return "not working"; + } + + return "unknown port status"; +} + +static const char * +str_srv_data_status(enum srv_lookup_status status) +{ + switch (status) { + case SRV_NEUTRAL: + return "neutral"; + case SRV_RESOLVED: + return "resolved"; + case SRV_RESOLVE_ERROR: + return "not resolved"; + case SRV_EXPIRED: + return "expired"; + } + + return "unknown SRV lookup status"; +} + +static void dump_srv_data(const struct srv_data *srv_data) +{ + if (srv_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "srv_data is NULL\n"); + return; + } + + DEBUG(SSSDBG_OP_FAILURE, "srv_data: dns_domain [%s] discovery_domain [%s] " + "sssd_domain [%s] proto [%s] srv [%s] " + "srv_lookup_status [%s] ttl [%d] " + "last_status_change [%"SPRItime"]\n", + srv_data->dns_domain == NULL ? "dns_domain is NULL" + : srv_data->dns_domain, + srv_data->discovery_domain == NULL ? "discovery_domain is NULL" + : srv_data->discovery_domain, + srv_data->sssd_domain == NULL ? "sssd_domain is NULL" + : srv_data->sssd_domain, + srv_data->proto == NULL ? "proto is NULL" + : srv_data->proto, + srv_data->srv == NULL ? "srv is NULL" + : srv_data->srv, + str_srv_data_status(srv_data->srv_lookup_status), + srv_data->ttl, srv_data->last_status_change.tv_sec); +} + +void dump_fo_server(const struct fo_server *srv) +{ + DEBUG(SSSDBG_OP_FAILURE, "fo_server: primary [%s] port [%d] " + "port_status [%s] common->name [%s].\n", + srv->primary ? "true" : "false", srv->port, + str_port_status(srv->port_status), + srv->common == NULL ? "common is NULL" + : (srv->common->name == NULL + ? "common->name is NULL" + : srv->common->name)); + dump_srv_data(srv->srv_data); +} + +void dump_fo_server_list(const struct fo_server *srv) +{ + const struct fo_server *s; + + s = srv; + while (s->prev != NULL) { + s = s->prev; + } + + while (s != NULL) { + dump_fo_server(s); + s = s->next; + } +} + +static const char * +str_server_status(enum server_status status) +{ + switch (status) { + case SERVER_NAME_NOT_RESOLVED: + return "name not resolved"; + case SERVER_RESOLVING_NAME: + return "resolving name"; + case SERVER_NAME_RESOLVED: + return "name resolved"; + case SERVER_WORKING: + return "working"; + case SERVER_NOT_WORKING: + return "not working"; + } + + return "unknown server status"; +} + +int fo_is_srv_lookup(struct fo_server *s) +{ + return s && s->srv_data; +} + +static void fo_server_free(struct fo_server *server) +{ + if (server == NULL) { + return; + } + + talloc_free(server->fo_internal_owner); +} + +static struct fo_server * +collapse_srv_lookup(struct fo_server **_server) +{ + struct fo_server *tmp, *meta, *server; + + server = *_server; + meta = server->srv_data->meta; + DEBUG(SSSDBG_CONF_SETTINGS, "Need to refresh SRV lookup for domain %s\n", + meta->srv_data->dns_domain); + + if (server != meta) { + while (server->prev && server->prev->srv_data == meta->srv_data) { + tmp = server->prev; + DLIST_REMOVE(server->service->server_list, tmp); + fo_server_free(tmp); + } + while (server->next && server->next->srv_data == meta->srv_data) { + tmp = server->next; + DLIST_REMOVE(server->service->server_list, tmp); + fo_server_free(tmp); + } + + if (server == server->service->active_server) { + server->service->active_server = NULL; + } + if (server == server->service->last_tried_server) { + server->service->last_tried_server = meta; + } + + /* add back the meta server to denote SRV lookup */ + DLIST_ADD_AFTER(server->service->server_list, meta, server); + DLIST_REMOVE(server->service->server_list, server); + fo_server_free(server); + } + + meta->srv_data->srv_lookup_status = SRV_NEUTRAL; + meta->srv_data->last_status_change.tv_sec = 0; + + *_server = NULL; + + return meta; +} + +static enum srv_lookup_status +get_srv_data_status(struct srv_data *data) +{ + struct timeval tv; + time_t timeout; + + gettimeofday(&tv, NULL); + + /* Determine timeout value based on state of previous lookup. */ + if (data->srv_lookup_status == SRV_RESOLVE_ERROR) { + timeout = data->meta->service->ctx->opts->srv_retry_neg_timeout; + } else { + timeout = data->ttl; + } + + if (STATUS_DIFF(data, tv) > timeout) { + switch(data->srv_lookup_status) { + case SRV_EXPIRED: + case SRV_NEUTRAL: + break; + case SRV_RESOLVED: + data->srv_lookup_status = SRV_EXPIRED; + data->last_status_change.tv_sec = 0; + break; + case SRV_RESOLVE_ERROR: + data->srv_lookup_status = SRV_NEUTRAL; + data->last_status_change.tv_sec = 0; + DEBUG(SSSDBG_TRACE_FUNC, + "Changing state of SRV lookup from 'SRV_RESOLVE_ERROR' to " + "'SRV_NEUTRAL'.\n"); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown state for SRV server!\n"); + } + } + + return data->srv_lookup_status; +} + +static void +set_srv_data_status(struct srv_data *data, enum srv_lookup_status status) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "Marking SRV lookup of service '%s' as '%s'\n", + data->meta->service->name, str_srv_data_status(status)); + + gettimeofday(&data->last_status_change, NULL); + data->srv_lookup_status = status; +} + +/* + * This function will return the status of the server. If the status was + * last updated a long time ago, we will first reset the status. + */ +static enum server_status +get_server_status(struct fo_server *server) +{ + struct timeval tv; + time_t timeout; + + if (server->common == NULL) + return SERVER_NAME_RESOLVED; + + DEBUG(SSSDBG_TRACE_LIBS, + "Status of server '%s' is '%s'\n", SERVER_NAME(server), + str_server_status(server->common->server_status)); + + timeout = server->service->ctx->opts->retry_timeout; + gettimeofday(&tv, NULL); + if (timeout != 0 && server->common->server_status == SERVER_NOT_WORKING) { + if (STATUS_DIFF(server->common, tv) > timeout) { + DEBUG(SSSDBG_CONF_SETTINGS, "Resetting the server status of '%s'\n", + SERVER_NAME(server)); + server->common->server_status = SERVER_NAME_NOT_RESOLVED; + server->common->last_status_change.tv_sec = tv.tv_sec; + } + } + + if (server->common->rhostent && server->common->rhostent->addr_list[0] && + STATUS_DIFF(server->common, tv) > + server->common->rhostent->addr_list[0]->ttl) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Hostname resolution expired, resetting the server " + "status of '%s'\n", SERVER_NAME(server)); + fo_set_server_status(server, SERVER_NAME_NOT_RESOLVED); + } + + return server->common->server_status; +} + +/* + * This function will return the status of the service. If the status was + * last updated a long time ago, we will first reset the status. + */ +static enum port_status +get_port_status(struct fo_server *server) +{ + struct timeval tv; + time_t timeout; + + DEBUG(SSSDBG_TRACE_LIBS, + "Port status of port %d for server '%s' is '%s'\n", server->port, + SERVER_NAME(server), str_port_status(server->port_status)); + + if (server->port_status == PORT_NOT_WORKING) { + DEBUG(SSSDBG_MINOR_FAILURE, "SSSD is unable to complete the full " + "connection request, this internal status does not necessarily " + "indicate network port issues.\n"); + } + + timeout = server->service->ctx->opts->retry_timeout; + if (timeout != 0 && server->port_status == PORT_NOT_WORKING) { + gettimeofday(&tv, NULL); + if (STATUS_DIFF(server, tv) > timeout) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Resetting the status of port %d for server '%s'\n", + server->port, SERVER_NAME(server)); + server->port_status = PORT_NEUTRAL; + server->last_status_change.tv_sec = tv.tv_sec; + } + } + + return server->port_status; +} + +static int +server_works(struct fo_server *server) +{ + if (get_server_status(server) == SERVER_NOT_WORKING) + return 0; + + return 1; +} + +static int +service_works(struct fo_server *server) +{ + if (!server_works(server)) + return 0; + if (get_port_status(server) == PORT_NOT_WORKING) + return 0; + + return 1; +} + +static int +service_destructor(struct fo_service *service) +{ + DLIST_REMOVE(service->ctx->service_list, service); + return 0; +} + +int +fo_new_service(struct fo_ctx *ctx, const char *name, + datacmp_fn user_data_cmp, + struct fo_service **_service) +{ + struct fo_service *service; + int ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Creating new service '%s'\n", name); + ret = fo_get_service(ctx, name, &service); + if (ret == EOK) { + DEBUG(SSSDBG_FUNC_DATA, "Service '%s' already exists\n", name); + if (_service) { + *_service = service; + } + return EEXIST; + } else if (ret != ENOENT) { + return ret; + } + + service = talloc_zero(ctx, struct fo_service); + if (service == NULL) + return ENOMEM; + + service->name = talloc_strdup(service, name); + if (service->name == NULL) { + talloc_free(service); + return ENOMEM; + } + + service->user_data_cmp = user_data_cmp; + + service->ctx = ctx; + DLIST_ADD(ctx->service_list, service); + + talloc_set_destructor(service, service_destructor); + if (_service) { + *_service = service; + } + + return EOK; +} + +int +fo_get_service(struct fo_ctx *ctx, const char *name, + struct fo_service **_service) +{ + struct fo_service *service; + + DLIST_FOR_EACH(service, ctx->service_list) { + if (!strcmp(name, service->name)) { + *_service = service; + return EOK; + } + } + + return ENOENT; +} + +static int +get_server_common(TALLOC_CTX *mem_ctx, struct fo_ctx *ctx, const char *name, + struct server_common **_common) +{ + struct server_common *common; + + DLIST_FOR_EACH(common, ctx->server_common_list) { + if (!strcasecmp(name, common->name)) { + *_common = rc_reference(mem_ctx, struct server_common, common); + if (*_common == NULL) + return ENOMEM; + return EOK; + } + } + + return ENOENT; +} + +static int server_common_destructor(void *memptr) +{ + struct server_common *common; + + common = talloc_get_type(memptr, struct server_common); + if (common->request_list) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: pending requests still associated with this server\n"); + return -1; + } + DLIST_REMOVE(common->ctx->server_common_list, common); + + return 0; +} + +static struct server_common * +create_server_common(TALLOC_CTX *mem_ctx, struct fo_ctx *ctx, const char *name) +{ + struct server_common *common; + + common = rc_alloc(mem_ctx, struct server_common); + if (common == NULL) { + return NULL; + } + + common->name = talloc_strdup(common, name); + if (common->name == NULL) { + return NULL; + } + + common->ctx = ctx; + common->prev = NULL; + common->next = NULL; + common->rhostent = NULL; + common->request_list = NULL; + common->server_status = DEFAULT_SERVER_STATUS; + common->last_status_change.tv_sec = 0; + common->last_status_change.tv_usec = 0; + + talloc_set_destructor((TALLOC_CTX *) common, server_common_destructor); + DLIST_ADD_END(ctx->server_common_list, common, struct server_common *); + return common; +} + +static struct fo_server * +fo_server_alloc(struct fo_service *service, int port, + void *user_data, bool primary) +{ + static struct fo_server *server; + TALLOC_CTX *server_owner; + + server_owner = talloc_new(service); + if (server_owner == NULL) { + return NULL; + } + + server = rc_alloc(server_owner, struct fo_server); + if (server == NULL) { + return NULL; + } + + server->fo_internal_owner = server_owner; + + server->common = NULL; + server->next = NULL; + server->prev = NULL; + server->srv_data = NULL; + server->last_status_change.tv_sec = 0; + server->last_status_change.tv_usec = 0; + + server->port = port; + server->user_data = user_data; + server->service = service; + server->port_status = DEFAULT_PORT_STATUS; + server->primary = primary; + + return server; +} + +int +fo_add_srv_server(struct fo_service *service, const char *srv, + const char *discovery_domain, const char *sssd_domain, + const char *proto, void *user_data) +{ + struct fo_server *server; + + DEBUG(SSSDBG_TRACE_FUNC, + "Adding new SRV server to service '%s' using '%s'.\n", + service->name, proto); + + DLIST_FOR_EACH(server, service->server_list) { + /* Compare user data only if user_data_cmp and both arguments + * are not NULL. + */ + if (server->service->user_data_cmp && user_data && server->user_data) { + if (server->service->user_data_cmp(server->user_data, user_data)) { + continue; + } + } + + if (fo_is_srv_lookup(server)) { + if (((discovery_domain == NULL && + server->srv_data->dns_domain == NULL) || + (discovery_domain != NULL && + server->srv_data->dns_domain != NULL && + strcasecmp(server->srv_data->dns_domain, discovery_domain) == 0)) && + strcasecmp(server->srv_data->proto, proto) == 0) { + return EEXIST; + } + } + } + + /* SRV servers are always primary */ + server = fo_server_alloc(service, 0, user_data, true); + if (server == NULL) { + return ENOMEM; + } + + /* add the SRV-specific data */ + server->srv_data = talloc_zero(service, struct srv_data); + if (server->srv_data == NULL) + return ENOMEM; + + server->srv_data->proto = talloc_strdup(server->srv_data, proto); + server->srv_data->srv = talloc_strdup(server->srv_data, srv); + if (server->srv_data->proto == NULL || + server->srv_data->srv == NULL) + return ENOMEM; + + if (discovery_domain) { + server->srv_data->discovery_domain = talloc_strdup(server->srv_data, + discovery_domain); + if (server->srv_data->discovery_domain == NULL) + return ENOMEM; + server->srv_data->dns_domain = talloc_strdup(server->srv_data, + discovery_domain); + if (server->srv_data->dns_domain == NULL) + return ENOMEM; + } + + server->srv_data->sssd_domain = + talloc_strdup(server->srv_data, sssd_domain); + if (server->srv_data->sssd_domain == NULL) + return ENOMEM; + + server->srv_data->meta = server; + server->srv_data->srv_lookup_status = DEFAULT_SRV_STATUS; + server->srv_data->last_status_change.tv_sec = 0; + + DLIST_ADD_END(service->server_list, server, struct fo_server *); + return EOK; +} + +static struct fo_server * +create_fo_server(struct fo_service *service, const char *name, + int port, void *user_data, bool primary) +{ + struct fo_server *server; + int ret; + + server = fo_server_alloc(service, port, user_data, primary); + if (server == NULL) + return NULL; + + server->port = port; + server->user_data = user_data; + server->service = service; + server->port_status = DEFAULT_PORT_STATUS; + server->primary = primary; + + if (name != NULL) { + ret = get_server_common(server, service->ctx, name, &server->common); + if (ret == ENOENT) { + server->common = create_server_common(server, service->ctx, name); + if (server->common == NULL) { + fo_server_free(server); + return NULL; + } + } else if (ret != EOK) { + fo_server_free(server); + return NULL; + } + } + + return server; +} + +int +fo_get_server_count(struct fo_service *service) +{ + struct fo_server *server; + int count = 0; + + DLIST_FOR_EACH(server, service->server_list) { + count++; + } + + return count; +} + +static bool fo_server_match(struct fo_server *server, + const char *name, + int port, + void *user_data) +{ + if (server->port != port) { + return false; + } + + /* Compare user data only if user_data_cmp and both arguments + * are not NULL. + */ + if (server->service->user_data_cmp && server->user_data && user_data) { + if (server->service->user_data_cmp(server->user_data, user_data)) { + return false; + } + } + + if (name == NULL && server->common == NULL) { + return true; + } + + if (name != NULL && + server->common != NULL && server->common->name != NULL) { + if (!strcasecmp(name, server->common->name)) + return true; + } + + return false; +} + +static bool fo_server_cmp(struct fo_server *s1, struct fo_server *s2) +{ + char *name = NULL; + + if (s2->common != NULL) { + name = s2->common->name; + } + + return fo_server_match(s1, name, s2->port, s2->user_data); +} + +static bool fo_server_exists(struct fo_server *list, + const char *name, + int port, + void *user_data) +{ + struct fo_server *server = NULL; + + DLIST_FOR_EACH(server, list) { + if (fo_server_match(server, name, port, user_data)) { + return true; + } + } + + return false; +} + +static errno_t fo_add_server_to_list(struct fo_server **to_list, + struct fo_server *check_list, + struct fo_server *server, + const char *service_name) +{ + const char *debug_name = NULL; + const char *name = NULL; + bool exists; + + if (server->common == NULL || server->common->name == NULL) { + debug_name = "(no name)"; + name = NULL; + } else { + debug_name = server->common->name; + name = server->common->name; + } + + exists = fo_server_exists(check_list, name, server->port, + server->user_data); + + if (exists) { + DEBUG(SSSDBG_TRACE_FUNC, "Server '%s:%d' for service '%s' " + "is already present\n", debug_name, server->port, service_name); + return EEXIST; + } + + DLIST_ADD_END(*to_list, server, struct fo_server *); + + DEBUG(SSSDBG_TRACE_FUNC, "Inserted %s server '%s:%d' to service " + "'%s'\n", (server->primary ? "primary" : "backup"), + debug_name, server->port, service_name); + + return EOK; +} + +static errno_t fo_add_server_list(struct fo_service *service, + struct fo_server *after_server, + struct fo_server_info *servers, + size_t num_servers, + struct srv_data *srv_data, + void *user_data, + bool primary, + struct fo_server **_last_server) +{ + struct fo_server *server = NULL; + struct fo_server *last_server = NULL; + struct fo_server *srv_list = NULL; + size_t i; + errno_t ret; + + for (i = 0; i < num_servers; i++) { + server = create_fo_server(service, servers[i].host, servers[i].port, + user_data, primary); + if (server == NULL) { + return ENOMEM; + } + + server->srv_data = srv_data; + + ret = fo_add_server_to_list(&srv_list, service->server_list, + server, service->name); + if (ret != EOK) { + fo_server_free(server); + continue; + } + + last_server = server; + } + + if (srv_list != NULL) { + DLIST_ADD_LIST_AFTER(service->server_list, after_server, + srv_list, struct fo_server *); + } + + if (_last_server != NULL) { + *_last_server = last_server == NULL ? after_server : last_server; + } + + return EOK; +} + +int +fo_add_server(struct fo_service *service, const char *name, int port, + void *user_data, bool primary) +{ + struct fo_server *server; + errno_t ret; + + server = create_fo_server(service, name, port, user_data, primary); + if (!server) { + return ENOMEM; + } + + ret = fo_add_server_to_list(&service->server_list, service->server_list, + server, service->name); + if (ret != EOK) { + fo_server_free(server); + } + + return ret; +} + +void fo_ref_server(TALLOC_CTX *ref_ctx, + struct fo_server *server) +{ + if (server) { + server = rc_reference(ref_ctx, struct fo_server, server); + } +} + +static int +get_first_server_entity(struct fo_service *service, struct fo_server **_server) +{ + struct fo_server *server; + + /* If we already have a working server, use that one. */ + server = service->active_server; + if (server != NULL) { + if (service_works(server) && fo_is_server_primary(server)) { + goto done; + } + service->active_server = NULL; + } + + /* + * Otherwise iterate through the server list. + */ + + /* First, try primary servers after the last one we tried. + * (only if the last one was primary as well) + */ + if (service->last_tried_server != NULL && + service->last_tried_server->primary) { + if (service->last_tried_server->port_status == PORT_NEUTRAL && + server_works(service->last_tried_server)) { + server = service->last_tried_server; + goto done; + } + + DLIST_FOR_EACH(server, service->last_tried_server->next) { + /* Go only through primary servers */ + if (!server->primary) continue; + + if (service_works(server)) { + goto done; + } + } + } + + /* If none were found, try at the start, primary first */ + DLIST_FOR_EACH(server, service->server_list) { + /* First iterate only over primary servers */ + if (!server->primary) continue; + + if (service_works(server)) { + goto done; + } + if (server == service->last_tried_server) { + break; + } + } + + DLIST_FOR_EACH(server, service->server_list) { + /* Now iterate only over backup servers */ + if (server->primary) continue; + + if (service_works(server)) { + goto done; + } + } + + service->last_tried_server = NULL; + return ENOENT; + +done: + service->last_tried_server = server; + *_server = server; + return EOK; +} + +static int +resolve_service_request_destructor(struct resolve_service_request *request) +{ + DLIST_REMOVE(request->server_common->request_list, request); + return 0; +} + +static int +set_lookup_hook(struct tevent_context *ev, + struct fo_server *server, + struct tevent_req *req) +{ + struct resolve_service_request *request; + + request = talloc(req, struct resolve_service_request); + if (request == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No memory\n"); + talloc_free(request); + return ENOMEM; + } + request->server_common = rc_reference(request, struct server_common, + server->common); + if (request->server_common == NULL) { + talloc_free(request); + return ENOMEM; + } + request->ev = ev; + request->req = req; + DLIST_ADD(server->common->request_list, request); + talloc_set_destructor(request, resolve_service_request_destructor); + + return EOK; +} + + + +/******************************************************************* + * Get server to connect to. * + *******************************************************************/ + +struct resolve_service_state { + struct fo_server *server; + + struct resolv_ctx *resolv; + struct tevent_context *ev; + struct tevent_timer *timeout_handler; + struct fo_ctx *fo_ctx; +}; + +static errno_t fo_resolve_service_activate_timeout(struct tevent_req *req, + struct tevent_context *ev, const unsigned long timeout_seconds); +static void fo_resolve_service_cont(struct tevent_req *subreq); +static void fo_resolve_service_done(struct tevent_req *subreq); +static bool fo_resolve_service_server(struct tevent_req *req); + +/* Forward declarations for SRV resolving */ +static struct tevent_req * +resolve_srv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct resolv_ctx *resolv, struct fo_ctx *ctx, + struct fo_server *server); +static int +resolve_srv_recv(struct tevent_req *req, struct fo_server **server); + +struct tevent_req * +fo_resolve_service_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct resolv_ctx *resolv, struct fo_ctx *ctx, + struct fo_service *service) +{ + int ret; + struct fo_server *server; + struct tevent_req *req; + struct tevent_req *subreq; + struct resolve_service_state *state; + + DEBUG(SSSDBG_CONF_SETTINGS, + "Trying to resolve service '%s'\n", service->name); + req = tevent_req_create(mem_ctx, &state, struct resolve_service_state); + if (req == NULL) + return NULL; + + state->resolv = resolv; + state->ev = ev; + state->fo_ctx = ctx; + + ret = get_first_server_entity(service, &server); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No available servers for service '%s'\n", service->name); + goto done; + } + + /* Activate per-service timeout handler */ + ret = fo_resolve_service_activate_timeout(req, ev, + ctx->opts->service_resolv_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not set service timeout [dns_resolver_timeout]\n"); + goto done; + } + + if (fo_is_srv_lookup(server)) { + /* Don't know the server yet, must do a SRV lookup */ + subreq = resolve_srv_send(state, ev, resolv, + ctx, server); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, + fo_resolve_service_cont, + req); + return req; + } + + /* This is a regular server, just do hostname lookup */ + state->server = server; + if (fo_resolve_service_server(req)) { + tevent_req_post(req, ev); + } + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void set_server_common_status(struct server_common *common, + enum server_status status); + +static void +fo_resolve_service_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + + DEBUG(SSSDBG_MINOR_FAILURE, "Service resolving timeout reached\n"); + tevent_req_error(req, ETIMEDOUT); +} + +static errno_t +fo_resolve_service_activate_timeout(struct tevent_req *req, + struct tevent_context *ev, + const unsigned long timeout_seconds) +{ + struct timeval tv; + struct resolve_service_state *state = tevent_req_data(req, + struct resolve_service_state); + + tv = tevent_timeval_current(); + tv = tevent_timeval_add(&tv, timeout_seconds, 0); + state->timeout_handler = tevent_add_timer(ev, state, tv, + fo_resolve_service_timeout, req); + if (state->timeout_handler == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Resolve timeout [dns_resolver_timeout] set to %lu seconds\n", + timeout_seconds); + return EOK; +} + +/* SRV resolving finished, see if we got server to work with */ +static void +fo_resolve_service_cont(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct resolve_service_state *state = tevent_req_data(req, + struct resolve_service_state); + int ret; + + ret = resolve_srv_recv(subreq, &state->server); + talloc_zfree(subreq); + + /* We will proceed normally on ERR_SRV_DUPLICATES and if the server + * is already being resolved, we hook to that request. */ + if (ret != EOK && ret != ERR_SRV_DUPLICATES) { + tevent_req_error(req, ret); + return; + } + + fo_resolve_service_server(req); +} + +static bool +fo_resolve_service_server(struct tevent_req *req) +{ + struct resolve_service_state *state = tevent_req_data(req, + struct resolve_service_state); + struct tevent_req *subreq; + int ret; + + switch (get_server_status(state->server)) { + case SERVER_NAME_NOT_RESOLVED: /* Request name resolution. */ + subreq = resolv_gethostbyname_send(state->server->common, + state->ev, state->resolv, + state->server->common->name, + state->fo_ctx->opts->family_order, + default_host_dbs); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return true; + } + tevent_req_set_callback(subreq, fo_resolve_service_done, + state->server->common); + fo_set_server_status(state->server, SERVER_RESOLVING_NAME); + /* FALLTHROUGH */ + SSS_ATTRIBUTE_FALLTHROUGH; + case SERVER_RESOLVING_NAME: + /* Name resolution is already under way. Just add ourselves into the + * waiting queue so we get notified after the operation is finished. */ + ret = set_lookup_hook(state->ev, state->server, req); + if (ret != EOK) { + tevent_req_error(req, ret); + return true; + } + break; + default: /* The name is already resolved. Return immediately. */ + tevent_req_done(req); + return true; + } + + return false; +} + +static void +fo_resolve_service_done(struct tevent_req *subreq) +{ + struct server_common *common = tevent_req_callback_data(subreq, + struct server_common); + int resolv_status; + struct resolve_service_request *request; + int ret; + + if (common->rhostent != NULL) { + talloc_zfree(common->rhostent); + } + + ret = resolv_gethostbyname_recv(subreq, common, + &resolv_status, NULL, + &common->rhostent); + talloc_zfree(subreq); + if (ret != EOK) { + if (resolv_status == ARES_EFILE) { + /* resolv_strerror(resolv_status) provided msg from c-ares lib. + * c-ares lib in most distros will default to /etc/hosts for + * file based host resolving */ + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to resolve server '%s': %s [%s]\n", + common->name, + resolv_strerror(resolv_status), + _PATH_HOSTS); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to resolve server '%s': %s\n", + common->name, + resolv_strerror(resolv_status)); + } + /* If the resolver failed to resolve a hostname but did not + * encounter an error, tell the caller to retry another server. + * + * If there are no more servers to try, the next request would + * just shortcut with ENOENT. + */ + if (ret == ENOENT) { + ret = EAGAIN; + } + set_server_common_status(common, SERVER_NOT_WORKING); + } else { + set_server_common_status(common, SERVER_NAME_RESOLVED); + } + + /* Take care of all requests for this server. */ + while ((request = common->request_list) != NULL) { + DLIST_REMOVE(common->request_list, request); + + /* If the request callback decresed refcount on the returned + * server, we would have crashed as common would not be valid + * anymore. Rather schedule the notify for next tev iteration + */ + tevent_req_defer_callback(request->req, request->ev); + + if (ret) { + tevent_req_error(request->req, ret); + } else { + tevent_req_done(request->req); + } + } +} + +int +fo_resolve_service_recv(struct tevent_req *req, + TALLOC_CTX *ref_ctx, + struct fo_server **server) +{ + struct resolve_service_state *state; + + state = tevent_req_data(req, struct resolve_service_state); + + /* always return the server if asked for, otherwise the caller + * cannot mark it as faulty in case we return an error */ + if (server != NULL) { + fo_ref_server(ref_ctx, state->server); + *server = state->server; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/******************************************************************* + * Resolve the server to connect to using a SRV query. * + *******************************************************************/ + +static void resolve_srv_done(struct tevent_req *subreq); + +struct resolve_srv_state { + struct fo_server *meta; + struct fo_service *service; + + struct fo_server *out; + + struct resolv_ctx *resolv; + struct tevent_context *ev; + struct fo_ctx *fo_ctx; +}; + +static struct tevent_req * +resolve_srv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct resolv_ctx *resolv, struct fo_ctx *ctx, + struct fo_server *server) +{ + int ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct resolve_srv_state *state; + int status; + + req = tevent_req_create(mem_ctx, &state, struct resolve_srv_state); + if (req == NULL) + return NULL; + + state->service = server->service; + state->ev = ev; + state->resolv = resolv; + state->fo_ctx = ctx; + state->meta = server->srv_data->meta; + + status = get_srv_data_status(server->srv_data); + DEBUG(SSSDBG_FUNC_DATA, "The status of SRV lookup is %s\n", + str_srv_data_status(status)); + switch(status) { + case SRV_EXPIRED: /* Need a refresh */ + state->meta = collapse_srv_lookup(&server); + /* FALLTHROUGH. + * "server" might be invalid now if the SRV + * query collapsed + * */ + SSS_ATTRIBUTE_FALLTHROUGH; + case SRV_NEUTRAL: /* Request SRV lookup */ + if (server != NULL && server != state->meta) { + /* A server created by expansion of meta server was marked as + * neutral. We have to collapse the servers and issue new + * SRV resolution. */ + state->meta = collapse_srv_lookup(&server); + } + + if (ctx->srv_send_fn == NULL || ctx->srv_recv_fn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "No SRV lookup plugin is set\n"); + ret = ENOTSUP; + goto done; + } + + subreq = ctx->srv_send_fn(state, ev, + state->meta->srv_data->srv, + state->meta->srv_data->proto, + state->meta->srv_data->discovery_domain, + ctx->srv_pvt); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, resolve_srv_done, req); + break; + case SRV_RESOLVE_ERROR: /* query could not be resolved but don't retry yet */ + ret = EIO; + state->out = server; + + /* The port status was reseted to neutral but we still haven't reached + * timeout to try to resolve SRV record again. We will set the port + * status back to not working. */ + fo_set_port_status(state->meta, PORT_NOT_WORKING); + goto done; + case SRV_RESOLVED: /* The query is resolved and valid. Return. */ + state->out = server; + tevent_req_done(req); + tevent_req_post(req, state->ev); + return req; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected status %d for a SRV server\n", status); + ret = EIO; + goto done; + } + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void +resolve_srv_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct resolve_srv_state *state = tevent_req_data(req, + struct resolve_srv_state); + struct fo_server *last_server = NULL; + struct fo_server_info *primary_servers = NULL; + struct fo_server_info *backup_servers = NULL; + size_t num_primary_servers = 0; + size_t num_backup_servers = 0; + char *dns_domain = NULL; + int ret; + uint32_t ttl; + + ret = state->fo_ctx->srv_recv_fn(state, subreq, &dns_domain, &ttl, + &primary_servers, &num_primary_servers, + &backup_servers, &num_backup_servers); + talloc_free(subreq); + switch (ret) { + case EOK: + if ((num_primary_servers == 0 || primary_servers == NULL) + && (num_backup_servers == 0 || backup_servers == NULL)) { + DEBUG(SSSDBG_CRIT_FAILURE, "SRV lookup plugin returned EOK but " + "no servers\n"); + ret = EFAULT; + goto done; + } + + state->meta->srv_data->ttl = ttl; + talloc_zfree(state->meta->srv_data->dns_domain); + state->meta->srv_data->dns_domain = talloc_steal(state->meta->srv_data, + dns_domain); + + last_server = state->meta; + + if (primary_servers != NULL) { + ret = fo_add_server_list(state->service, last_server, + primary_servers, num_primary_servers, + state->meta->srv_data, + state->meta->user_data, + true, &last_server); + if (ret != EOK) { + goto done; + } + } + + if (backup_servers != NULL) { + ret = fo_add_server_list(state->service, last_server, + backup_servers, num_backup_servers, + state->meta->srv_data, + state->meta->user_data, + false, &last_server); + if (ret != EOK) { + goto done; + } + } + + if (last_server == state->meta) { + /* SRV lookup returned only those servers that are already present. + * This may happen only when an ongoing SRV resolution already + * exist. We will return server, but won't set any state. */ + DEBUG(SSSDBG_TRACE_FUNC, "SRV lookup did not return " + "any new server.\n"); + ret = ERR_SRV_DUPLICATES; + + /* Since no new server is returned, state->meta->next is NULL. + * We return last tried server if possible which is server + * from previous resolution of SRV record, and first server + * otherwise. */ + if (state->service->last_tried_server != NULL) { + state->out = state->service->last_tried_server; + goto done; + } + + state->out = state->service->server_list; + goto done; + } + + /* At least one new server was inserted. + * We will return the first new server. */ + if (state->meta->next == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: state->meta->next is NULL\n"); + ret = ERR_INTERNAL; + goto done; + } + + state->out = state->meta->next; + + /* And remove meta server from the server list. It will be + * inserted again during srv collapse. */ + DLIST_REMOVE(state->service->server_list, state->meta); + if (state->service->last_tried_server == state->meta) { + state->service->last_tried_server = state->out; + } + + set_srv_data_status(state->meta->srv_data, SRV_RESOLVED); + ret = EOK; + break; + case ERR_SRV_NOT_FOUND: + /* fall through */ + SSS_ATTRIBUTE_FALLTHROUGH; + case ERR_SRV_LOOKUP_ERROR: + fo_set_port_status(state->meta, PORT_NOT_WORKING); + /* fall through */ + SSS_ATTRIBUTE_FALLTHROUGH; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unable to resolve SRV [%d]: %s\n", + ret, sss_strerror(ret)); + } + +done: + if (ret == ERR_SRV_DUPLICATES) { + tevent_req_error(req, ret); + return; + } else if (ret != EOK) { + state->out = state->meta; + set_srv_data_status(state->meta->srv_data, SRV_RESOLVE_ERROR); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int +resolve_srv_recv(struct tevent_req *req, struct fo_server **server) +{ + struct resolve_srv_state *state = tevent_req_data(req, + struct resolve_srv_state); + + /* always return the server if asked for, otherwise the caller + * cannot mark it as faulty in case we return an error */ + if (server) { + *server = state->out; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/******************************************************************* + * Get Fully Qualified Domain Name of the host machine * + *******************************************************************/ +static void +set_server_common_status(struct server_common *common, + enum server_status status) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "Marking server '%s' as '%s'\n", common->name, + str_server_status(status)); + + common->server_status = status; + gettimeofday(&common->last_status_change, NULL); +} + +void +fo_set_server_status(struct fo_server *server, enum server_status status) +{ + if (server->common == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Bug: Trying to set server status of a name-less server\n"); + return; + } + + set_server_common_status(server->common, status); +} + +void +fo_set_port_status(struct fo_server *server, enum port_status status) +{ + struct fo_server *siter; + + DEBUG(SSSDBG_CONF_SETTINGS, + "Marking port %d of server '%s' as '%s'\n", server->port, + SERVER_NAME(server), str_port_status(status)); + + server->port_status = status; + gettimeofday(&server->last_status_change, NULL); + if (status == PORT_WORKING) { + fo_set_server_status(server, SERVER_WORKING); + server->service->active_server = server; + } + + if (!server->common || !server->common->name) return; + + /* It is possible to introduce duplicates when expanding SRV results + * into fo_server structures. Find the duplicates and set the same + * status */ + DLIST_FOR_EACH(siter, server->service->server_list) { + if (fo_server_cmp(siter, server)) { + DEBUG(SSSDBG_TRACE_FUNC, + "Marking port %d of duplicate server '%s' as '%s'\n", + siter->port, SERVER_NAME(siter), + str_port_status(status)); + siter->port_status = status; + gettimeofday(&siter->last_status_change, NULL); + } + } +} + +struct fo_server *fo_get_active_server(struct fo_service *service) +{ + return service->active_server; +} + +void fo_try_next_server(struct fo_service *service) +{ + struct fo_server *server; + + if (!service) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: No service supplied\n"); + return; + } + + server = service->active_server; + if (!server) { + return; + } + + service->active_server = 0; + + if (server->port_status == PORT_WORKING) { + server->port_status = PORT_NOT_WORKING; + } +} + +void * +fo_get_server_user_data(struct fo_server *server) +{ + return server->user_data; +} + +int +fo_get_server_port(struct fo_server *server) +{ + return server->port; +} + +const char * +fo_get_server_name(struct fo_server *server) +{ + if (!server->common) { + return NULL; + } + return server->common->name; +} + +const char *fo_get_server_str_name(struct fo_server *server) +{ + if (!server->common) { + if (fo_is_srv_lookup(server)) { + return "SRV lookup meta-server"; + } + return "unknown name"; + } + + return server->common->name; +} + +struct resolv_hostent * +fo_get_server_hostent(struct fo_server *server) +{ + if (server->common == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Bug: Trying to get hostent from a name-less server\n"); + return NULL; + } + + return server->common->rhostent; +} + +bool +fo_is_server_primary(struct fo_server *server) +{ + return server->primary; +} + +time_t +fo_get_server_hostname_last_change(struct fo_server *server) +{ + if (server->common == NULL) { + return 0; + } + return server->common->last_status_change.tv_sec; +} + +struct fo_server *fo_server_first(struct fo_server *server) +{ + if (!server) return NULL; + + while (server->prev) { server = server->prev; } + return server; +} + +struct fo_server *fo_server_next(struct fo_server *server) +{ + if (!server) return NULL; + + return server->next; +} + +size_t fo_server_count(struct fo_server *server) +{ + struct fo_server *item = fo_server_first(server); + size_t size = 0; + + while (item) { + ++size; + item = item->next; + } + return size; +} + +time_t fo_get_service_retry_timeout(struct fo_service *svc) +{ + if (svc == NULL || svc->ctx == NULL || svc->ctx->opts == NULL) { + return 0; + } + + return svc->ctx->opts->retry_timeout; +} + +bool fo_get_use_search_list(struct fo_server *server) +{ + if ( + server == NULL || + server->service == NULL || + server->service->ctx == NULL || + server->service->ctx->opts == NULL + ) { + return true; + } + + return server->service->ctx->opts->use_search_list; +} + + +void fo_reset_servers(struct fo_service *service) +{ + struct fo_server *server; + + DLIST_FOR_EACH(server, service->server_list) { + if (server->srv_data != NULL) { + set_srv_data_status(server->srv_data, SRV_NEUTRAL); + } + + if (server->common) { + fo_set_server_status(server, SERVER_NAME_NOT_RESOLVED); + } + + fo_set_port_status(server, PORT_NEUTRAL); + } +} + + +void fo_reset_services(struct fo_ctx *fo_ctx) +{ + struct fo_service *service; + + DEBUG(SSSDBG_TRACE_LIBS, + "Resetting all servers in all services\n"); + + DLIST_FOR_EACH(service, fo_ctx->service_list) { + fo_reset_servers(service); + } +} + +bool fo_svc_has_server(struct fo_service *service, struct fo_server *server) +{ + struct fo_server *srv; + + DLIST_FOR_EACH(srv, service->server_list) { + if (srv == server) return true; + } + + return false; +} + +const char **fo_svc_server_list(TALLOC_CTX *mem_ctx, + struct fo_service *service, + size_t *_count) +{ + const char **list; + const char *server; + struct fo_server *srv; + size_t count; + + count = 0; + DLIST_FOR_EACH(srv, service->server_list) { + count++; + } + + list = talloc_zero_array(mem_ctx, const char *, count + 1); + if (list == NULL) { + return NULL; + } + + count = 0; + DLIST_FOR_EACH(srv, service->server_list) { + server = fo_get_server_name(srv); + if (server == NULL) { + /* _srv_ */ + continue; + } + + list[count] = talloc_strdup(list, server); + if (list[count] == NULL) { + talloc_free(list); + return NULL; + } + count++; + } + + if (_count != NULL) { + *_count = count; + } + + return list; +} + +bool fo_set_srv_lookup_plugin(struct fo_ctx *ctx, + fo_srv_lookup_plugin_send_t send_fn, + fo_srv_lookup_plugin_recv_t recv_fn, + void *pvt) +{ + if (ctx == NULL || send_fn == NULL || recv_fn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid parameters\n"); + return false; + } + + if (ctx->srv_send_fn != NULL || ctx->srv_recv_fn != NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "SRV lookup plugin is already set\n"); + return false; + } + + ctx->srv_send_fn = send_fn; + ctx->srv_recv_fn = recv_fn; + ctx->srv_pvt = talloc_steal(ctx, pvt); + + return true; +} diff --git a/src/providers/fail_over.h b/src/providers/fail_over.h new file mode 100644 index 0000000..36021ad --- /dev/null +++ b/src/providers/fail_over.h @@ -0,0 +1,245 @@ +/* + SSSD + + Fail over helper functions. + + Authors: + Martin Nagy + + Copyright (C) Red Hat, Inc 2009 + + 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 3 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; 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, see . +*/ + +#ifndef __FAIL_OVER_H__ +#define __FAIL_OVER_H__ + +#include +#include + +#include "resolv/async_resolv.h" +#include "providers/fail_over_srv.h" + +#define FO_PROTO_TCP "tcp" +#define FO_PROTO_UDP "udp" + +/* Some forward declarations that don't have to do anything with fail over. */ +struct hostent; +struct tevent_context; +struct tevent_req; + +enum port_status { + PORT_NEUTRAL, /* We didn't try this port yet. */ + PORT_WORKING, /* This port was reported to work. */ + PORT_NOT_WORKING /* This port was reported to not work. */ +}; + +enum server_status { + SERVER_NAME_NOT_RESOLVED, /* We didn't yet resolved the host name. */ + SERVER_RESOLVING_NAME, /* Name resolving is in progress. */ + SERVER_NAME_RESOLVED, /* We resolved the host name but didn't try to connect. */ + SERVER_WORKING, /* We successfully connected to the server. */ + SERVER_NOT_WORKING /* We tried and failed to connect to the server. */ +}; + +struct fo_ctx; +struct fo_service; +struct fo_server; + +/* + * Failover settings. + * + * The 'retry_timeout' member specifies the + * duration in seconds of how long a server or port will be considered + * non-working after being marked as such. + * + * The 'service_resolv_timeout' member specifies how long we wait for + * service resolution. When this timeout is reached, the resolve request + * is cancelled with an error + * + * The 'srv_retry_timeout' member specifies how long a SRV lookup + * is considered valid until we ask the server again. + * + * The 'srv_retry_neg_timeout' member specifies how long a SRV lookup + * waits before previously failed lookup is tried again. + * + * The 'use_search_list' member specifies whether DNS lookup should perform + * the search as specified in /etc/resolv.conf or not. + * + * The family_order member specifies the order of address families to + * try when looking up the service. + */ +struct fo_options { + time_t srv_retry_neg_timeout; + time_t retry_timeout; + int service_resolv_timeout; + bool use_search_list; + enum restrict_family family_order; +}; + +void dump_fo_server(const struct fo_server *srv); +void dump_fo_server_list(const struct fo_server *srv); + +/* + * Create a new fail over context based on options passed in the + * opts parameter + */ +struct fo_ctx *fo_context_init(TALLOC_CTX *mem_ctx, + struct fo_options *opts); + +typedef int (*datacmp_fn)(void*, void*); + +/* + * Create a new service structure for 'ctx', saving it to the location pointed + * to by '_service'. The needed memory will be allocated from 'ctx'. + * Service name will be set to 'name'. + * + * Function pointed by user_data_cmp returns 0 if user_data is equal + * or nonzero value if not. Set to NULL if no user data comparison + * is needed in fail over duplicate servers detection. + */ +int fo_new_service(struct fo_ctx *ctx, + const char *name, + datacmp_fn user_data_cmp, + struct fo_service **_service); + +/* + * Look up service named 'name' from the 'ctx' service list. Target of + * '_service' will be set to the service if it was found. + */ +int fo_get_service(struct fo_ctx *ctx, + const char *name, + struct fo_service **_service); + +/* + * Get number of servers registered for the 'service'. + */ +int fo_get_server_count(struct fo_service *service); + +/* + * Adds a server 'name' to the 'service'. Port 'port' will be used for + * connection. If 'name' is NULL, no server resolution will be done. + */ +int fo_add_server(struct fo_service *service, + const char *name, int port, + void *user_data, bool primary); + +int fo_add_srv_server(struct fo_service *service, + const char *srv, + const char *discovery_domain, + const char *sssd_domain, + const char *proto, + void *user_data); + +/* + * Request the first server from the service's list of servers. It is only + * considered if it is not marked as not working (or the retry interval already + * passed). If the server address wasn't resolved yet, it will be done. + */ +struct tevent_req *fo_resolve_service_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resolv_ctx *resolv, + struct fo_ctx *ctx, + struct fo_service *service); + +int fo_resolve_service_recv(struct tevent_req *req, + TALLOC_CTX *ref_ctx, + struct fo_server **server); + + +/* To be used by async consumers of fo_resolve_service. If a server should be returned + * to an outer request, it should be referenced by a memory from that outer request, + * because the failover's server list might change with a subsequent call (see upstream + * bug #2829) + */ +void fo_ref_server(TALLOC_CTX *ref_ctx, struct fo_server *server); + +/* + * Set feedback about 'server'. Caller should use this to indicate a problem + * with the server itself, not only with the service on that server. This + * should be used, for example, when the IP address of the server can't be + * reached. This setting can affect other services as well, since they can + * share the same server. + */ +void fo_set_server_status(struct fo_server *server, + enum server_status status); + +/* + * Set feedback about the port status. This function should be used when + * the server itself is working but the service is not. When status is set + * to PORT_WORKING, 'server' is also marked as an "active server" for its + * service. When the next fo_resolve_service_send() function is called, this + * server will be preferred. This will hold as long as it is not marked as + * not-working. + */ +void fo_set_port_status(struct fo_server *server, + enum port_status status); + +/* + * Instruct fail-over to try next server on the next connect attempt. + * Should be used after connection to service was unexpectedly dropped + * but there is no authoritative information on whether active server is down. + */ +void fo_try_next_server(struct fo_service *service); + +void *fo_get_server_user_data(struct fo_server *server); + +int fo_get_server_port(struct fo_server *server); + +const char *fo_get_server_name(struct fo_server *server); + +const char *fo_get_server_str_name(struct fo_server *server); + +struct resolv_hostent *fo_get_server_hostent(struct fo_server *server); + +bool fo_is_server_primary(struct fo_server *server); + +time_t fo_get_server_hostname_last_change(struct fo_server *server); + +int fo_is_srv_lookup(struct fo_server *s); + +time_t fo_get_service_retry_timeout(struct fo_service *svc); + +bool fo_get_use_search_list(struct fo_server *server); + +void fo_reset_services(struct fo_ctx *fo_ctx); + +void fo_reset_servers(struct fo_service *svc); + +struct fo_server *fo_get_active_server(struct fo_service *service); + +bool fo_svc_has_server(struct fo_service *service, struct fo_server *server); + +const char **fo_svc_server_list(TALLOC_CTX *mem_ctx, + struct fo_service *service, + size_t *_count); + +/* + * Folowing functions allow to iterate trough list of servers. + */ +struct fo_server *fo_server_first(struct fo_server *server); + +struct fo_server *fo_server_next(struct fo_server *server); + +size_t fo_server_count(struct fo_server *server); + +/* + * pvt will be talloc_stealed to ctx + */ +bool fo_set_srv_lookup_plugin(struct fo_ctx *ctx, + fo_srv_lookup_plugin_send_t send_fn, + fo_srv_lookup_plugin_recv_t recv_fn, + void *pvt); + +#endif /* !__FAIL_OVER_H__ */ diff --git a/src/providers/fail_over_srv.c b/src/providers/fail_over_srv.c new file mode 100644 index 0000000..5f474ea --- /dev/null +++ b/src/providers/fail_over_srv.c @@ -0,0 +1,719 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "util/util.h" +#include "resolv/async_resolv.h" +#include "providers/fail_over_srv.h" + +struct fo_discover_srv_state { + char *dns_domain; + struct fo_server_info *servers; + size_t num_servers; + uint32_t ttl; +}; + +static void fo_discover_srv_done(struct tevent_req *subreq); + +struct tevent_req *fo_discover_srv_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resolv_ctx *resolv_ctx, + const char *service, + const char *protocol, + const char **discovery_domains) +{ + struct fo_discover_srv_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct fo_discover_srv_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + subreq = resolv_discover_srv_send(state, ev, resolv_ctx, service, + protocol, discovery_domains); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, fo_discover_srv_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void fo_discover_srv_done(struct tevent_req *subreq) +{ + struct fo_discover_srv_state *state = NULL; + struct tevent_req *req = NULL; + struct ares_srv_reply *reply_list = NULL; + struct ares_srv_reply *record = NULL; + int i; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct fo_discover_srv_state); + + ret = resolv_discover_srv_recv(state, subreq, + &reply_list, &state->ttl, &state->dns_domain); + talloc_zfree(subreq); + if (ret == ENOENT) { + ret = ERR_SRV_NOT_FOUND; + goto done; + } else if (ret == EIO) { + ret = ERR_SRV_LOOKUP_ERROR; + goto done; + } else if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Got answer. Processing...\n"); + + /* sort and store the answer */ + ret = resolv_sort_srv_reply(&reply_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not sort the answers from DNS " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + state->num_servers = 0; + for (record = reply_list; record != NULL; record = record->next) { + state->num_servers++; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Got %zu servers\n", state->num_servers); + + state->servers = talloc_array(state, struct fo_server_info, + state->num_servers); + if (state->servers == NULL) { + ret = ENOMEM; + goto done; + } + + for (record = reply_list, i = 0; + record != NULL; + record = record->next, i++) { + state->servers[i].host = talloc_steal(state->servers, record->host); + state->servers[i].port = record->port; + state->servers[i].priority = record->priority; + } + + talloc_zfree(reply_list); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t fo_discover_srv_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_servers, + size_t *_num_servers) +{ + struct fo_discover_srv_state *state = NULL; + state = tevent_req_data(req, struct fo_discover_srv_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_dns_domain != NULL) { + *_dns_domain = talloc_steal(mem_ctx, state->dns_domain); + } + + if (_servers != NULL) { + *_servers = talloc_steal(mem_ctx, state->servers); + } + + if (_ttl != NULL) { + *_ttl = state->ttl; + } + + if (_num_servers != NULL) { + *_num_servers = state->num_servers; + } + + return EOK; +} + +struct fo_discover_servers_state { + struct tevent_context *ev; + struct resolv_ctx *resolv_ctx; + const char *service; + const char *protocol; + const char *primary_domain; + const char *backup_domain; + + char *dns_domain; + uint32_t ttl; + struct fo_server_info *primary_servers; + size_t num_primary_servers; + struct fo_server_info *backup_servers; + size_t num_backup_servers; +}; + +static void fo_discover_servers_primary_done(struct tevent_req *subreq); +static void fo_discover_servers_backup_done(struct tevent_req *subreq); + +struct tevent_req *fo_discover_servers_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resolv_ctx *resolv_ctx, + const char *service, + const char *protocol, + const char *primary_domain, + const char *backup_domain) +{ + struct fo_discover_servers_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + const char **domains = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct fo_discover_servers_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (primary_domain == NULL) { + if (backup_domain == NULL) { + state->primary_servers = NULL; + state->num_primary_servers = 0; + state->backup_servers = NULL; + state->num_backup_servers = 0; + state->dns_domain = NULL; + state->ttl = 0; + + ret = EOK; + goto immediately; + } else { + primary_domain = backup_domain; + backup_domain = NULL; + } + } + + state->ev = ev; + state->resolv_ctx = resolv_ctx; + + state->service = talloc_strdup(state, service); + if (state->service == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->protocol = talloc_strdup(state, protocol); + if (state->protocol == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->primary_domain = talloc_strdup(state, primary_domain); + if (state->primary_domain == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->backup_domain = talloc_strdup(state, backup_domain); + if (state->backup_domain == NULL && backup_domain != NULL) { + ret = ENOMEM; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Looking up primary servers\n"); + + domains = talloc_zero_array(state, const char *, 3); + if (domains == NULL) { + ret = ENOMEM; + goto immediately; + } + + domains[0] = state->primary_domain; + domains[1] = state->backup_domain; + + subreq = fo_discover_srv_send(state, ev, resolv_ctx, + state->service, state->protocol, domains); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, fo_discover_servers_primary_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void fo_discover_servers_primary_done(struct tevent_req *subreq) +{ + struct fo_discover_servers_state *state = NULL; + struct tevent_req *req = NULL; + const char **domains = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct fo_discover_servers_state); + + ret = fo_discover_srv_recv(state, subreq, + &state->dns_domain, + &state->ttl, + &state->primary_servers, + &state->num_primary_servers); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve primary servers " + "[%d]: %s\n", ret, sss_strerror(ret)); + if (ret != ERR_SRV_NOT_FOUND && ret != ERR_SRV_LOOKUP_ERROR) { + /* abort on system error */ + goto done; + } + } + + if (state->backup_domain == NULL) { + /* if there is no backup domain, we are done */ + DEBUG(SSSDBG_TRACE_FUNC, "No backup domain specified\n"); + goto done; + } + + if (state->dns_domain != NULL + && strcasecmp(state->dns_domain, state->backup_domain) == 0) { + /* If there was no error and dns_domain is the same as backup domain, + * it means that we were unable to resolve SRV in primary domain, but + * SRV from backup domain was resolved and those servers are considered + * to be primary. We are done. */ + state->backup_servers = NULL; + state->num_backup_servers = 0; + + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Looking up backup servers\n"); + + domains = talloc_zero_array(state, const char *, 2); + if (domains == NULL) { + ret = ENOMEM; + goto done; + } + + domains[0] = state->backup_domain; + + subreq = fo_discover_srv_send(state, state->ev, state->resolv_ctx, + state->service, state->protocol, domains); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, fo_discover_servers_backup_done, req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static void fo_discover_servers_backup_done(struct tevent_req *subreq) +{ + struct fo_discover_servers_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct fo_discover_servers_state); + + ret = fo_discover_srv_recv(state, subreq, NULL, + NULL, &state->backup_servers, + &state->num_backup_servers); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to retrieve backup servers " + "[%d]: %s\n", ret, sss_strerror(ret)); + if (ret == ERR_SRV_NOT_FOUND || ret == ERR_SRV_LOOKUP_ERROR) { + /* we have successfully fetched primary servers, so we will + * finish the request normally on non system error */ + ret = EOK; + } + } + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t fo_discover_servers_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_primary_servers, + size_t *_num_primary_servers, + struct fo_server_info **_backup_servers, + size_t *_num_backup_servers) +{ + struct fo_discover_servers_state *state = NULL; + state = tevent_req_data(req, struct fo_discover_servers_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_primary_servers) { + *_primary_servers = talloc_steal(mem_ctx, state->primary_servers); + } + + if (_num_primary_servers) { + *_num_primary_servers = state->num_primary_servers; + } + + if (_backup_servers) { + *_backup_servers = talloc_steal(mem_ctx, state->backup_servers); + } + + if (_num_backup_servers) { + *_num_backup_servers = state->num_backup_servers; + } + + if (_dns_domain) { + *_dns_domain = talloc_steal(mem_ctx, state->dns_domain); + } + + if (_ttl) { + *_ttl = state->ttl; + } + + + return EOK; +} + +struct fo_resolve_srv_dns_ctx { + struct resolv_ctx *resolv_ctx; + enum restrict_family family_order; + enum host_database *host_dbs; + char *hostname; + char *sssd_domain; + char *detected_domain; +}; + +struct fo_resolve_srv_dns_state { + struct tevent_context *ev; + struct fo_resolve_srv_dns_ctx *ctx; + const char *service; + const char *protocol; + const char *discovery_domain; + + char *dns_domain; + uint32_t ttl; + struct fo_server_info *servers; + size_t num_servers; +}; + +static void fo_resolve_srv_dns_domain_done(struct tevent_req *subreq); +static errno_t fo_resolve_srv_dns_discover(struct tevent_req *req); +static void fo_resolve_srv_dns_done(struct tevent_req *subreq); + +struct fo_resolve_srv_dns_ctx * +fo_resolve_srv_dns_ctx_init(TALLOC_CTX *mem_ctx, + struct resolv_ctx *resolv_ctx, + enum restrict_family family_order, + enum host_database *host_dbs, + const char *hostname, + const char *sssd_domain) +{ + struct fo_resolve_srv_dns_ctx *ctx = NULL; + + ctx = talloc_zero(mem_ctx, struct fo_resolve_srv_dns_ctx); + if (ctx == NULL) { + return NULL; + } + + ctx->resolv_ctx = resolv_ctx; + ctx->family_order = family_order; + ctx->host_dbs = host_dbs; + + ctx->hostname = talloc_strdup(ctx, hostname); + if (ctx->hostname == NULL) { + goto fail; + } + + ctx->sssd_domain = talloc_strdup(ctx, sssd_domain); + if (ctx->sssd_domain == NULL) { + goto fail; + } + + return ctx; + +fail: + talloc_free(ctx); + return NULL; +} + +struct tevent_req *fo_resolve_srv_dns_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *service, + const char *protocol, + const char *discovery_domain, + void *pvt) +{ + struct fo_resolve_srv_dns_state *state = NULL; + struct fo_resolve_srv_dns_ctx *ctx = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct fo_resolve_srv_dns_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ctx = talloc_get_type(pvt, struct fo_resolve_srv_dns_ctx); + if (ctx == NULL) { + ret = EINVAL; + goto immediately; + } + + state->ev = ev; + state->ctx = ctx; + state->service = service; + state->protocol = protocol; + + if (discovery_domain == NULL) { + state->discovery_domain = NULL; + } else { + state->discovery_domain = discovery_domain; + } + + if (discovery_domain == NULL && ctx->detected_domain == NULL) { + /* we will try to detect proper discovery domain */ + subreq = resolv_get_domain_send(state, state->ev, ctx->resolv_ctx, + ctx->hostname, ctx->host_dbs, + ctx->family_order); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, fo_resolve_srv_dns_domain_done, req); + } else { + /* we will use either provided or previously detected + * discovery domain */ + ret = fo_resolve_srv_dns_discover(req); + if (ret != EAGAIN) { + goto immediately; + } + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void fo_resolve_srv_dns_domain_done(struct tevent_req *subreq) +{ + struct fo_resolve_srv_dns_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct fo_resolve_srv_dns_state); + + ret = resolv_get_domain_recv(state->ctx, subreq, + &state->ctx->detected_domain); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = fo_resolve_srv_dns_discover(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t fo_resolve_srv_dns_discover(struct tevent_req *req) +{ + struct fo_resolve_srv_dns_state *state = NULL; + struct fo_resolve_srv_dns_ctx *ctx = NULL; + struct tevent_req *subreq = NULL; + const char **domains = NULL; + errno_t ret; + + state = tevent_req_data(req, struct fo_resolve_srv_dns_state); + ctx = state->ctx; + + domains = talloc_zero_array(state, const char *, 3); + if (domains == NULL) { + ret = ENOMEM; + goto done; + } + + if (state->discovery_domain == NULL) { + /* we will use detected domain with SSSD domain as fallback */ + domains[0] = talloc_strdup(domains, ctx->detected_domain); + if (domains[0] == NULL) { + ret = ENOMEM; + goto done; + } + + if (strcasecmp(ctx->detected_domain, ctx->sssd_domain) != 0) { + domains[1] = talloc_strdup(domains, ctx->sssd_domain); + if (domains[1] == NULL) { + ret = ENOMEM; + goto done; + } + } + } else { + /* We will use only discovery domain that was provided via plugin + * interface. We don't have to dup here because it is already on + * state. */ + domains[0] = state->discovery_domain; + } + + subreq = fo_discover_srv_send(state, state->ev, ctx->resolv_ctx, + state->service, state->protocol, domains); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, fo_resolve_srv_dns_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + talloc_free(domains); + } + + return ret; +} + +static void fo_resolve_srv_dns_done(struct tevent_req *subreq) +{ + struct fo_resolve_srv_dns_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct fo_resolve_srv_dns_state); + + ret = fo_discover_srv_recv(state, subreq, + &state->dns_domain, &state->ttl, + &state->servers, &state->num_servers); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t fo_resolve_srv_dns_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_primary_servers, + size_t *_num_primary_servers, + struct fo_server_info **_backup_servers, + size_t *_num_backup_servers) +{ + struct fo_resolve_srv_dns_state *state = NULL; + state = tevent_req_data(req, struct fo_resolve_srv_dns_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_primary_servers) { + *_primary_servers = talloc_steal(mem_ctx, state->servers); + } + + if (_num_primary_servers) { + *_num_primary_servers = state->num_servers; + } + + /* backup servers are not supported by simple srv lookup */ + if (_backup_servers) { + *_backup_servers = NULL; + } + + if (_num_backup_servers) { + *_num_backup_servers = 0; + } + + if (_dns_domain) { + *_dns_domain = talloc_steal(mem_ctx, state->dns_domain); + } + + if (_ttl) { + *_ttl = state->ttl; + } + + return EOK; +} diff --git a/src/providers/fail_over_srv.h b/src/providers/fail_over_srv.h new file mode 100644 index 0000000..fe4088e --- /dev/null +++ b/src/providers/fail_over_srv.h @@ -0,0 +1,133 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef __FAIL_OVER_SRV_H__ +#define __FAIL_OVER_SRV_H__ + +#include +#include + +#include "resolv/async_resolv.h" + +/* SRV lookup plugin interface */ + +struct fo_server_info { + char *host; + int port; + unsigned short priority; +}; + +/* + * If discovery_domain is NULL, it should be detected automatically. + */ +typedef struct tevent_req * +(*fo_srv_lookup_plugin_send_t)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *service, + const char *protocol, + const char *discovery_domain, + void *pvt); + +/* + * Returns: + * EOK - at least one primary or backup server was found + * ERR_SRV_NOT_FOUND - no primary nor backup server found + * ERR_SRV_LOOKUP_ERROR - error communicating with SRV database + * other code - depends on plugin + * + * If EOK is returned: + * - and no primary server is found: + * *_primary_servers = NULL + * *_num_primary_servers = 0 + * - and no backup server is found: + * *_backup_servers = NULL + * *_num_backup_servers = 0 + * - *_dns_domain = DNS domain name where the servers were found + */ +typedef errno_t +(*fo_srv_lookup_plugin_recv_t)(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_primary_servers, + size_t *_num_primary_servers, + struct fo_server_info **_backup_servers, + size_t *_num_backup_servers); + +struct tevent_req *fo_discover_srv_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resolv_ctx *resolv_ctx, + const char *service, + const char *protocol, + const char **discovery_domains); + +errno_t fo_discover_srv_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_servers, + size_t *_num_servers); + +struct tevent_req *fo_discover_servers_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resolv_ctx *resolv_ctx, + const char *service, + const char *protocol, + const char *primary_domain, + const char *backup_domain); + +errno_t fo_discover_servers_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_primary_servers, + size_t *_num_primary_servers, + struct fo_server_info **_backup_servers, + size_t *_num_backup_servers); + +/* Simple SRV lookup plugin */ + +struct fo_resolve_srv_dns_ctx; + +struct fo_resolve_srv_dns_ctx * +fo_resolve_srv_dns_ctx_init(TALLOC_CTX *mem_ctx, + struct resolv_ctx *resolv_ctx, + enum restrict_family family_order, + enum host_database *host_dbs, + const char *hostname, + const char *sssd_domain); + +struct tevent_req *fo_resolve_srv_dns_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *service, + const char *protocol, + const char *discovery_domain, + void *pvt); + +errno_t fo_resolve_srv_dns_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_primary_servers, + size_t *_num_primary_servers, + struct fo_server_info **_backup_servers, + size_t *_num_backup_servers); + +#endif /* __FAIL_OVER_SRV_H__ */ diff --git a/src/providers/files/files_auth.c b/src/providers/files/files_auth.c new file mode 100644 index 0000000..b71de69 --- /dev/null +++ b/src/providers/files/files_auth.c @@ -0,0 +1,69 @@ +/* + SSSD + + files_auth.c - PAM operations on the files provider + + Copyright (C) 2018 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "providers/data_provider/dp.h" +#include "providers/data_provider.h" +#include "providers/files/files_private.h" +#include "util/cert.h" + +struct files_auth_ctx { + struct pam_data *pd; +}; + +struct tevent_req * +files_auth_handler_send(TALLOC_CTX *mem_ctx, + void *unused, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct files_auth_ctx *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct files_auth_ctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + + tevent_req_done(req); + tevent_req_post(req, params->ev); + return req; +} + +errno_t files_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct files_auth_ctx *state = NULL; + + state = tevent_req_data(req, struct files_auth_ctx); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/files/files_certmap.c b/src/providers/files/files_certmap.c new file mode 100644 index 0000000..665279f --- /dev/null +++ b/src/providers/files/files_certmap.c @@ -0,0 +1,183 @@ +/* + SSSD + + files_init.c - Initialization of the files provider + + Copyright (C) 2018 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/files/files_private.h" +#include "util/util.h" +#include "util/cert.h" +#include "lib/certmap/sss_certmap.h" + +struct priv_sss_debug { + int level; +}; + +static void ext_debug(void *private, const char *file, long line, + const char *function, const char *format, ...) +{ + va_list ap; + struct priv_sss_debug *data = private; + int level = SSSDBG_OP_FAILURE; + + if (data != NULL) { + level = data->level; + } + + va_start(ap, format); + sss_vdebug_fn(file, line, function, level, APPEND_LINE_FEED, format, ap); + va_end(ap); +} + +errno_t files_init_certmap(TALLOC_CTX *mem_ctx, struct files_id_ctx *id_ctx) +{ + int ret; + bool hint; + struct certmap_info **certmap_list = NULL; + size_t c; + + ret = sysdb_get_certmap(mem_ctx, id_ctx->be->domain->sysdb, + &certmap_list, &hint); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_certmap failed.\n"); + goto done; + } + + if (certmap_list == NULL || *certmap_list == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No certmap data, nothing to do.\n"); + ret = EOK; + goto done; + } + + ret = sss_certmap_init(mem_ctx, ext_debug, NULL, &id_ctx->sss_certmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + goto done; + } + + for (c = 0; certmap_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Trying to add rule [%s][%d][%s][%s].\n", + certmap_list[c]->name, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule); + + ret = sss_certmap_add_rule(id_ctx->sss_certmap_ctx, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule, + certmap_list[c]->domains); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_certmap_add_rule failed for rule [%s] " + "with error [%d][%s], skipping. " + "Please check for typos and if rule syntax is supported.\n", + certmap_list[c]->name, ret, sss_strerror(ret)); + continue; + } + } + + ret = EOK; + +done: + talloc_free(certmap_list); + + return ret; +} + +errno_t files_map_cert_to_user(struct files_id_ctx *id_ctx, + struct dp_id_data *data) +{ + errno_t ret; + char *filter; + char *user; + struct ldb_message *msg = NULL; + struct sysdb_attrs *attrs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = sss_cert_derb64_to_ldap_filter(tmp_ctx, data->filter_value, "", + id_ctx->sss_certmap_ctx, + id_ctx->domain, &filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter failed.\n"); + goto done; + } + if (filter == NULL || filter[0] != '(' + || filter[strlen(filter) - 1] != ')') { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter returned bad filter [%s].\n", + filter); + ret = EINVAL; + goto done; + } + + filter[strlen(filter) - 1] = '\0'; + user = sss_create_internal_fqname(tmp_ctx, &filter[1], + id_ctx->domain->name); + if (user == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Certificate mapped to user: [%s].\n", user); + + ret = sysdb_search_user_by_name(tmp_ctx, id_ctx->domain, user, NULL, &msg); + if (ret == EOK) { + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_base64_blob(attrs, SYSDB_USER_MAPPED_CERT, + data->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_base64_blob failed.\n"); + goto done; + } + + ret = sysdb_set_entry_attr(id_ctx->domain->sysdb, msg->dn, attrs, + SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_entry_attr failed.\n"); + goto done; + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "Mapped user [%s] not found.\n", user); + ret = EOK; + goto done; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_name failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} diff --git a/src/providers/files/files_id.c b/src/providers/files/files_id.c new file mode 100644 index 0000000..2103478 --- /dev/null +++ b/src/providers/files/files_id.c @@ -0,0 +1,222 @@ +/* + SSSD + + files_id.c - Identity operaions on the files provider + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/data_provider/dp.h" +#include "providers/files/files_private.h" + +struct files_account_info_handler_state { + struct dp_reply_std reply; + + struct files_id_ctx *id_ctx; + struct dp_id_data *data; +}; + +void handle_certmap(struct tevent_req *req) +{ + struct files_account_info_handler_state *state; + int ret; + + state = tevent_req_data(req, struct files_account_info_handler_state); + + ret = files_map_cert_to_user(state->id_ctx, state->data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "files_map_cert_to_user failed\n"); + } + + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +struct tevent_req * +files_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct files_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params) +{ + struct files_account_info_handler_state *state; + struct tevent_req *req; + struct tevent_req **update_req = NULL; + bool needs_update; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct files_account_info_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + state->id_ctx = id_ctx; + + switch (data->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: + if (data->filter_type != BE_FILTER_ENUM) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected user filter type: %d\n", data->filter_type); + ret = EINVAL; + goto immediate; + } + update_req = &id_ctx->users_req; + needs_update = (id_ctx->refresh_ctx != NULL); + break; + case BE_REQ_GROUP: + if (data->filter_type != BE_FILTER_ENUM) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected group filter type: %d\n", data->filter_type); + ret = EINVAL; + goto immediate; + } + update_req = &id_ctx->groups_req; + needs_update = (id_ctx->refresh_ctx != NULL); + break; + case BE_REQ_INITGROUPS: + if (data->filter_type != BE_FILTER_NAME) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected initgr filter type: %d\n", data->filter_type); + ret = EINVAL; + goto immediate; + } + if (strcmp(data->filter_value, DP_REQ_OPT_FILES_INITGR) != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected initgr filter value: %d\n", data->filter_type); + ret = EINVAL; + goto immediate; + } + update_req = &id_ctx->initgroups_req; + needs_update = (id_ctx->refresh_ctx != NULL); + break; + case BE_REQ_BY_CERT: + if (data->filter_type != BE_FILTER_CERT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected filter type for lookup by cert: %d\n", + data->filter_type); + ret = EINVAL; + goto immediate; + } + + if (id_ctx->sss_certmap_ctx == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Certificate mapping not configured.\n"); + ret = EOK; + goto immediate; + } + + /* Refresh is running, we have to wait until it is done */ + if (id_ctx->refresh_ctx != NULL) { + state->data = data; + + ret = sf_add_certmap_req(id_ctx->refresh_ctx, req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add request certmap request list.\n"); + goto immediate; + } + + return req; + } + + /* No refresh is running, we have reply immediately */ + ret = files_map_cert_to_user(id_ctx, data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "files_map_cert_to_user failed\n"); + } + goto immediate; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected entry type: %d\n", data->entry_type & BE_REQ_TYPE_MASK); + ret = EINVAL; + goto immediate; + } + + if (needs_update == false) { + DEBUG(SSSDBG_TRACE_LIBS, "The files domain no longer needs an update\n"); + ret = EOK; + goto immediate; + } + + if (*update_req != NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Received a concurrent update!\n"); + ret = EAGAIN; + goto immediate; + } + + /* id_ctx now must mark the requests as updated when the inotify-induced + * update finishes + */ + *update_req = req; + return req; + +immediate: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + tevent_req_post(req, params->ev); + return req; +} + +static void finish_update_req(struct tevent_req **update_req, + errno_t ret) +{ + if (*update_req == NULL) { + return; + } + + if (ret != EOK) { + tevent_req_error(*update_req, ret); + } else { + tevent_req_done(*update_req); + } + *update_req = NULL; +} + +void files_account_info_finished(struct files_id_ctx *id_ctx, + int req_type, + errno_t ret) +{ + finish_update_req(&id_ctx->users_req, ret); + finish_update_req(&id_ctx->groups_req, ret); + finish_update_req(&id_ctx->initgroups_req, ret); +} + +errno_t files_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct files_account_info_handler_state *state = NULL; + + state = tevent_req_data(req, struct files_account_info_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + return EOK; +} diff --git a/src/providers/files/files_init.c b/src/providers/files/files_init.c new file mode 100644 index 0000000..ab6fa87 --- /dev/null +++ b/src/providers/files/files_init.c @@ -0,0 +1,261 @@ +/* + SSSD + + files_init.c - Initialization of the files provider + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/data_provider/dp.h" +#include "providers/files/files_private.h" +#include "util/util.h" + +#define DEFAULT_PASSWD_FILE "/etc/passwd" +#define DEFAULT_GROUP_FILE "/etc/group" + +static errno_t files_init_file_sources(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + const char ***_passwd_files, + const char ***_group_files) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *conf_passwd_files; + char *conf_group_files; + char **passwd_list = NULL; + char **group_list = NULL; + int num_passwd_files = 0; + int num_group_files = 0; + const char **passwd_files = NULL; + const char **group_files = NULL; + char *dfl_passwd_files = NULL; + char *env_group_files = NULL; + int i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_getenv(tmp_ctx, "SSS_FILES_PASSWD", DEFAULT_PASSWD_FILE, + &dfl_passwd_files); + if (ret == EOK) { + sss_log(SSS_LOG_ALERT, + "Defaulting to %s for the passwd file, " + "this should only be used for testing!\n", + dfl_passwd_files); + } else if (ret != ENOENT) { + sss_log(SSS_LOG_ALERT, "sss_getenv() failed"); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, + "Using passwd file: [%s].\n", + dfl_passwd_files); + + ret = sss_getenv(tmp_ctx, "SSS_FILES_GROUP", DEFAULT_GROUP_FILE, + &env_group_files); + if (ret == EOK) { + sss_log(SSS_LOG_ALERT, + "Defaulting to %s for the group file, " + "this should only be used for testing!\n", + env_group_files); + } else if (ret != ENOENT) { + sss_log(SSS_LOG_ALERT, "sss_getenv() failed"); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, + "Using group file: [%s].\n", + env_group_files); + + ret = confdb_get_string(be_ctx->cdb, tmp_ctx, be_ctx->conf_path, + CONFDB_FILES_PASSWD, dfl_passwd_files, + &conf_passwd_files); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to retrieve confdb passwd files!\n"); + goto done; + } + + ret = confdb_get_string(be_ctx->cdb, tmp_ctx, be_ctx->conf_path, + CONFDB_FILES_GROUP, env_group_files, + &conf_group_files); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to retrieve confdb group files!\n"); + goto done; + } + + ret = split_on_separator(tmp_ctx, conf_passwd_files, ',', true, true, + &passwd_list, &num_passwd_files); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse passwd list!\n"); + goto done; + } + + passwd_files = talloc_zero_array(tmp_ctx, const char *, + num_passwd_files + 1); + if (passwd_files == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_passwd_files; i++) { + DEBUG(SSSDBG_TRACE_FUNC, + "Using passwd file: [%s].\n", passwd_list[i]); + + passwd_files[i] = talloc_strdup(passwd_files, passwd_list[i]); + if (passwd_files[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* Retrieve list of group files */ + ret = split_on_separator(tmp_ctx, conf_group_files, ',', true, true, + &group_list, &num_group_files); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse group files!\n"); + goto done; + } + + group_files = talloc_zero_array(tmp_ctx, const char *, + num_group_files + 1); + if (group_files == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_group_files; i++) { + DEBUG(SSSDBG_TRACE_FUNC, + "Using group file: [%s].\n", group_list[i]); + group_files[i] = talloc_strdup(group_files, group_list[i]); + if (group_files[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + *_passwd_files = talloc_steal(mem_ctx, passwd_files); + *_group_files = talloc_steal(mem_ctx, group_files); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int sssm_files_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data) +{ + struct files_id_ctx *ctx; + errno_t ret; + + ctx = talloc_zero(mem_ctx, struct files_id_ctx); + if (ctx == NULL) { + return ENOMEM; + } + + ctx->be = be_ctx; + ctx->domain = be_ctx->domain; + + ret = files_init_file_sources(ctx, be_ctx, + &ctx->passwd_files, + &ctx->group_files); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot initialize the passwd/group source files\n"); + goto done; + } + + ctx->fctx = sf_init(ctx, be_ctx->ev, + ctx->passwd_files, + ctx->group_files, + ctx); + if (ctx->fctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = confdb_certmap_to_sysdb(be_ctx->cdb, be_ctx->domain, true); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize certificate mapping rules. " + "Authentication with certificates/Smartcards might not work " + "as expected.\n"); + /* not fatal, ignored */ + } else { + ret = files_init_certmap(ctx, ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "files_init_certmap failed. " + "Authentication with certificates/Smartcards might not work " + "as expected.\n"); + /* not fatal, ignored */ + } + } + + *_module_data = ctx; + ret = EOK; +done: + if (ret != EOK) { + talloc_free(ctx); + } + return ret; +} + +int sssm_files_id_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct files_id_ctx *ctx; + + ctx = talloc_get_type(module_data, struct files_id_ctx); + if (ctx == NULL) { + return EINVAL; + } + + dp_set_method(dp_methods, DPM_ACCOUNT_HANDLER, + files_account_info_handler_send, + files_account_info_handler_recv, + ctx, struct files_id_ctx, + struct dp_id_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_ACCT_DOMAIN_HANDLER, + default_account_domain_send, + default_account_domain_recv, + NULL, void, + struct dp_get_acct_domain_data, struct dp_reply_std); + + return EOK; +} + +int sssm_files_auth_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + files_auth_handler_send, files_auth_handler_recv, NULL, void, + struct pam_data, struct pam_data *); + + return EOK; +} diff --git a/src/providers/files/files_ops.c b/src/providers/files/files_ops.c new file mode 100644 index 0000000..556d56d --- /dev/null +++ b/src/providers/files/files_ops.c @@ -0,0 +1,1484 @@ +/* + SSSD + + Files provider operations + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ +#include + +#include "config.h" + +#include "providers/files/files_private.h" +#include "db/sysdb.h" +#include "util/inotify.h" +#include "util/util.h" +#include "providers/data_provider/dp_iface.h" + +/* When changing this constant, make sure to also adjust the files integration + * test for reallocation branch + */ +#define FILES_REALLOC_CHUNK 64 + +#define PWD_MAXSIZE 1024 +#define GRP_MAXSIZE 2048 + +#define SF_UPDATE_PASSWD 1<<0 +#define SF_UPDATE_GROUP 1<<1 +#define SF_UPDATE_BOTH (SF_UPDATE_PASSWD | SF_UPDATE_GROUP) +#define SF_UPDATE_IMMEDIATE 1<<2 + +struct files_ctx { + struct files_ops_ctx *ops; +}; + +static errno_t enum_files_users(TALLOC_CTX *mem_ctx, + const char *passwd_file, + struct passwd ***_users) +{ + errno_t ret, close_ret; + struct passwd *pwd_iter = NULL; + struct passwd *pwd = NULL; + struct passwd **users = NULL; + FILE *pwd_handle = NULL; + size_t n_users = 0; + + pwd_handle = fopen(passwd_file, "r"); + if (pwd_handle == NULL) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot open passwd file %s [%d]\n", + passwd_file, ret); + goto done; + } + + users = talloc_zero_array(mem_ctx, struct passwd *, + FILES_REALLOC_CHUNK); + if (users == NULL) { + ret = ENOMEM; + goto done; + } + + while ((pwd_iter = fgetpwent(pwd_handle)) != NULL) { + /* FIXME - we might want to support paging of sorts to avoid allocating + * all users atop a memory context or only return users that differ from + * the local storage as a diff to minimize memory spikes + */ + DEBUG(SSSDBG_TRACE_LIBS, + "User found (%s, %s, %"SPRIuid", %"SPRIgid", %s, %s, %s)\n", + pwd_iter->pw_name, pwd_iter->pw_passwd, + pwd_iter->pw_uid, pwd_iter->pw_gid, + pwd_iter->pw_gecos, pwd_iter->pw_dir, + pwd_iter->pw_shell); + + pwd = talloc_zero(users, struct passwd); + if (pwd == NULL) { + ret = ENOMEM; + goto done; + } + + pwd->pw_uid = pwd_iter->pw_uid; + pwd->pw_gid = pwd_iter->pw_gid; + + pwd->pw_name = talloc_strdup(pwd, pwd_iter->pw_name); + if (pwd->pw_name == NULL) { + /* We only check pw_name here on purpose to allow broken + * records to be optionally rejected when saving them + * or fallback values to be used. + */ + ret = ENOMEM; + goto done; + } + + pwd->pw_dir = talloc_strdup(pwd, pwd_iter->pw_dir); + pwd->pw_gecos = talloc_strdup(pwd, pwd_iter->pw_gecos); + pwd->pw_shell = talloc_strdup(pwd, pwd_iter->pw_shell); + pwd->pw_passwd = talloc_strdup(pwd, pwd_iter->pw_passwd); + + users[n_users] = pwd; + n_users++; + if (n_users % FILES_REALLOC_CHUNK == 0) { + users = talloc_realloc(mem_ctx, + users, + struct passwd *, + talloc_array_length(users) + FILES_REALLOC_CHUNK); + if (users == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + ret = EOK; + users[n_users] = NULL; + *_users = users; +done: + if (ret != EOK) { + talloc_free(users); + } + + if (pwd_handle) { + close_ret = fclose(pwd_handle); + if (close_ret != 0) { + close_ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot close passwd file %s [%d]\n", + passwd_file, close_ret); + } + } + return ret; +} + +static errno_t enum_files_groups(TALLOC_CTX *mem_ctx, + const char *group_file, + struct group ***_groups) +{ + errno_t ret, close_ret; + struct group *grp_iter = NULL; + struct group *grp = NULL; + struct group **groups = NULL; + size_t n_groups = 0; + FILE *grp_handle = NULL; + + grp_handle = fopen(group_file, "r"); + if (grp_handle == NULL) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot open group file %s [%d]\n", + group_file, ret); + goto done; + } + + groups = talloc_zero_array(mem_ctx, struct group *, + FILES_REALLOC_CHUNK); + if (groups == NULL) { + ret = ENOMEM; + goto done; + } + + while ((grp_iter = fgetgrent(grp_handle)) != NULL) { + DEBUG(SSSDBG_TRACE_LIBS, + "Group found (%s, %"SPRIgid")\n", + grp_iter->gr_name, grp_iter->gr_gid); + + grp = talloc_zero(groups, struct group); + if (grp == NULL) { + ret = ENOMEM; + goto done; + } + + grp->gr_gid = grp_iter->gr_gid; + grp->gr_name = talloc_strdup(grp, grp_iter->gr_name); + if (grp->gr_name == NULL) { + /* We only check gr_name here on purpose to allow broken + * records to be optionally rejected when saving them + * or fallback values to be used. + */ + ret = ENOMEM; + goto done; + } + grp->gr_passwd = talloc_strdup(grp, grp_iter->gr_passwd); + + if (grp_iter->gr_mem != NULL) { + size_t nmem; + + for (nmem = 0; grp_iter->gr_mem[nmem] != NULL; nmem++); + + grp->gr_mem = talloc_zero_array(grp, char *, nmem + 1); + if (grp->gr_mem == NULL) { + ret = ENOMEM; + goto done; + } + + for (nmem = 0; grp_iter->gr_mem[nmem] != NULL; nmem++) { + grp->gr_mem[nmem] = talloc_strdup(grp, grp_iter->gr_mem[nmem]); + if (grp->gr_mem[nmem] == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + groups[n_groups] = grp; + n_groups++; + if (n_groups % FILES_REALLOC_CHUNK == 0) { + groups = talloc_realloc(mem_ctx, + groups, + struct group *, + talloc_array_length(groups) + FILES_REALLOC_CHUNK); + if (groups == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + ret = EOK; + groups[n_groups] = NULL; + *_groups = groups; +done: + if (ret != EOK) { + talloc_free(groups); + } + + if (grp_handle) { + close_ret = fclose(grp_handle); + if (close_ret != 0) { + close_ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot close group file %s [%d]\n", + group_file, close_ret); + } + } + return ret; +} + +static errno_t delete_all_users(struct sss_domain_info *dom) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *base_dn; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + base_dn = sysdb_user_base_dn(tmp_ctx, dom); + if (base_dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_delete_recursive(dom->sysdb, base_dn, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to delete users subtree [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t save_file_user(struct files_id_ctx *id_ctx, + struct passwd *pw) +{ + errno_t ret; + char *fqname; + TALLOC_CTX *tmp_ctx = NULL; + const char *shell; + const char *gecos; + struct sysdb_attrs *attrs = NULL; + + if (strcmp(pw->pw_name, "root") == 0 + || pw->pw_uid == 0 + || pw->pw_gid == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping %s\n", pw->pw_name); + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + fqname = sss_create_internal_fqname(tmp_ctx, pw->pw_name, + id_ctx->domain->name); + if (fqname == NULL) { + ret = ENOMEM; + goto done; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + if (pw->pw_shell && pw->pw_shell[0] != '\0') { + shell = pw->pw_shell; + } else { + shell = NULL; + } + + if (pw->pw_gecos && pw->pw_gecos[0] != '\0') { + gecos = pw->pw_gecos; + } else { + gecos = NULL; + } + + /* FIXME - optimize later */ + ret = sysdb_store_user(id_ctx->domain, + fqname, + pw->pw_passwd, + pw->pw_uid, + pw->pw_gid, + gecos, + pw->pw_dir, + shell, + NULL, attrs, + NULL, 0, 0); + if (ret != EOK) { + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t refresh_override_attrs(struct files_id_ctx *id_ctx, + enum sysdb_member_type type) +{ + const char *override_attrs[] = { SYSDB_OVERRIDE_OBJECT_DN, + NULL}; + struct ldb_dn *base_dn; + size_t count; + struct ldb_message **msgs; + struct ldb_message *msg = NULL; + struct ldb_context *ldb_ctx; + size_t c; + TALLOC_CTX *tmp_ctx; + int ret; + const char *filter; + + ldb_ctx = sysdb_ctx_get_ldb(id_ctx->domain->sysdb); + if (ldb_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing ldb_context.\n"); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = talloc_asprintf(tmp_ctx, "%s=%s", SYSDB_OBJECTCLASS, + type == SYSDB_MEMBER_USER ? + SYSDB_OVERRIDE_USER_CLASS : + SYSDB_OVERRIDE_GROUP_CLASS ); + if (filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + base_dn = ldb_dn_new(tmp_ctx, ldb_ctx, SYSDB_TMPL_VIEW_BASE); + if (base_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, id_ctx->domain->sysdb, base_dn, + LDB_SCOPE_SUBTREE, filter, + override_attrs, &count, &msgs); + if (ret != EOK) { + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "No overrides, nothing to do.\n"); + ret = EOK; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_entry failed.\n"); + } + goto done; + } + + for (c = 0; c < count; c++) { + talloc_free(msg); + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + ret = ENOMEM; + goto done; + } + + msg->dn = ldb_msg_find_attr_as_dn(ldb_ctx, tmp_ctx, msgs[c], + SYSDB_OVERRIDE_OBJECT_DN); + if (msg->dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get object DN, skipping.\n"); + continue; + } + + ret = ldb_msg_add_empty(msg, SYSDB_OVERRIDE_DN, LDB_FLAG_MOD_ADD, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_msg_add_empty failed.\n"); + continue; + } + + ret = ldb_msg_add_string(msg, SYSDB_OVERRIDE_DN, + ldb_dn_get_linearized(msgs[c]->dn)); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_msg_add_string failed.\n"); + continue; + } + + ret = ldb_modify(ldb_ctx, msg); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to store override DN: %s(%d)[%s], skipping.\n", + ldb_strerror(ret), ret, ldb_errstring(ldb_ctx)); + continue; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t sf_enum_groups(struct files_id_ctx *id_ctx, + struct group **groups, size_t start, size_t size); + +static errno_t sf_enum_users(struct files_id_ctx *id_ctx, struct passwd **users, + size_t start, size_t size) +{ + errno_t ret; + size_t i; + + for (i = start; i < (start + size) && users[i] != NULL; i++) { + ret = save_file_user(id_ctx, users[i]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot save user %s: [%d]: %s\n", + users[i]->pw_name, ret, sss_strerror(ret)); + continue; + } + } + + if (users[i] == NULL) { + ret = refresh_override_attrs(id_ctx, SYSDB_MEMBER_USER); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to refresh override attributes, " + "override values might not be available.\n"); + } + + ret = EOK; + } else { + ret = EAGAIN; + } + + return ret; +} + +static const char **get_cached_user_names(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom) +{ + errno_t ret; + struct ldb_result *res = NULL; + const char **user_names = NULL; + unsigned c = 0; + + ret = sysdb_enumpwent(mem_ctx, dom, &res); + if (ret != EOK) { + goto done; + } + + user_names = talloc_zero_array(mem_ctx, const char *, res->count + 1); + if (user_names == NULL) { + goto done; + } + + for (unsigned i = 0; i < res->count; i++) { + user_names[c] = ldb_msg_find_attr_as_string(res->msgs[i], + SYSDB_NAME, + NULL); + if (user_names[c] == NULL) { + continue; + } + c++; + } + +done: + /* Don't free res and keep it around to avoid duplicating the names */ + return user_names; +} + +static errno_t delete_all_groups(struct sss_domain_info *dom) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *base_dn; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + base_dn = sysdb_group_base_dn(tmp_ctx, dom); + if (base_dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_delete_recursive(dom->sysdb, base_dn, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to delete groups subtree [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t save_file_group(struct files_id_ctx *id_ctx, + struct group *grp, + const char **cached_users) +{ + errno_t ret; + char *fqname; + struct sysdb_attrs *attrs = NULL; + TALLOC_CTX *tmp_ctx = NULL; + char **fq_gr_files_mem; + const char **fq_gr_mem; + unsigned mi = 0; + + if (strcmp(grp->gr_name, "root") == 0 + || grp->gr_gid == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping %s\n", grp->gr_name); + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + fqname = sss_create_internal_fqname(tmp_ctx, grp->gr_name, + id_ctx->domain->name); + if (fqname == NULL) { + ret = ENOMEM; + goto done; + + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + if (grp->gr_mem && grp->gr_mem[0]) { + fq_gr_files_mem = sss_create_internal_fqname_list( + tmp_ctx, + (const char *const*) grp->gr_mem, + id_ctx->domain->name); + if (fq_gr_files_mem == NULL) { + ret = ENOMEM; + goto done; + } + + fq_gr_mem = talloc_zero_array(tmp_ctx, const char *, + talloc_array_length(fq_gr_files_mem)); + if (fq_gr_mem == NULL) { + ret = ENOMEM; + goto done; + } + + for (unsigned i=0; fq_gr_files_mem[i] != NULL; i++) { + if (string_in_list(fq_gr_files_mem[i], + discard_const(cached_users), + true)) { + fq_gr_mem[mi] = fq_gr_files_mem[i]; + mi++; + + DEBUG(SSSDBG_TRACE_LIBS, + "User %s is cached, will become a member of %s\n", + fq_gr_files_mem[i], grp->gr_name); + } else { + ret = sysdb_attrs_add_string(attrs, + SYSDB_GHOST, + fq_gr_files_mem[i]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot add ghost %s for group %s\n", + fq_gr_files_mem[i], fqname); + continue; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "User %s is not cached, will become a ghost of %s\n", + fq_gr_files_mem[i], grp->gr_name); + } + } + + if (fq_gr_mem != NULL && fq_gr_mem[0] != NULL) { + ret = sysdb_attrs_users_from_str_list( + attrs, SYSDB_MEMBER, id_ctx->domain->name, + (const char *const *) fq_gr_mem); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group members\n"); + goto done; + } + } + + } + + ret = sysdb_store_group(id_ctx->domain, fqname, grp->gr_gid, + attrs, 0, 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group to cache\n"); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sf_enum_groups(struct files_id_ctx *id_ctx, + struct group **groups, size_t start, size_t size) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + const char **cached_users = NULL; + size_t i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + cached_users = get_cached_user_names(tmp_ctx, id_ctx->domain); + if (cached_users == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = start; i < (start + size) && groups[i] != NULL; i++) { + ret = save_file_group(id_ctx, groups[i], cached_users); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot save group %s\n", groups[i]->gr_name); + continue; + } + } + + if (groups[i] == NULL) { + ret = refresh_override_attrs(id_ctx, SYSDB_MEMBER_GROUP); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to refresh override attributes, " + "override values might not be available.\n"); + } + + ret = EOK; + } else { + ret = EAGAIN; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +enum update_steps { + WAIT_TO_START_USERS, + DELETE_USERS, + READ_USERS, + SAVE_USERS, + WAIT_TO_START_GROUPS, + DELETE_GROUPS, + READ_GROUPS, + SAVE_GROUPS, + UPDATE_FINISH, + UPDATE_DONE, +}; + +struct certmap_req_list { + struct tevent_req *req; + struct certmap_req_list *prev; + struct certmap_req_list *next; +}; + +struct files_refresh_ctx { + struct timeval start_passwd_refresh; + enum refresh_task_status updating_passwd; + bool passwd_start_again; + struct timeval start_group_refresh; + enum refresh_task_status updating_groups; + bool group_start_again; + + struct certmap_req_list *certmap_req_list; +}; + +errno_t sf_add_certmap_req(struct files_refresh_ctx *refresh_ctx, + struct tevent_req *req) +{ + struct certmap_req_list *certmap_req_item; + + certmap_req_item = talloc_zero(refresh_ctx, struct certmap_req_list); + if (certmap_req_item == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to allow memory for certmap request list.\n"); + return ENOMEM; + } + certmap_req_item->req = req; + DLIST_ADD(refresh_ctx->certmap_req_list, certmap_req_item); + + return EOK; +} + +static errno_t check_state(struct files_refresh_ctx *refresh_ctx, uint8_t flags) +{ + errno_t ret; + struct timeval tv; + struct timeval delay = { 1, 0 }; + const struct timeval tv_zero = {0 , 0}; + + errno = 0; + ret = gettimeofday(&tv, NULL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "gettimeofday failed [%d][%s], keeping old value.\n", + ret, sss_strerror(ret)); + } + + if ((flags & SF_UPDATE_PASSWD) && (flags & SF_UPDATE_GROUP)) { + if (flags & SF_UPDATE_IMMEDIATE) { + refresh_ctx->start_passwd_refresh = tv_zero; + } else { + if (ret == EOK) { + timeradd(&tv, &delay, + &refresh_ctx->start_passwd_refresh); + } + } + + switch (refresh_ctx->updating_passwd) { + case REFRESH_NOT_RUNNIG: + break; + case REFRESH_WAITING_TO_START: + DEBUG(SSSDBG_TRACE_FUNC, + "Refresh is already waiting to start, nothing to do.\n"); + return EAGAIN; + case REFRESH_ACTIVE: + DEBUG(SSSDBG_TRACE_FUNC, + "Refresh currently active, queing another refresh.\n"); + refresh_ctx->passwd_start_again = true; + return EAGAIN; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown refresh state [%d].\n", + refresh_ctx->updating_passwd); + return EINVAL; + } + + /* Groups are updated after passwd, in case a new passwd update + * arrives we have to run the passwd steps again. */ + switch (refresh_ctx->updating_groups) { + case REFRESH_NOT_RUNNIG: + break; + case REFRESH_WAITING_TO_START: + refresh_ctx->passwd_start_again = true; + return EAGAIN; + case REFRESH_ACTIVE: + refresh_ctx->passwd_start_again = true; + refresh_ctx->group_start_again = true; + return EAGAIN; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown refresh state [%d].\n", + refresh_ctx->updating_groups); + return EINVAL; + } + + refresh_ctx->passwd_start_again = false; + refresh_ctx->updating_passwd = REFRESH_WAITING_TO_START; + refresh_ctx->updating_groups = REFRESH_WAITING_TO_START; + return EOK; + } else if (flags & SF_UPDATE_GROUP) { + if (flags & SF_UPDATE_IMMEDIATE) { + refresh_ctx->start_group_refresh = tv_zero; + } else { + if (ret == EOK) { + timeradd(&tv, &delay, + &refresh_ctx->start_group_refresh); + } + } + + switch (refresh_ctx->updating_groups) { + case REFRESH_NOT_RUNNIG: + break; + case REFRESH_WAITING_TO_START: + DEBUG(SSSDBG_TRACE_FUNC, + "Refresh is already waiting to start, nothing to do.\n"); + return EAGAIN; + case REFRESH_ACTIVE: + DEBUG(SSSDBG_TRACE_FUNC, + "Refresh currently active, queing another refresh.\n"); + refresh_ctx->group_start_again = true; + return EAGAIN; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown refresh state [%d].\n", + refresh_ctx->updating_passwd); + return EINVAL; + } + + refresh_ctx->group_start_again = false; + refresh_ctx->updating_groups = REFRESH_WAITING_TO_START; + return EOK; + } + + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected refresh flags [%"PRIu8"].\n", flags); + return EINVAL; +} + +struct sf_enum_files_state { + struct files_id_ctx *id_ctx; + struct files_refresh_ctx *refresh_ctx; + uint8_t flags; + struct tevent_timer *te; + enum update_steps current_step; + size_t step; + bool in_transaction; + size_t batch_size; + size_t obj_idx; + size_t file_idx; + struct passwd **users; + struct group **groups; + uint32_t delay; + uint32_t initial_delay; +}; + +static int clear_refresh_ctx(void *ptr) +{ + struct sf_enum_files_state *state = (struct sf_enum_files_state *) ptr; + + state->id_ctx->refresh_ctx = NULL; + + return 0; +} + +static void sf_enum_files_steps(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *data); +static struct tevent_req *sf_enum_files_send(struct files_id_ctx *id_ctx, + uint8_t flags) +{ + struct tevent_req *req; + struct sf_enum_files_state *state; + struct timeval tv; + errno_t ret; + struct files_refresh_ctx *refresh_ctx = NULL; + + if (id_ctx->refresh_ctx != NULL) { + refresh_ctx = id_ctx->refresh_ctx; + } else { + refresh_ctx = talloc_zero(id_ctx, struct files_refresh_ctx); + if (refresh_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to allocate refresh context.\n"); + return NULL; + } + refresh_ctx->updating_passwd = REFRESH_NOT_RUNNIG; + refresh_ctx->updating_groups = REFRESH_NOT_RUNNIG; + refresh_ctx->certmap_req_list = NULL; + } + + ret = check_state(refresh_ctx, flags); + if (ret != EOK) { + return NULL; + } + + req = tevent_req_create(id_ctx, &state, struct sf_enum_files_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + if (id_ctx->refresh_ctx == NULL) { + id_ctx->refresh_ctx = talloc_steal(state, refresh_ctx); + talloc_set_destructor((TALLOC_CTX *) state, clear_refresh_ctx); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "The files refresh task should run only " + "once, but a second was detected. Error in internal procession " + "logic.\n"); + ret = EFAULT; + goto done; + } + + state->id_ctx = id_ctx; + state->flags = flags; + state->step = 0; + state->batch_size = 1000; + state->obj_idx = 0; + state->file_idx = 0; + state->initial_delay = 100; + state->delay = 100; + + if (state->flags & SF_UPDATE_PASSWD) { + state->current_step = WAIT_TO_START_USERS; + } else if (state->flags & SF_UPDATE_GROUP) { + state->current_step = WAIT_TO_START_GROUPS; + } else { + DEBUG(SSSDBG_OP_FAILURE, "None of the expected flags are set, " + "cannot start the refresh.\n"); + ret = EINVAL; + goto done; + } + + tv = tevent_timeval_current_ofs(0, state->initial_delay); + state->te = tevent_add_timer(id_ctx->be->ev, state, tv, + sf_enum_files_steps, req); + if (state->te == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to schedule files update.\n"); + ret = EFAULT; + goto done; + } + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, id_ctx->be->ev); + return req; +} + +static void sf_enum_files_steps(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *data) +{ + errno_t ret; + errno_t tret; + struct sf_enum_files_state *state; + struct tevent_req *req; + struct files_id_ctx *id_ctx; + const char *filename = NULL; + struct timeval now; + struct timeval diff; + uint32_t delay; + struct certmap_req_list *certmap_req_item; + struct certmap_req_list *certmap_req_tmp; + + req = talloc_get_type(data, struct tevent_req); + state = tevent_req_data(req, struct sf_enum_files_state); + + state->te = NULL; + id_ctx = state->id_ctx; + delay = state->delay; + + switch (state->current_step) { + case WAIT_TO_START_USERS: + DEBUG(SSSDBG_TRACE_ALL, "Step WAIT_TO_START_USERS.\n"); + errno = 0; + ret = gettimeofday(&now, NULL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "gettimeofday failed [%d][%s], starting user refresh now.\n", + ret, sss_strerror(ret)); + state->current_step = DELETE_USERS; + delay = 0; + } else { + timersub(&state->id_ctx->refresh_ctx->start_passwd_refresh, &now, + &diff); + if (diff.tv_sec < 0) { + state->current_step = DELETE_USERS; + delay = 0; + } else { + delay = diff.tv_sec*1000000 + diff.tv_usec; + } + } + break; + case DELETE_USERS: + if (!state->in_transaction) { + ret = sysdb_transaction_start(id_ctx->domain->sysdb); + if (ret != EOK) { + goto done; + } + state->in_transaction = true; + } + + id_ctx->refresh_ctx->updating_passwd = REFRESH_ACTIVE; + DEBUG(SSSDBG_TRACE_ALL, "Step DELETE_USERS.\n"); + ret = delete_all_users(id_ctx->domain); + if (ret != EOK) { + goto done; + } + state->file_idx = 0; + state->current_step = READ_USERS; + break; + case READ_USERS: + DEBUG(SSSDBG_TRACE_ALL, "Step READ_USERS.\n"); + talloc_zfree(state->users); + state->obj_idx = 0; + /* All users were deleted, therefore we need to enumerate each file again */ + if (id_ctx->passwd_files[state->file_idx] != NULL) { + filename = id_ctx->passwd_files[state->file_idx++]; + ret = enum_files_users(state, filename, &state->users); + if (ret == EOK) { + state->current_step = SAVE_USERS; + } else if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The file %s does not exist (yet), skipping\n", + filename); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot enumerate users from %s, aborting\n", + filename); + goto done; + } + } else { + id_ctx->refresh_ctx->updating_passwd = REFRESH_NOT_RUNNIG; + if (state->flags & SF_UPDATE_GROUP) { + state->current_step = WAIT_TO_START_GROUPS; + } else { + if (state->id_ctx->refresh_ctx->passwd_start_again) { + state->id_ctx->refresh_ctx->passwd_start_again = false; + id_ctx->refresh_ctx->updating_passwd = REFRESH_WAITING_TO_START; + state->current_step = WAIT_TO_START_USERS; + } else if (state->id_ctx->refresh_ctx->group_start_again) { + state->id_ctx->refresh_ctx->group_start_again = false; + id_ctx->refresh_ctx->updating_groups = REFRESH_WAITING_TO_START; + state->current_step = WAIT_TO_START_GROUPS; + } else { + state->current_step = UPDATE_FINISH; + } + } + } + break; + case SAVE_USERS: + DEBUG(SSSDBG_TRACE_ALL, "Step SAVE_USERS.\n"); + if (state->users != NULL) { + ret = sf_enum_users(id_ctx, state->users, + state->obj_idx, state->batch_size); + if (ret == EOK) { + /* check next file */ + state->current_step = READ_USERS; + } else if (ret == EAGAIN) { + state->obj_idx += state->batch_size; + } else { + DEBUG(SSSDBG_OP_FAILURE, "Saving users failed.\n"); + goto done; + } + } + break; + case WAIT_TO_START_GROUPS: + DEBUG(SSSDBG_TRACE_ALL, "Step WAIT_TO_START_GROUPS.\n"); + errno = 0; + ret = gettimeofday(&now, NULL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "gettimeofday failed [%d][%s], starting user refresh now.\n", + ret, sss_strerror(ret)); + state->current_step = DELETE_GROUPS; + delay = 0; + } else { + timersub(&state->id_ctx->refresh_ctx->start_passwd_refresh, &now, + &diff); + if (diff.tv_sec < 0) { + state->current_step = DELETE_GROUPS; + delay = 0; + } else { + delay = diff.tv_sec*1000000 + diff.tv_usec; + } + } + break; + case DELETE_GROUPS: + if (!state->in_transaction) { + ret = sysdb_transaction_start(id_ctx->domain->sysdb); + if (ret != EOK) { + goto done; + } + state->in_transaction = true; + } + id_ctx->refresh_ctx->updating_groups = REFRESH_ACTIVE; + DEBUG(SSSDBG_TRACE_ALL, "Step DELETE_GROUPS.\n"); + ret = delete_all_groups(id_ctx->domain); + if (ret != EOK) { + goto done; + } + state->file_idx = 0; + state->current_step = READ_GROUPS; + break; + case READ_GROUPS: + DEBUG(SSSDBG_TRACE_ALL, "Step READ_GROUPS.\n"); + talloc_zfree(state->groups); + state->obj_idx = 0; + /* All groups were deleted, therefore we need to enumerate each file again */ + if (id_ctx->group_files[state->file_idx] != NULL) { + filename = id_ctx->group_files[state->file_idx++]; + ret = enum_files_groups(state, filename, &state->groups); + if (ret == EOK) { + state->current_step = SAVE_GROUPS; + } else if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The file %s does not exist (yet), skipping\n", + filename); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot enumerate groups from %s, aborting\n", + filename); + goto done; + } + } else { + id_ctx->refresh_ctx->updating_groups = REFRESH_NOT_RUNNIG; + if (state->id_ctx->refresh_ctx->passwd_start_again) { + state->id_ctx->refresh_ctx->passwd_start_again = false; + id_ctx->refresh_ctx->updating_passwd = REFRESH_WAITING_TO_START; + state->current_step = WAIT_TO_START_USERS; + } else if (state->id_ctx->refresh_ctx->group_start_again) { + state->id_ctx->refresh_ctx->group_start_again = false; + id_ctx->refresh_ctx->updating_groups = REFRESH_WAITING_TO_START; + state->current_step = WAIT_TO_START_GROUPS; + } else { + state->current_step = UPDATE_FINISH; + } + } + break; + case SAVE_GROUPS: + DEBUG(SSSDBG_TRACE_ALL, "Step SAVE_GROUPS.\n"); + if (state->groups != NULL) { + ret = sf_enum_groups(id_ctx, state->groups, + state->obj_idx, state->batch_size); + if (ret == EOK) { + state->current_step = READ_GROUPS; + } else if (ret == EAGAIN) { + state->obj_idx += state->batch_size; + } else { + DEBUG(SSSDBG_OP_FAILURE, "Saving groups failed.\n"); + goto done; + } + } + break; + case UPDATE_FINISH: + DEBUG(SSSDBG_TRACE_ALL, "Step UPDATE_FINISH.\n"); + ret = dp_add_sr_attribute(id_ctx->be); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add session recording attribute, ignored.\n"); + } + + ret = sysdb_transaction_commit(id_ctx->domain->sysdb); + if (ret != EOK) { + goto done; + } + state->in_transaction = false; + + state->current_step = UPDATE_DONE; + + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Undefined update step [%u].\n", + state->current_step); + ret = EINVAL; + goto done; + } + + if (state->current_step != UPDATE_DONE) { + tv = tevent_timeval_current_ofs(0, delay); + state->te = tevent_add_timer(id_ctx->be->ev, state, tv, + sf_enum_files_steps, req); + if (state->te == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to schedule files update.\n"); + ret = EFAULT; + goto done; + } + + return; + } + + ret = EOK; +done: + if (state->in_transaction) { + tret = sysdb_transaction_cancel(id_ctx->domain->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot cancel transaction: %d\n", ret); + } + state->in_transaction = false; + } + + DLIST_FOR_EACH_SAFE(certmap_req_item, certmap_req_tmp, + id_ctx->refresh_ctx->certmap_req_list) { + handle_certmap(certmap_req_item->req); + DLIST_REMOVE(certmap_req_item, + id_ctx->refresh_ctx->certmap_req_list); + talloc_free(certmap_req_item); + } + + id_ctx->refresh_ctx->updating_passwd = REFRESH_NOT_RUNNIG; + id_ctx->refresh_ctx->updating_groups = REFRESH_NOT_RUNNIG; + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t sf_enum_files_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static void sf_cb_done(struct files_id_ctx *id_ctx) +{ + /* Only activate a domain when both callbacks are done */ + if (id_ctx->refresh_ctx == NULL) { + dp_sbus_domain_active(id_ctx->be->provider, + id_ctx->domain); + } +} + +static void sf_passwd_cb_done(struct tevent_req *req); +static int sf_passwd_cb(const char *filename, uint32_t flags, void *pvt) +{ + struct files_id_ctx *id_ctx; + struct tevent_req *req; + errno_t ret; + + id_ctx = talloc_get_type(pvt, struct files_id_ctx); + if (id_ctx == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passwd notification\n"); + dp_sbus_domain_inconsistent(id_ctx->be->provider, id_ctx->domain); + + dp_sbus_reset_users_ncache(id_ctx->be->provider, id_ctx->domain); + dp_sbus_reset_users_memcache(id_ctx->be->provider); + dp_sbus_reset_initgr_memcache(id_ctx->be->provider); + + /* Using SF_UDPATE_BOTH here the case when someone edits /etc/group, adds a group member and + * only then edits passwd and adds the user. The reverse is not needed, + * because member/memberof links are established when groups are saved. + */ + req = sf_enum_files_send(id_ctx, SF_UPDATE_BOTH); + if (req == NULL) { + if (id_ctx->refresh_ctx != NULL) { + /* Update is currently active, nothing to do */ + return EOK; + } + DEBUG(SSSDBG_OP_FAILURE, "Failed to start files update.\n"); + ret = ENOMEM; + sf_cb_done(id_ctx); + files_account_info_finished(id_ctx, BE_REQ_USER, ret); + return ret; + } + + tevent_req_set_callback(req, sf_passwd_cb_done, id_ctx); + + return EOK; +} + +static void sf_passwd_cb_done(struct tevent_req *req) +{ + struct files_id_ctx *id_ctx; + errno_t ret; + + id_ctx = tevent_req_callback_data(req, struct files_id_ctx); + + ret = sf_enum_files_recv(req); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not update files: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; +done: + sf_cb_done(id_ctx); + files_account_info_finished(id_ctx, BE_REQ_USER, ret); + files_account_info_finished(id_ctx, BE_REQ_GROUP, ret); +} + +static void sf_group_cb_done(struct tevent_req *req); +static int sf_group_cb(const char *filename, uint32_t flags, void *pvt) +{ + struct files_id_ctx *id_ctx; + errno_t ret; + struct tevent_req *req; + + id_ctx = talloc_get_type(pvt, struct files_id_ctx); + if (id_ctx == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "group notification\n"); + dp_sbus_domain_inconsistent(id_ctx->be->provider, id_ctx->domain); + + dp_sbus_reset_groups_ncache(id_ctx->be->provider, id_ctx->domain); + dp_sbus_reset_groups_memcache(id_ctx->be->provider); + dp_sbus_reset_initgr_memcache(id_ctx->be->provider); + + req = sf_enum_files_send(id_ctx, SF_UPDATE_GROUP); + if (req == NULL) { + if (id_ctx->refresh_ctx != NULL) { + /* Update is currently active, nothing to do */ + return EOK; + } + DEBUG(SSSDBG_OP_FAILURE, "Failed to start files update.\n"); + ret = ENOMEM; + sf_cb_done(id_ctx); + files_account_info_finished(id_ctx, BE_REQ_GROUP, ret); + return ret; + } + + tevent_req_set_callback(req, sf_group_cb_done, id_ctx); + + return EOK; +} + +static void sf_group_cb_done(struct tevent_req *req) +{ + struct files_id_ctx *id_ctx; + errno_t ret; + + id_ctx = tevent_req_callback_data(req, struct files_id_ctx); + + ret = sf_enum_files_recv(req); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not update files: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; +done: + sf_cb_done(id_ctx); + files_account_info_finished(id_ctx, BE_REQ_GROUP, ret); +} + +static void startup_enum_files_done(struct tevent_req *req); +static void startup_enum_files(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct files_id_ctx *id_ctx = talloc_get_type(pvt, struct files_id_ctx); + struct tevent_req *req; + + talloc_zfree(imm); + + req = sf_enum_files_send(id_ctx, SF_UPDATE_BOTH|SF_UPDATE_IMMEDIATE); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Could not update files after startup.\n"); + return; + } + + tevent_req_set_callback(req, startup_enum_files_done, NULL); +} + +static void startup_enum_files_done(struct tevent_req *req) +{ + errno_t ret; + + ret = sf_enum_files_recv(req); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not update files after startup: [%d]: %s\n", + ret, sss_strerror(ret)); + } +} + +static struct snotify_ctx *sf_setup_watch(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *filename, + snotify_cb_fn fn, + struct files_id_ctx *id_ctx) +{ + return snotify_create(mem_ctx, ev, SNOTIFY_WATCH_DIR, + filename, NULL, + IN_DELETE_SELF | IN_CLOSE_WRITE | IN_MOVE_SELF | \ + IN_CREATE | IN_MOVED_TO, + fn, id_ctx); +} + +struct files_ctx *sf_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char **passwd_files, + const char **group_files, + struct files_id_ctx *id_ctx) +{ + struct files_ctx *fctx; + struct tevent_immediate *imm; + int i; + struct snotify_ctx *snctx; + + fctx = talloc(mem_ctx, struct files_ctx); + if (fctx == NULL) { + return NULL; + } + + for (i = 0; passwd_files[i]; i++) { + snctx = sf_setup_watch(fctx, ev, passwd_files[i], + sf_passwd_cb, id_ctx); + if (snctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot set watch for passwd file %s\n", passwd_files[i]); + /* Rather than reporting incomplete or inconsistent information + * in case e.g. group memberships span multiple files, just abort + */ + talloc_free(fctx); + return NULL; + } + } + + for (i = 0; group_files[i]; i++) { + snctx = sf_setup_watch(fctx, ev, group_files[i], + sf_group_cb, id_ctx); + if (snctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot set watch for group file %s\n", group_files[i]); + /* Rather than reporting incomplete or inconsistent information + * in case e.g. group memberships span multiple files, just abort + */ + talloc_free(fctx); + return NULL; + } + } + + /* Enumerate users and groups on startup to process any changes when + * sssd was down. We schedule a request here to minimize the time + * we spend in the init function + */ + imm = tevent_create_immediate(id_ctx); + if (imm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_create_immediate failed.\n"); + talloc_free(fctx); + return NULL; + } + tevent_schedule_immediate(imm, ev, startup_enum_files, id_ctx); + + return fctx; +} diff --git a/src/providers/files/files_private.h b/src/providers/files/files_private.h new file mode 100644 index 0000000..4134a05 --- /dev/null +++ b/src/providers/files/files_private.h @@ -0,0 +1,100 @@ +/* + SSSD + + Files provider declarations + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef __FILES_PRIVATE_H_ +#define __FILES_PRIVATE_H_ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "providers/data_provider/dp.h" + +enum refresh_task_status { + REFRESH_NOT_RUNNIG = 0, + REFRESH_WAITING_TO_START, + REFRESH_ACTIVE +}; + +struct files_id_ctx { + struct be_ctx *be; + struct sss_domain_info *domain; + struct files_ctx *fctx; + struct sss_certmap_ctx *sss_certmap_ctx; + + const char **passwd_files; + const char **group_files; + + struct files_refresh_ctx *refresh_ctx; + + struct tevent_req *users_req; + struct tevent_req *groups_req; + struct tevent_req *initgroups_req; +}; + +/* files_ops.c */ +struct files_ctx *sf_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char **passwd_files, + const char **group_files, + struct files_id_ctx *id_ctx); + +errno_t sf_add_certmap_req(struct files_refresh_ctx *refresh_ctx, + struct tevent_req *req); +/* files_id.c */ +void handle_certmap(struct tevent_req *req); + +struct tevent_req * +files_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct files_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params); + +errno_t files_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +void files_account_info_finished(struct files_id_ctx *id_ctx, + int req_type, + errno_t ret); + +/* files_auth.c */ +struct tevent_req *files_auth_handler_send(TALLOC_CTX *mem_ctx, + void *unused, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t files_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +/* files_certmap.c */ +errno_t files_init_certmap(TALLOC_CTX *mem_ctx, struct files_id_ctx *id_ctx); + +errno_t files_map_cert_to_user(struct files_id_ctx *id_ctx, + struct dp_id_data *data); +#endif /* __FILES_PRIVATE_H_ */ diff --git a/src/providers/ipa/ipa_access.c b/src/providers/ipa/ipa_access.c new file mode 100644 index 0000000..205ebe3 --- /dev/null +++ b/src/providers/ipa/ipa_access.c @@ -0,0 +1,787 @@ +/* + SSSD + + IPA Backend Module -- Access control + + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "util/util.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_access.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_access.h" +#include "providers/ipa/ipa_hosts.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_hbac_rules.h" +#include "providers/ipa/ipa_rules_common.h" + +/* External logging function for HBAC. */ +void hbac_debug_messages(const char *file, int line, + const char *function, + enum hbac_debug_level level, + const char *fmt, ...) +{ + int loglevel; + va_list ap; + + switch(level) { + case HBAC_DBG_FATAL: + loglevel = SSSDBG_FATAL_FAILURE; + break; + case HBAC_DBG_ERROR: + loglevel = SSSDBG_OP_FAILURE; + break; + case HBAC_DBG_WARNING: + loglevel = SSSDBG_MINOR_FAILURE; + break; + case HBAC_DBG_INFO: + loglevel = SSSDBG_CONF_SETTINGS; + break; + case HBAC_DBG_TRACE: + loglevel = SSSDBG_TRACE_INTERNAL; + break; + default: + loglevel = SSSDBG_UNRESOLVED; + break; + } + + va_start(ap, fmt); + sss_vdebug_fn(file, line, function, loglevel, 0, fmt, ap); + va_end(ap); +} + +enum hbac_result { + HBAC_ALLOW = 1, + HBAC_DENY, + HBAC_NOT_APPLICABLE +}; + +enum check_result { + RULE_APPLICABLE = 0, + RULE_NOT_APPLICABLE, + RULE_ERROR +}; + +struct ipa_fetch_hbac_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct sdap_id_ctx *sdap_ctx; + struct ipa_access_ctx *access_ctx; + struct sdap_id_op *sdap_op; + struct dp_option *ipa_options; + + struct sdap_search_base **search_bases; + + /* Hosts */ + struct ipa_common_entries *hosts; + struct sysdb_attrs *ipa_host; + + /* Rules */ + struct ipa_common_entries *rules; + + /* Services */ + struct ipa_common_entries *services; +}; + +static errno_t ipa_fetch_hbac_retry(struct tevent_req *req); +static void ipa_fetch_hbac_connect_done(struct tevent_req *subreq); +static errno_t ipa_fetch_hbac_hostinfo(struct tevent_req *req); +static void ipa_fetch_hbac_hostinfo_done(struct tevent_req *subreq); +static void ipa_fetch_hbac_services_done(struct tevent_req *subreq); +static void ipa_fetch_hbac_rules_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_fetch_hbac_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_access_ctx *access_ctx) +{ + struct ipa_fetch_hbac_state *state; + struct tevent_req *req; + time_t now, refresh_interval; + bool offline; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_fetch_hbac_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->access_ctx = access_ctx; + state->sdap_ctx = access_ctx->sdap_ctx; + state->ipa_options = access_ctx->ipa_options; + state->search_bases = access_ctx->hbac_search_bases; + state->hosts = talloc_zero(state, struct ipa_common_entries); + if (state->hosts == NULL) { + ret = ENOMEM; + goto immediately; + } + state->services = talloc_zero(state, struct ipa_common_entries); + if (state->hosts == NULL) { + ret = ENOMEM; + goto immediately; + } + state->rules = talloc_zero(state, struct ipa_common_entries); + if (state->rules == NULL) { + ret = ENOMEM; + goto immediately; + } + + if (state->search_bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No HBAC search base found.\n"); + ret = EINVAL; + goto immediately; + } + + state->sdap_op = sdap_id_op_create(state, state->sdap_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + offline = be_is_offline(be_ctx); + DEBUG(SSSDBG_TRACE_ALL, "Connection status is [%s].\n", + offline ? "offline" : "online"); + + refresh_interval = dp_opt_get_int(state->ipa_options, IPA_HBAC_REFRESH); + now = time(NULL); + + if (offline || now < access_ctx->last_update + refresh_interval) { + DEBUG(SSSDBG_TRACE_FUNC, "Performing cached HBAC evaluation\n"); + ret = EOK; + goto immediately; + } + + ret = ipa_fetch_hbac_retry(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ipa_fetch_hbac_retry(struct tevent_req *req) +{ + struct ipa_fetch_hbac_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct ipa_fetch_hbac_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: " + "%d(%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, ipa_fetch_hbac_connect_done, req); + + return EAGAIN; +} + +static void ipa_fetch_hbac_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + if (dp_error == DP_ERR_OFFLINE) { + ret = EOK; + goto done; + } + + ret = ipa_fetch_hbac_hostinfo(req); + if (ret == EAGAIN) { + return; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_fetch_hbac_hostinfo(struct tevent_req *req) +{ + struct ipa_fetch_hbac_state *state; + struct tevent_req *subreq; + const char *hostname; + bool srchost; + + state = tevent_req_data(req, struct ipa_fetch_hbac_state); + + srchost = dp_opt_get_bool(state->ipa_options, IPA_HBAC_SUPPORT_SRCHOST); + if (srchost) { + /* Support srchost + * -> we don't want any particular host, + * we want all hosts + */ + hostname = NULL; + + /* THIS FEATURE IS DEPRECATED */ + DEBUG(SSSDBG_MINOR_FAILURE, "WARNING: Using deprecated option " + "ipa_hbac_support_srchost.\n"); + sss_log(SSS_LOG_NOTICE, "WARNING: Using deprecated option " + "ipa_hbac_support_srchost.\n"); + } else { + hostname = dp_opt_get_string(state->ipa_options, IPA_HOSTNAME); + } + + subreq = ipa_host_info_send(state, state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, hostname, + state->access_ctx->host_map, + state->access_ctx->hostgroup_map, + state->access_ctx->host_search_bases); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_fetch_hbac_hostinfo_done, req); + + return EAGAIN; +} + +static void ipa_fetch_hbac_hostinfo_done(struct tevent_req *subreq) +{ + struct ipa_fetch_hbac_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + int dp_error; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_hbac_state); + + ret = ipa_host_info_recv(subreq, state, + &state->hosts->entry_count, + &state->hosts->entries, + &state->hosts->group_count, + &state->hosts->groups); + state->hosts->entry_subdir = HBAC_HOSTS_SUBDIR; + state->hosts->group_subdir = HBAC_HOSTGROUPS_SUBDIR; + talloc_zfree(subreq); + + if (ret != EOK) { + /* Only call sdap_id_op_done in case of an error to trigger a + * failover. In general changing the tevent_req layout would be better + * so that all searches are in another sub-request so that we can + * error out at any step and the parent request can call + * sdap_id_op_done just once. */ + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ipa_fetch_hbac_retry(req); + if (ret != EAGAIN) { + goto done; + } + return; + } + goto done; + } + + subreq = ipa_hbac_service_info_send(state, state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, + state->search_bases); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_fetch_hbac_services_done, req); + + return; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void ipa_fetch_hbac_services_done(struct tevent_req *subreq) +{ + struct ipa_fetch_hbac_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_hbac_state); + + ret = ipa_hbac_service_info_recv(subreq, state, + &state->services->entry_count, + &state->services->entries, + &state->services->group_count, + &state->services->groups); + state->services->entry_subdir = HBAC_SERVICES_SUBDIR; + state->services->group_subdir = HBAC_SERVICEGROUPS_SUBDIR; + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + /* Get the ipa_host attrs */ + ret = ipa_get_host_attrs(state->ipa_options, + state->hosts->entry_count, + state->hosts->entries, + &state->ipa_host); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not locate IPA host.\n"); + goto done; + } + + subreq = ipa_hbac_rule_info_send(state, state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, + state->search_bases, + state->ipa_host); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_fetch_hbac_rules_done, req); + + return; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void ipa_fetch_hbac_rules_done(struct tevent_req *subreq) +{ + struct ipa_fetch_hbac_state *state = NULL; + struct tevent_req *req = NULL; + int dp_error; + errno_t ret; + bool found; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_hbac_state); + + ret = ipa_hbac_rule_info_recv(subreq, state, + &state->rules->entry_count, + &state->rules->entries); + state->rules->entry_subdir = HBAC_RULES_SUBDIR; + talloc_zfree(subreq); + if (ret == ENOENT) { + /* Set ret to EOK so we can safely call sdap_id_op_done. */ + found = false; + ret = EOK; + } else if (ret == EOK) { + found = true; + } else { + goto done; + } + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ipa_fetch_hbac_retry(req); + if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (found == false) { + /* No rules were found that apply to this host. */ + ret = ipa_common_purge_rules(state->be_ctx->domain, + HBAC_RULES_SUBDIR); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to remove HBAC rules\n"); + goto done; + } + + ret = ENOENT; + goto done; + } + + ret = ipa_common_save_rules(state->be_ctx->domain, + state->hosts, state->services, state->rules, + &state->access_ctx->last_update); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save HBAC rules\n"); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_fetch_hbac_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t ipa_hbac_evaluate_rules(struct be_ctx *be_ctx, + struct dp_option *ipa_options, + struct pam_data *pd) +{ + TALLOC_CTX *tmp_ctx; + struct hbac_ctx hbac_ctx; + struct hbac_rule **hbac_rules; + struct hbac_eval_req *eval_req; + enum hbac_eval_result result; + struct hbac_info *info = NULL; + const char **attrs_get_cached_rules; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + hbac_ctx.be_ctx = be_ctx; + hbac_ctx.ipa_options = ipa_options; + hbac_ctx.pd = pd; + + /* Get HBAC rules from the sysdb */ + attrs_get_cached_rules = hbac_get_attrs_to_get_cached_rules(tmp_ctx); + if (attrs_get_cached_rules == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "hbac_get_attrs_to_get_cached_rules() failed\n"); + ret = ENOMEM; + goto done; + } + ret = ipa_common_get_cached_rules(tmp_ctx, be_ctx->domain, + IPA_HBAC_RULE, HBAC_RULES_SUBDIR, + attrs_get_cached_rules, + &hbac_ctx.rule_count, &hbac_ctx.rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not retrieve rules from the cache\n"); + goto done; + } + + ret = hbac_ctx_to_rules(tmp_ctx, &hbac_ctx, &hbac_rules, &eval_req); + if (ret == EPERM) { + DEBUG(SSSDBG_CRIT_FAILURE, + "DENY rules detected. Denying access to all users\n"); + ret = ERR_ACCESS_DENIED; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not construct HBAC rules\n"); + goto done; + } + + hbac_enable_debug(hbac_debug_messages); + + result = hbac_evaluate(hbac_rules, eval_req, &info); + if (result == HBAC_EVAL_ALLOW) { + DEBUG(SSSDBG_MINOR_FAILURE, "Access granted by HBAC rule [%s]\n", + info->rule_name); + ret = EOK; + goto done; + } else if (result == HBAC_EVAL_ERROR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error [%s] occurred in rule [%s]\n", + hbac_error_string(info->code), info->rule_name); + ret = EIO; + goto done; + } else if (result == HBAC_EVAL_OOM) { + DEBUG(SSSDBG_CRIT_FAILURE, "Insufficient memory\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Access denied by HBAC rules\n"); + ret = ERR_ACCESS_DENIED; + +done: + hbac_free_info(info); + talloc_free(tmp_ctx); + return ret; +} + +struct ipa_pam_access_handler_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_access_ctx *access_ctx; + struct pam_data *pd; +}; + +static void ipa_pam_access_handler_sdap_done(struct tevent_req *subreq); +static void ipa_pam_access_handler_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_pam_access_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_access_ctx *access_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct ipa_pam_access_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_pam_access_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->ev = params->ev; + state->be_ctx = params->be_ctx; + state->access_ctx = access_ctx; + + subreq = sdap_access_send(state, params->ev, params->be_ctx, + params->domain, access_ctx->sdap_access_ctx, + access_ctx->sdap_ctx->conn, pd); + if (subreq == NULL) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_pam_access_handler_sdap_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_pam_access_handler_sdap_done(struct tevent_req *subreq) +{ + struct ipa_pam_access_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_access_handler_state); + + ret = sdap_access_recv(subreq); + talloc_free(subreq); + switch (ret) { + case EOK: + case ERR_PASSWORD_EXPIRED_WARN: + /* Account wasn't locked. Continue below to HBAC processing. */ + state->pd->pam_status = PAM_SUCCESS; + break; + case ERR_PASSWORD_EXPIRED_RENEW: + state->pd->pam_status = PAM_NEW_AUTHTOK_REQD; + break; + case ERR_ACCESS_DENIED: + case ERR_PASSWORD_EXPIRED_REJECT: + /* Account was locked or password expired. */ + state->pd->pam_status = PAM_PERM_DENIED; + goto done; + case ERR_ACCOUNT_EXPIRED: + state->pd->pam_status = PAM_ACCT_EXPIRED; + goto done; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Error retrieving access check result " + "[%d]: %s.\n", ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + } + + subreq = ipa_fetch_hbac_send(state, state->ev, state->be_ctx, + state->access_ctx); + if (subreq == NULL) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + /* The callback function will not overwrite pam_status in case of + * success. Because of that, pam_status must be set to the desired + * value in advance. */ + tevent_req_set_callback(subreq, ipa_pam_access_handler_done, req); + + return; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_pam_access_handler_done(struct tevent_req *subreq) +{ + struct ipa_pam_access_handler_state *state; + struct tevent_req *req; + int preset_pam_status; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_access_handler_state); + + ret = ipa_fetch_hbac_recv(subreq); + talloc_free(subreq); + + if (ret == ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "No HBAC rules found, denying access\n"); + state->pd->pam_status = PAM_PERM_DENIED; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to fetch HBAC rules [%d]: %s\n", + ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + /* ipa_hbac_evaluate_rules() could overwrite state->pd->pam_status but + we don't want that. Save the previous value and set it back in case + of succcess. */ + preset_pam_status = state->pd->pam_status; + ret = ipa_hbac_evaluate_rules(state->be_ctx, + state->access_ctx->ipa_options, state->pd); + if (ret == EOK) { + state->pd->pam_status = preset_pam_status; + } else if (ret == ERR_ACCESS_DENIED) { + state->pd->pam_status = PAM_PERM_DENIED; + } else { + state->pd->pam_status = PAM_SYSTEM_ERR; + } +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +ipa_pam_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct ipa_pam_access_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_pam_access_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} + +struct ipa_refresh_access_rules_state { + int dummy; +}; + +static void ipa_refresh_access_rules_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_refresh_access_rules_send(TALLOC_CTX *mem_ctx, + struct ipa_access_ctx *access_ctx, + void *no_input_data, + struct dp_req_params *params) +{ + struct ipa_refresh_access_rules_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + DEBUG(SSSDBG_TRACE_FUNC, "Refreshing HBAC rules\n"); + + req = tevent_req_create(mem_ctx, &state, + struct ipa_refresh_access_rules_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + subreq = ipa_fetch_hbac_send(state, params->ev, params->be_ctx, access_ctx); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + tevent_req_post(req, params->ev); + return req; + } + + tevent_req_set_callback(subreq, ipa_refresh_access_rules_done, req); + + return req; +} + +static void ipa_refresh_access_rules_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = ipa_fetch_hbac_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t ipa_refresh_access_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + void **_no_output_data) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ipa/ipa_access.h b/src/providers/ipa/ipa_access.h new file mode 100644 index 0000000..9cec0d1 --- /dev/null +++ b/src/providers/ipa/ipa_access.h @@ -0,0 +1,76 @@ +/* + SSSD + + IPA Backend Module -- Access control + + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _IPA_ACCESS_H_ +#define _IPA_ACCESS_H_ + +#include "providers/ldap/ldap_common.h" + +enum ipa_access_mode { + IPA_ACCESS_DENY = 0, + IPA_ACCESS_ALLOW +}; + +struct ipa_access_ctx { + struct sdap_id_ctx *sdap_ctx; + struct dp_option *ipa_options; + time_t last_update; + struct sdap_access_ctx *sdap_access_ctx; + + struct sdap_attr_map *host_map; + struct sdap_attr_map *hostgroup_map; + struct sdap_search_base **host_search_bases; + struct sdap_search_base **hbac_search_bases; +}; + +struct hbac_ctx { + struct be_ctx *be_ctx; + struct dp_option *ipa_options; + struct pam_data *pd; + size_t rule_count; + struct sysdb_attrs **rules; +}; + +struct tevent_req * +ipa_pam_access_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_access_ctx *access_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +ipa_pam_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +struct tevent_req * +ipa_refresh_access_rules_send(TALLOC_CTX *mem_ctx, + struct ipa_access_ctx *access_ctx, + void *no_input_data, + struct dp_req_params *params); + +errno_t ipa_refresh_access_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + void **_no_output_data); + +#endif /* _IPA_ACCESS_H_ */ diff --git a/src/providers/ipa/ipa_auth.c b/src/providers/ipa/ipa_auth.c new file mode 100644 index 0000000..1d61a10 --- /dev/null +++ b/src/providers/ipa/ipa_auth.c @@ -0,0 +1,478 @@ +/* + SSSD + + IPA Backend Module -- Authentication + + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "util/util.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/ipa/ipa_auth.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_config.h" + +struct get_password_migration_flag_state { + struct tevent_context *ev; + struct sdap_id_op *sdap_op; + struct sdap_id_ctx *sdap_id_ctx; + struct fo_server *srv; + char *ipa_realm; + bool password_migration; +}; + +static void get_password_migration_flag_auth_done(struct tevent_req *subreq); +static void get_password_migration_flag_done(struct tevent_req *subreq); + +static struct tevent_req *get_password_migration_flag_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_id_ctx, + char *ipa_realm) +{ + int ret; + struct tevent_req *req, *subreq; + struct get_password_migration_flag_state *state; + + if (sdap_id_ctx == NULL || ipa_realm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing parameter.\n"); + return NULL; + } + + req = tevent_req_create(memctx, &state, + struct get_password_migration_flag_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->sdap_id_ctx = sdap_id_ctx; + state->srv = NULL; + state->password_migration = false; + state->ipa_realm = ipa_realm; + + state->sdap_op = sdap_id_op_create(state, + state->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n"); + goto fail; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto fail; + } + + tevent_req_set_callback(subreq, get_password_migration_flag_auth_done, req); + + return req; + +fail: + talloc_zfree(req); + return NULL; +} + +static void get_password_migration_flag_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_password_migration_flag_state *state = tevent_req_data(req, + struct get_password_migration_flag_state); + int ret, dp_error; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No IPA server is available, cannot get the " + "migration flag while offline\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to IPA server: [%d](%s)\n", + ret, strerror(ret)); + } + + tevent_req_error(req, ret); + return; + } + + subreq = ipa_get_config_send(state, state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_id_ctx->opts, state->ipa_realm, + NULL, NULL, NULL); + + tevent_req_set_callback(subreq, get_password_migration_flag_done, req); +} + +static void get_password_migration_flag_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_password_migration_flag_state *state = tevent_req_data(req, + struct get_password_migration_flag_state); + int ret; + struct sysdb_attrs *reply = NULL; + const char *value = NULL; + + ret = ipa_get_config_recv(subreq, state, &reply); + talloc_zfree(subreq); + if (ret) { + DEBUG(SSSDBG_IMPORTANT_INFO, "Unable to retrieve migration flag " + "from IPA server"); + goto done; + } + + ret = sysdb_attrs_get_string(reply, IPA_CONFIG_MIGRATION_ENABLED, &value); + if (ret == EOK && strcasecmp(value, "true") == 0) { + state->password_migration = true; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } +} + +static int get_password_migration_flag_recv(struct tevent_req *req, + bool *password_migration) +{ + struct get_password_migration_flag_state *state = tevent_req_data(req, + struct get_password_migration_flag_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *password_migration = state->password_migration; + return EOK; +} + +struct ipa_pam_auth_handler_state { + struct tevent_context *ev; + struct ipa_auth_ctx *auth_ctx; + struct be_ctx *be_ctx; + struct pam_data *pd; + struct sss_domain_info *dom; +}; + +static void ipa_pam_auth_handler_krb5_done(struct tevent_req *subreq); +static void ipa_pam_auth_handler_flag_done(struct tevent_req *subreq); +static void ipa_pam_auth_handler_connect_done(struct tevent_req *subreq); +static void ipa_pam_auth_handler_auth_done(struct tevent_req *subreq); +static void ipa_pam_auth_handler_retry_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_pam_auth_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct ipa_pam_auth_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_pam_auth_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->ev = params->ev; + state->auth_ctx = auth_ctx; + state->be_ctx = params->be_ctx; + state->dom = find_domain_by_name(state->be_ctx->domain, + state->pd->domain, + true); + if (state->dom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain %s\n", state->pd->domain); + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + pd->pam_status = PAM_SYSTEM_ERR; + + subreq = krb5_auth_queue_send(state, params->ev, params->be_ctx, + pd, auth_ctx->krb5_auth_ctx); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_pam_auth_handler_krb5_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_pam_auth_handler_krb5_done(struct tevent_req *subreq) +{ + struct ipa_pam_auth_handler_state *state; + struct tevent_req *req; + int dp_err; + char *realm; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + state->pd->pam_status = PAM_SYSTEM_ERR; + ret = krb5_auth_queue_recv(subreq, &state->pd->pam_status, &dp_err); + talloc_free(subreq); + if (ret != EOK && state->pd->pam_status != PAM_CRED_ERR) { + DEBUG(SSSDBG_OP_FAILURE, "KRB5 auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (dp_err != DP_ERR_OK) { + goto done; + } + + if (state->pd->cmd == SSS_PAM_AUTHENTICATE + && state->pd->pam_status == PAM_CRED_ERR + && !IS_SUBDOMAIN(state->dom)) { + realm = dp_opt_get_string(state->auth_ctx->ipa_options, IPA_KRB5_REALM); + subreq = get_password_migration_flag_send(state, state->ev, + state->auth_ctx->sdap_id_ctx, + realm); + if (subreq == NULL) { + goto done; + } + + tevent_req_set_callback(subreq, ipa_pam_auth_handler_flag_done, req); + return; + } + + /* PAM_CRED_ERR is used to indicate to the IPA provider that trying + * password migration would make sense. From this point on it isn't + * necessary to keep this status, so it can be translated to PAM_AUTH_ERR. + */ + if (state->pd->pam_status == PAM_CRED_ERR) { + state->pd->pam_status = PAM_AUTH_ERR; + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_pam_auth_handler_flag_done(struct tevent_req *subreq) +{ + struct ipa_pam_auth_handler_state *state; + struct sdap_auth_ctx *sdap_auth_ctx; + bool password_migration = false; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + ret = get_password_migration_flag_recv(subreq, &password_migration); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to get password migration flag " + "[%d]: %s\n", ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + if (password_migration) { + sdap_auth_ctx = state->auth_ctx->sdap_auth_ctx; + subreq = sdap_cli_connect_send(state, state->ev, + sdap_auth_ctx->opts, + sdap_auth_ctx->be, + sdap_auth_ctx->service, + true, CON_TLS_ON, true); + if (subreq == NULL) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + tevent_req_set_callback(subreq, ipa_pam_auth_handler_connect_done, req); + return; + } + + /* PAM_CRED_ERR is used to indicate to the IPA provider that trying + * password migration would make sense. From this point on it isn't + * necessary to keep this status, so it can be translated to PAM_AUTH_ERR. + */ + if (state->pd->pam_status == PAM_CRED_ERR) { + state->pd->pam_status = PAM_AUTH_ERR; + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_pam_auth_handler_connect_done(struct tevent_req *subreq) +{ + struct ipa_pam_auth_handler_state *state; + struct tevent_req *req; + struct sdap_handle *sh = NULL; + const char *attrs[] = {SYSDB_ORIG_DN, NULL}; + struct ldb_message *msg; + const char *dn; + int timeout; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + state->pd->pam_status = PAM_SYSTEM_ERR; + + ret = sdap_cli_connect_recv(subreq, state, NULL, &sh, NULL); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot connect to LDAP server to perform " + "migration [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Assuming Kerberos password is missing, " + "starting password migration.\n"); + + ret = sysdb_search_user_by_name(state, state->be_ctx->domain, + state->pd->user, attrs, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_name failed.\n"); + goto done; + } + + dn = ldb_msg_find_attr_as_string(msg, SYSDB_ORIG_DN, NULL); + if (dn == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Missing original DN for user [%s].\n", + state->pd->user); + goto done; + } + + timeout = dp_opt_get_int(state->auth_ctx->sdap_auth_ctx->opts->basic, + SDAP_OPT_TIMEOUT); + + subreq = sdap_auth_send(state, state->ev, sh, NULL, NULL, dn, + state->pd->authtok, timeout); + if (subreq == NULL) { + goto done; + } + + tevent_req_set_callback(subreq, ipa_pam_auth_handler_auth_done, req); + return; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_pam_auth_handler_auth_done(struct tevent_req *subreq) +{ + struct ipa_pam_auth_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + ret = sdap_auth_recv(subreq, state, NULL); + + talloc_free(subreq); + switch (ret) { + case EOK: + break; + case ERR_AUTH_DENIED: + case ERR_AUTH_FAILED: + case ERR_PASSWORD_EXPIRED: + /* TODO: do we need to handle expired passwords? */ + DEBUG(SSSDBG_MINOR_FAILURE, "LDAP authentication failed, " + "password migration not possible.\n"); + state->pd->pam_status = PAM_CRED_INSUFFICIENT; + goto done; + default: + DEBUG(SSSDBG_OP_FAILURE, "auth_send request failed.\n"); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "LDAP authentication succeeded, " + "trying Kerberos authentication again.\n"); + + subreq = krb5_auth_queue_send(state, state->ev, state->be_ctx, state->pd, + state->auth_ctx->krb5_auth_ctx); + if (subreq == NULL) { + goto done; + } + + tevent_req_set_callback(subreq, ipa_pam_auth_handler_retry_done, req); + return; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_pam_auth_handler_retry_done(struct tevent_req *subreq) +{ + struct ipa_pam_auth_handler_state *state; + struct tevent_req *req; + int dp_err; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + ret = krb5_auth_queue_recv(subreq, &state->pd->pam_status, &dp_err); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_auth_recv request failed.\n"); + state->pd->pam_status = PAM_SYSTEM_ERR; + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +ipa_pam_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct ipa_pam_auth_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/ipa/ipa_auth.h b/src/providers/ipa/ipa_auth.h new file mode 100644 index 0000000..53666eb --- /dev/null +++ b/src/providers/ipa/ipa_auth.h @@ -0,0 +1,42 @@ +/* + SSSD + + IPA Backend Module -- Authentication + + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _IPA_AUTH_H_ +#define _IPA_AUTH_H_ + +#include "providers/backend.h" +#include "providers/ipa/ipa_common.h" + +struct tevent_req * +ipa_pam_auth_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +ipa_pam_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +#endif /* _IPA_AUTH_H_ */ diff --git a/src/providers/ipa/ipa_autofs.c b/src/providers/ipa/ipa_autofs.c new file mode 100644 index 0000000..29814f7 --- /dev/null +++ b/src/providers/ipa/ipa_autofs.c @@ -0,0 +1,65 @@ +/* + SSSD + + IPA Provider Initialization functions + + Authors: + Simo Sorce + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/child_common.h" +#include "providers/ipa/ipa_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ipa/ipa_auth.h" +#include "providers/ipa/ipa_access.h" +#include "providers/ipa/ipa_dyndns.h" +#include "providers/ipa/ipa_selinux.h" + +errno_t ipa_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + int ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing autofs IPA back end\n"); + + ret = ipa_get_autofs_options(id_ctx->ipa_options, + sysdb_ctx_get_ldb(be_ctx->domain->sysdb), + be_ctx->cdb, + be_ctx->conf_path, &id_ctx->sdap_id_ctx->opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get IPA autofs options\n"); + return ret; + } + + dp_set_method(dp_methods, DPM_AUTOFS_ENUMERATE, + sdap_autofs_enumerate_handler_send, sdap_autofs_enumerate_handler_recv, id_ctx->sdap_id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + dp_set_method(dp_methods, DPM_AUTOFS_GET_MAP, + sdap_autofs_get_map_handler_send, sdap_autofs_get_map_handler_recv, id_ctx->sdap_id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + dp_set_method(dp_methods, DPM_AUTOFS_GET_ENTRY, + sdap_autofs_get_entry_handler_send, sdap_autofs_get_entry_handler_recv, id_ctx->sdap_id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + return ret; +} diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c new file mode 100644 index 0000000..01c835c --- /dev/null +++ b/src/providers/ipa/ipa_common.c @@ -0,0 +1,1370 @@ +/* + SSSD + + IPA Provider Common Functions + + Authors: + Simo Sorce + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include + +#include "db/sysdb_selinux.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_dyndns.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/be_dyndns.h" +#include "util/sss_krb5.h" +#include "db/sysdb_services.h" +#include "db/sysdb_autofs.h" + +#include "providers/ipa/ipa_opts.h" +#include "providers/data_provider/dp_private.h" + +int ipa_get_options(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sss_domain_info *dom, + struct ipa_options **_opts) +{ + struct ipa_options *opts; + char *domain; + char *server; + char *realm; + char *ipa_hostname; + int ret; + char hostname[HOST_NAME_MAX + 1]; + + opts = talloc_zero(memctx, struct ipa_options); + if (!opts) return ENOMEM; + + ret = dp_get_options(opts, cdb, conf_path, + ipa_basic_opts, + IPA_OPTS_BASIC, + &opts->basic); + if (ret != EOK) { + goto done; + } + + domain = dp_opt_get_string(opts->basic, IPA_DOMAIN); + if (!domain) { + ret = dp_opt_set_string(opts->basic, IPA_DOMAIN, dom->name); + if (ret != EOK) { + goto done; + } + domain = dom->name; + } + + server = dp_opt_get_string(opts->basic, IPA_SERVER); + if (!server) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No ipa server set, will use service discovery!\n"); + } + + ipa_hostname = dp_opt_get_string(opts->basic, IPA_HOSTNAME); + if (ipa_hostname == NULL) { + ret = gethostname(hostname, sizeof(hostname)); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "gethostname failed [%d][%s].\n", ret, + strerror(ret)); + goto done; + } + hostname[HOST_NAME_MAX] = '\0'; + DEBUG(SSSDBG_TRACE_ALL, "Setting ipa_hostname to [%s].\n", hostname); + ret = dp_opt_set_string(opts->basic, IPA_HOSTNAME, hostname); + if (ret != EOK) { + goto done; + } + } + + /* First check whether the realm has been manually specified */ + realm = dp_opt_get_string(opts->basic, IPA_KRB5_REALM); + if (!realm) { + /* No explicit krb5_realm, use the IPA domain, transform to upper-case */ + realm = get_uppercase_realm(opts, domain); + if (!realm) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(opts->basic, IPA_KRB5_REALM, + realm); + if (ret != EOK) { + goto done; + } + } + + ret = EOK; + *_opts = opts; + +done: + if (ret != EOK) { + talloc_zfree(opts); + } + return ret; +} + +static errno_t ipa_parse_search_base(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + struct dp_option *opts, int class, + struct sdap_search_base ***_search_bases) +{ + const char *class_name; + char *unparsed_base; + + *_search_bases = NULL; + + switch (class) { + case IPA_HBAC_SEARCH_BASE: + class_name = "IPA_HBAC"; + break; + case IPA_SELINUX_SEARCH_BASE: + class_name = "IPA_SELINUX"; + break; + case IPA_SUBDOMAINS_SEARCH_BASE: + class_name = "IPA_SUBDOMAINS"; + break; + case IPA_MASTER_DOMAIN_SEARCH_BASE: + class_name = "IPA_MASTER_DOMAIN"; + break; + case IPA_RANGES_SEARCH_BASE: + class_name = "IPA_RANGES"; + break; + case IPA_VIEWS_SEARCH_BASE: + class_name = "IPA_VIEWS"; + break; + case IPA_DESKPROFILE_SEARCH_BASE: + class_name = "IPA_DESKPROFILE"; + break; + case IPA_SUBID_RANGES_SEARCH_BASE: + class_name = "IPA_SUBID_RANGES"; + break; + default: + DEBUG(SSSDBG_CONF_SETTINGS, + "Unknown search base type: [%d]\n", class); + class_name = "UNKNOWN"; + /* Non-fatal */ + break; + } + + unparsed_base = dp_opt_get_string(opts, class); + if (!unparsed_base || unparsed_base[0] == '\0') return ENOENT; + + return common_parse_search_base(mem_ctx, unparsed_base, ldb, + class_name, NULL, + _search_bases); +} + +int ipa_get_id_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sdap_options **_opts) +{ + TALLOC_CTX *tmpctx; + char *basedn; + char *realm; + char *value; + int ret; + int i; + bool server_mode; + struct ldb_context *ldb; + + ldb = sysdb_ctx_get_ldb(dp->be_ctx->domain->sysdb); + + tmpctx = talloc_new(ipa_opts); + if (!tmpctx) { + return ENOMEM; + } + + ipa_opts->id = talloc_zero(ipa_opts, struct sdap_options); + if (!ipa_opts->id) { + ret = ENOMEM; + goto done; + } + ipa_opts->id->dp = dp; + + ret = sdap_domain_add(ipa_opts->id, + ipa_opts->id_ctx->sdap_id_ctx->be->domain, + NULL); + if (ret != EOK) { + goto done; + } + + /* get sdap options */ + ret = dp_get_options(ipa_opts->id, cdb, conf_path, + ipa_def_ldap_opts, + SDAP_OPTS_BASIC, + &ipa_opts->id->basic); + if (ret != EOK) { + goto done; + } + + /* sssd-ipa can't use simple bind, ignore option that potentially can be set + * for sssd-ldap in the same domain + */ + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_DEFAULT_AUTHTOK_TYPE, NULL); + if (ret != EOK) { + goto done; + } + + ret = domain_to_basedn(tmpctx, + dp_opt_get_string(ipa_opts->basic, IPA_KRB5_REALM), + &basedn); + if (ret != EOK) { + goto done; + } + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE)) { + /* FIXME: get values by querying IPA */ + /* set search base */ + value = talloc_asprintf(tmpctx, "cn=accounts,%s", basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE)); + } + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_SEARCH_BASE, + &ipa_opts->id->sdom->search_bases); + if (ret != EOK) goto done; + + /* set krb realm */ + if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_KRB5_REALM)) { + realm = dp_opt_get_string(ipa_opts->basic, IPA_KRB5_REALM); + value = talloc_strdup(tmpctx, realm); + if (value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_KRB5_REALM, value); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_KRB5_REALM].opt_name, + dp_opt_get_string(ipa_opts->id->basic, SDAP_KRB5_REALM)); + } + + ret = sdap_set_sasl_options(ipa_opts->id, + dp_opt_get_string(ipa_opts->basic, + IPA_HOSTNAME), + dp_opt_get_string(ipa_opts->id->basic, + SDAP_KRB5_REALM), + dp_opt_get_string(ipa_opts->id->basic, + SDAP_KRB5_KEYTAB)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set the SASL-related options\n"); + goto done; + } + + /* fix schema to IPAv1 for now */ + ipa_opts->id->schema_type = SDAP_SCHEMA_IPA_V1; + + /* set user/group search bases if they are not specified */ + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_USER_SEARCH_BASE)) { + ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_USER_SEARCH_BASE, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_SEARCH_BASE)); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_USER_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_USER_SEARCH_BASE)); + } + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_USER_SEARCH_BASE, + &ipa_opts->id->sdom->user_search_bases); + if (ret != EOK) goto done; + + /* In server mode we need to search both cn=accounts,$SUFFIX and + * cn=trusts,$SUFFIX to allow trusted domain object accounts to be found. + * If cn=trusts,$SUFFIX is missing in the user search bases, add one + */ + server_mode = dp_opt_get_bool(ipa_opts->basic, IPA_SERVER_MODE); + if (server_mode != false) { + /* bases is not NULL at this point already */ + struct sdap_search_base **bases = ipa_opts->id->sdom->user_search_bases; + struct sdap_search_base *new_base = NULL; + + for (i = 0; bases[i] != NULL; i++) { + if (strcasestr(bases[i]->basedn, "cn=trusts,") != NULL) { + break; + } + } + if (NULL == bases[i]) { + /* no cn=trusts in the base, add a new one */ + char *new_dn = talloc_asprintf(bases, + "cn=trusts,%s", + basedn); + if (NULL == new_dn) { + ret = ENOMEM; + goto done; + } + + ret = sdap_create_search_base(bases, ldb, new_dn, + LDAP_SCOPE_SUBTREE, + "(objectClass=ipaIDObject)", + &new_base); + if (ret != EOK) { + goto done; + } + + bases = talloc_realloc(ipa_opts->id, + ipa_opts->id->sdom->user_search_bases, + struct sdap_search_base*, + i + 2); + + if (NULL == bases) { + ret = ENOMEM; + goto done; + } + + bases[i] = new_base; + bases[i+1] = NULL; + ipa_opts->id->sdom->user_search_bases = bases; + + DEBUG(SSSDBG_TRACE_FUNC, + "Option %s expanded to cover cn=trusts base\n", + ipa_opts->id->basic[SDAP_USER_SEARCH_BASE].opt_name); + } + } + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_GROUP_SEARCH_BASE)) { + ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_GROUP_SEARCH_BASE, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_SEARCH_BASE)); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_GROUP_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_GROUP_SEARCH_BASE)); + } + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_GROUP_SEARCH_BASE, + &ipa_opts->id->sdom->group_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_NETGROUP_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=ng,cn=alt,%s", basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_NETGROUP_SEARCH_BASE, + value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_NETGROUP_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_NETGROUP_SEARCH_BASE)); + } + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_NETGROUP_SEARCH_BASE, + &ipa_opts->id->sdom->netgroup_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_HOST_SEARCH_BASE)) { + + value = dp_opt_get_string(ipa_opts->basic, IPA_HOST_SEARCH_BASE); + if (!value) { + value = dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE); + } + + ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_HOST_SEARCH_BASE, + value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_HOST_SEARCH_BASE].opt_name, + value); + } + ret = sdap_parse_search_base(ipa_opts->id->basic, ldb, ipa_opts->id->basic, + SDAP_HOST_SEARCH_BASE, + &ipa_opts->id->sdom->host_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_HBAC_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=hbac,%s", basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_HBAC_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->basic[IPA_HBAC_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_HBAC_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts->basic, ldb, ipa_opts->basic, + IPA_HBAC_SEARCH_BASE, + &ipa_opts->hbac_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_SELINUX_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=selinux,%s", basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_SELINUX_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->basic[IPA_SELINUX_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_SELINUX_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts->basic, ldb, ipa_opts->basic, + IPA_SELINUX_SEARCH_BASE, + &ipa_opts->selinux_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_DESKPROFILE_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=desktop-profile,%s", basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_DESKPROFILE_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->basic[IPA_DESKPROFILE_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_DESKPROFILE_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts->basic, ldb, ipa_opts->basic, + IPA_DESKPROFILE_SEARCH_BASE, + &ipa_opts->deskprofile_search_bases); + if (ret != EOK) goto done; + +#ifdef BUILD_SUBID + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_SUBID_RANGES_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=subids,%s", + ipa_opts->id->sdom->search_bases[0]->basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_SUBID_RANGES_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->basic[IPA_SUBID_RANGES_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_SUBID_RANGES_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts->basic, ldb, ipa_opts->basic, + IPA_SUBID_RANGES_SEARCH_BASE, + &ipa_opts->id->sdom->subid_ranges_search_bases); + if (ret != EOK) goto done; + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_subid_map, + SDAP_OPTS_SUBID_RANGE, + &ipa_opts->id->subid_map); + if (ret != EOK) { + goto done; + } +#endif + + value = dp_opt_get_string(ipa_opts->id->basic, SDAP_DEREF); + if (value != NULL) { + ret = deref_string_to_val(value, &i); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to verify ldap_deref option.\n"); + goto done; + } + } + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_SERVICE_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=ipservices,%s", + dp_opt_get_string(ipa_opts->id->basic, + SDAP_SEARCH_BASE)); + if (!value) { + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_SERVICE_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_SERVICE_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_SERVICE_SEARCH_BASE)); + } + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_SERVICE_SEARCH_BASE, + &ipa_opts->id->sdom->service_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_SUBDOMAINS_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=trusts,%s", basedn); + if (value == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_SUBDOMAINS_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->basic[IPA_SUBDOMAINS_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_SUBDOMAINS_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts, ldb, ipa_opts->basic, + IPA_SUBDOMAINS_SEARCH_BASE, + &ipa_opts->subdomains_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_MASTER_DOMAIN_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=ad,cn=etc,%s", basedn); + if (value == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_MASTER_DOMAIN_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->basic[IPA_MASTER_DOMAIN_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_MASTER_DOMAIN_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts, ldb, ipa_opts->basic, + IPA_MASTER_DOMAIN_SEARCH_BASE, + &ipa_opts->master_domain_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_RANGES_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=ranges,cn=etc,%s", basedn); + if (value == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_RANGES_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->basic[IPA_RANGES_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_RANGES_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts, ldb, ipa_opts->basic, + IPA_RANGES_SEARCH_BASE, + &ipa_opts->ranges_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_VIEWS_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=views,cn=accounts,%s", basedn); + if (value == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_VIEWS_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->basic[IPA_VIEWS_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_VIEWS_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts, ldb, ipa_opts->basic, + IPA_VIEWS_SEARCH_BASE, + &ipa_opts->views_search_bases); + if (ret != EOK) goto done; + + ret = sdap_get_map(ipa_opts->id, cdb, conf_path, + ipa_attr_map, + SDAP_AT_GENERAL, + &ipa_opts->id->gen_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_user_map, + SDAP_OPTS_USER, + &ipa_opts->id->user_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_extend_map_with_list(ipa_opts->id, ipa_opts->id, + SDAP_USER_EXTRA_ATTRS, + ipa_opts->id->user_map, + SDAP_OPTS_USER, + &ipa_opts->id->user_map, + &ipa_opts->id->user_map_cnt); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_group_map, + SDAP_OPTS_GROUP, + &ipa_opts->id->group_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_netgroup_map, + IPA_OPTS_NETGROUP, + &ipa_opts->id->netgroup_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_host_map, + SDAP_OPTS_HOST, + &ipa_opts->id->host_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_hostgroup_map, + IPA_OPTS_HOSTGROUP, + &ipa_opts->hostgroup_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_service_map, + SDAP_OPTS_SERVICES, + &ipa_opts->id->service_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_selinux_user_map, + IPA_OPTS_SELINUX_USERMAP, + &ipa_opts->selinuxuser_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_view_map, + IPA_OPTS_VIEW, + &ipa_opts->view_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_override_map, + IPA_OPTS_OVERRIDE, + &ipa_opts->override_map); + if (ret != EOK) { + goto done; + } + + ret = EOK; + *_opts = ipa_opts->id; + +done: + talloc_zfree(tmpctx); + if (ret != EOK) { + talloc_zfree(ipa_opts->id); + } + return ret; +} + +int ipa_get_auth_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct dp_option **_opts) +{ + char *value; + char *copy = NULL; + int ret; + + ipa_opts->auth = talloc_zero(ipa_opts, struct dp_option); + if (ipa_opts->auth == NULL) { + ret = ENOMEM; + goto done; + } + + /* get krb5 options */ + ret = dp_get_options(ipa_opts, cdb, conf_path, + ipa_def_krb5_opts, + KRB5_OPTS, &ipa_opts->auth); + if (ret != EOK) { + goto done; + } + + /* If there is no KDC, try the deprecated krb5_kdcip option, too */ + /* FIXME - this can be removed in a future version */ + ret = krb5_try_kdcip(cdb, conf_path, ipa_opts->auth, KRB5_KDC); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_try_kdcip failed.\n"); + goto done; + } + + /* set krb realm */ + if (NULL == dp_opt_get_string(ipa_opts->auth, KRB5_REALM)) { + value = dp_opt_get_string(ipa_opts->basic, IPA_KRB5_REALM); + if (!value) { + ret = ENOMEM; + goto done; + } + copy = talloc_strdup(ipa_opts->auth, value); + if (copy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->auth, KRB5_REALM, copy); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->auth[KRB5_REALM].opt_name, + dp_opt_get_string(ipa_opts->auth, KRB5_REALM)); + } + + /* If krb5_fast_principal was not set explicitly, default to + * host/$client_hostname@REALM + */ + value = dp_opt_get_string(ipa_opts->auth, KRB5_FAST_PRINCIPAL); + if (value == NULL) { + value = talloc_asprintf(ipa_opts->auth, "host/%s@%s", + dp_opt_get_string(ipa_opts->basic, + IPA_HOSTNAME), + dp_opt_get_string(ipa_opts->auth, + KRB5_REALM)); + if (value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->auth, KRB5_FAST_PRINCIPAL, + value); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set %s!\n", + ipa_opts->auth[KRB5_FAST_PRINCIPAL].opt_name); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->auth[KRB5_FAST_PRINCIPAL].opt_name, value); + } + + /* Set flag that controls whether we want to write the + * kdcinfo files at all + */ + ipa_opts->service->krb5_service->write_kdcinfo = \ + dp_opt_get_bool(ipa_opts->auth, KRB5_USE_KDCINFO); + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->auth[KRB5_USE_KDCINFO].opt_name, + ipa_opts->service->krb5_service->write_kdcinfo ? "true" : "false"); + if (ipa_opts->service->krb5_service->write_kdcinfo) { + sss_krb5_parse_lookahead( + dp_opt_get_string(ipa_opts->auth, KRB5_KDCINFO_LOOKAHEAD), + &ipa_opts->service->krb5_service->lookahead_primary, + &ipa_opts->service->krb5_service->lookahead_backup); + } + + *_opts = ipa_opts->auth; + ret = EOK; + +done: + talloc_free(copy); + if (ret != EOK) { + talloc_zfree(ipa_opts->auth); + } + return ret; +} + +static void ipa_resolve_callback(void *private_data, struct fo_server *server) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct ipa_service *service; + struct resolv_hostent *srvaddr; + struct sockaddr *sockaddr; + char *new_uri; + const char *srv_name; + socklen_t sockaddr_len; + int ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed\n"); + return; + } + + service = talloc_get_type(private_data, struct ipa_service); + if (!service) { + DEBUG(SSSDBG_CRIT_FAILURE, "FATAL: Bad private_data\n"); + talloc_free(tmp_ctx); + return; + } + + srvaddr = fo_get_server_hostent(server); + if (!srvaddr) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No hostent available for server (%s)\n", + fo_get_server_str_name(server)); + talloc_free(tmp_ctx); + return; + } + + sockaddr = resolv_get_sockaddr_address(tmp_ctx, srvaddr, LDAP_PORT, &sockaddr_len); + if (sockaddr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "resolv_get_sockaddr_address failed.\n"); + talloc_free(tmp_ctx); + return; + } + + srv_name = fo_get_server_name(server); + if (srv_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not get server host name\n"); + talloc_free(tmp_ctx); + return; + } + + new_uri = talloc_asprintf(service, "ldap://%s", srv_name); + if (!new_uri) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy URI ...\n"); + talloc_free(tmp_ctx); + return; + } + DEBUG(SSSDBG_TRACE_FUNC, "Constructed uri '%s'\n", new_uri); + + /* free old one and replace with new one */ + talloc_zfree(service->sdap->uri); + service->sdap->uri = new_uri; + talloc_zfree(service->sdap->sockaddr); + service->sdap->sockaddr = talloc_steal(service, sockaddr); + service->sdap->sockaddr_len = sockaddr_len; + + if (service->krb5_service->write_kdcinfo) { + ret = write_krb5info_file_from_fo_server(service->krb5_service, + server, + true, + SSS_KRB5KDC_FO_SRV, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "write to %s/kdcinfo.%s failed, authentication might fail.\n", + PUBCONF_PATH, service->krb5_service->realm); + } + } + + talloc_free(tmp_ctx); +} + +static errno_t _ipa_servers_init(struct be_ctx *ctx, + struct ipa_service *service, + struct ipa_options *options, + const char *servers, + bool primary) +{ + TALLOC_CTX *tmp_ctx; + char **list = NULL; + char *ipa_domain; + int ret = 0; + int i; + int j; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + /* split server parm into a list */ + ret = split_on_separator(tmp_ctx, servers, ',', true, true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse server list!\n"); + goto done; + } + + for (j = 0; list[j]; j++) { + if (resolv_is_address(list[j])) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "ipa_server [%s] is detected as IP address, " + "this can cause GSSAPI problems\n", list[j]); + } + } + + /* now for each one add a new server to the failover service */ + for (i = 0; list[i]; i++) { + + talloc_steal(service, list[i]); + + if (be_fo_is_srv_identifier(list[i])) { + if (!primary) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add server [%s] to failover service: " + "SRV resolution only allowed for primary servers!\n", + list[i]); + continue; + } + + ipa_domain = dp_opt_get_string(options->basic, IPA_DOMAIN); + ret = be_fo_add_srv_server(ctx, "IPA", "ldap", ipa_domain, + BE_FO_PROTO_TCP, false, NULL); + if (ret) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added service lookup for service IPA\n"); + continue; + } + + /* It could be ipv6 address in square brackets. Remove + * the brackets if needed. */ + ret = remove_ipv6_brackets(list[i]); + if (ret != EOK) { + goto done; + } + + ret = be_fo_add_server(ctx, "IPA", list[i], 0, NULL, primary); + if (ret && ret != EEXIST) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added Server %s\n", list[i]); + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static inline errno_t +ipa_primary_servers_init(struct be_ctx *ctx, struct ipa_service *service, + struct ipa_options *options, const char *servers) +{ + return _ipa_servers_init(ctx, service, options, servers, true); +} + +static inline errno_t +ipa_backup_servers_init(struct be_ctx *ctx, struct ipa_service *service, + struct ipa_options *options, const char *servers) +{ + return _ipa_servers_init(ctx, service, options, servers, false); +} + +static int ipa_user_data_cmp(void *ud1, void *ud2) +{ + return strcasecmp((char*) ud1, (char*) ud2); +} + +int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *primary_servers, + const char *backup_servers, + struct ipa_options *options, + struct ipa_service **_service) +{ + TALLOC_CTX *tmp_ctx; + struct ipa_service *service; + char *realm; + int ret; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + realm = dp_opt_get_string(options->basic, IPA_KRB5_REALM); + if (!realm) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm set\n"); + ret = EINVAL; + goto done; + } + + service = talloc_zero(tmp_ctx, struct ipa_service); + if (!service) { + ret = ENOMEM; + goto done; + } + service->sdap = talloc_zero(service, struct sdap_service); + if (!service->sdap) { + ret = ENOMEM; + goto done; + } + + service->krb5_service = krb5_service_new(service, ctx, + "IPA", realm, + true, /* The configured value */ + 0, /* will be set later when */ + 0); /* the auth provider is set up */ + + if (!service->krb5_service) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_service(ctx, "IPA", ipa_user_data_cmp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create failover service!\n"); + goto done; + } + + service->sdap->name = talloc_strdup(service, "IPA"); + if (!service->sdap->name) { + ret = ENOMEM; + goto done; + } + + service->sdap->kinit_service_name = service->krb5_service->name; + + if (!primary_servers) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No primary servers defined, using service discovery\n"); + primary_servers = BE_SRV_IDENTIFIER; + } + + ret = ipa_primary_servers_init(ctx, service, options, primary_servers); + if (ret != EOK) { + goto done; + } + + if (backup_servers) { + ret = ipa_backup_servers_init(ctx, service, options, backup_servers); + if (ret != EOK) { + goto done; + } + } + + ret = be_fo_service_add_callback(memctx, ctx, "IPA", + ipa_resolve_callback, service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add failover callback!\n"); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *_service = talloc_steal(memctx, service); + } + talloc_zfree(tmp_ctx); + return ret; +} + +int ipa_get_autofs_options(struct ipa_options *ipa_opts, + struct ldb_context *ldb, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options **_opts) +{ + TALLOC_CTX *tmp_ctx; + char *basedn; + char *autofs_base; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = domain_to_basedn(tmp_ctx, + dp_opt_get_string(ipa_opts->basic, IPA_KRB5_REALM), + &basedn); + if (ret != EOK) { + goto done; + } + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_AUTOFS_SEARCH_BASE)) { + + autofs_base = talloc_asprintf(tmp_ctx, "cn=%s,cn=automount,%s", + dp_opt_get_string(ipa_opts->basic, + IPA_AUTOMOUNT_LOCATION), + basedn); + if (!autofs_base) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_AUTOFS_SEARCH_BASE, + autofs_base); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_AUTOFS_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_AUTOFS_SEARCH_BASE)); + } + + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_AUTOFS_SEARCH_BASE, + &ipa_opts->id->sdom->autofs_search_bases); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Could not parse autofs search base\n"); + goto done; + } + + ret = sdap_get_map(ipa_opts->id, cdb, conf_path, + ipa_autofs_mobject_map, + SDAP_OPTS_AUTOFS_MAP, + &ipa_opts->id->autofs_mobject_map); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not get autofs map object attribute map\n"); + goto done; + } + + ret = sdap_get_map(ipa_opts->id, cdb, conf_path, + ipa_autofs_entry_map, + SDAP_OPTS_AUTOFS_ENTRY, + &ipa_opts->id->autofs_entry_map); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not get autofs entry object attribute map\n"); + goto done; + } + + *_opts = ipa_opts->id; + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t ipa_get_dyndns_options(struct be_ctx *be_ctx, + struct ipa_options *ctx) +{ + errno_t ret; + char *val; + bool update; + int ttl; + + ret = be_nsupdate_init(ctx, be_ctx, ipa_dyndns_opts, &ctx->dyndns_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot initialize IPA dyndns opts [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (ctx->basic == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "IPA basic options not (yet) " + "initialized, cannot copy legacy options\n"); + return EOK; + } + + /* Reuse legacy option values */ + ret = confdb_get_string(be_ctx->cdb, ctx, be_ctx->conf_path, + "ipa_dyndns_update", NULL, &val); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get the value of %s\n", + "ipa_dyndns_update"); + /* Not fatal */ + } else if (ret == EOK && val) { + if (strcasecmp(val, "FALSE") == 0) { + update = false; + } else if (strcasecmp(val, "TRUE") == 0) { + update = true; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "ipa_dyndns_update value is not a boolean!\n"); + talloc_free(val); + return EINVAL; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Deprecation warning: The option %s is " + "deprecated and should not be used in favor of %s\n", + "ipa_dyndns_update", "dyndns_update"); + + ret = dp_opt_set_bool(ctx->dyndns_ctx->opts, + DP_OPT_DYNDNS_UPDATE, update); + talloc_free(val); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set option value\n"); + return ret; + } + } + + ret = confdb_get_int(be_ctx->cdb, be_ctx->conf_path, + "ipa_dyndns_ttl", -1, &ttl); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get the value of %s\n", + "ipa_dyndns_ttl"); + /* Not fatal */ + } else if (ret == EOK && ttl != -1) { + DEBUG(SSSDBG_MINOR_FAILURE, "Deprecation warning: The option %s is " + "deprecated and should not be used in favor of %s\n", + "ipa_dyndns_ttl", "dyndns_ttl"); + + ret = dp_opt_set_int(ctx->dyndns_ctx->opts, DP_OPT_DYNDNS_TTL, ttl); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set option value\n"); + return ret; + } + } + + /* Reuse legacy option values */ + ret = confdb_get_string(be_ctx->cdb, ctx, be_ctx->conf_path, + "ipa_dyndns_iface", NULL, &val); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get the value of %s\n", + "ipa_dyndns_iface"); + /* Not fatal */ + } else if (ret == EOK && val) { + DEBUG(SSSDBG_MINOR_FAILURE, "Deprecation warning: The option %s is " + "deprecated and should not be used in favor of %s\n", + "ipa_dyndns_iface", "dyndns_iface"); + + ret = dp_opt_set_string(ctx->dyndns_ctx->opts, + DP_OPT_DYNDNS_IFACE, val); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set option value\n"); + return ret; + } + } + + return EOK; +} + +errno_t ipa_get_host_attrs(struct dp_option *ipa_options, + size_t host_count, + struct sysdb_attrs **hosts, + struct sysdb_attrs **_ipa_host) +{ + const char *ipa_hostname; + const char *hostname; + errno_t ret; + + *_ipa_host = NULL; + ipa_hostname = dp_opt_get_cstring(ipa_options, IPA_HOSTNAME); + if (ipa_hostname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing ipa_hostname, this should never happen.\n"); + ret = EINVAL; + goto done; + } + + for (size_t i = 0; i < host_count; i++) { + ret = sysdb_attrs_get_string(hosts[i], SYSDB_FQDN, &hostname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not locate IPA host\n"); + goto done; + } + + if (strcasecmp(hostname, ipa_hostname) == 0) { + *_ipa_host = hosts[i]; + break; + } + } + + if (*_ipa_host == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not locate IPA host\n"); + ret = EINVAL; + goto done; + } + + ret = EOK; + +done: + return ret; +} diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h new file mode 100644 index 0000000..82b9622 --- /dev/null +++ b/src/providers/ipa/ipa_common.h @@ -0,0 +1,326 @@ +/* + SSSD + + IPA Common utility code + + Copyright (C) Simo Sorce 2009 + + 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 3 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; 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, see . +*/ + +#ifndef _IPA_COMMON_H_ +#define _IPA_COMMON_H_ + +#include "util/util.h" +#include "confdb/confdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/krb5/krb5_common.h" +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_srv.h" + +#define IPA_CN "cn" +#define IPA_TRUSTED_DOMAIN_SID "ipaNTTrustedDomainSID" +#define IPA_RANGE_TYPE "ipaRangeType" +#define IPA_BASE_ID "ipaBaseID" +#define IPA_ID_RANGE_SIZE "ipaIDRangeSize" +#define IPA_BASE_RID "ipaBaseRID" +#define IPA_SECONDARY_BASE_RID "ipaSecondaryBaseRID" +#define IPA_ID_RANGE_MPG "ipaAutoPrivateGroups" + +struct ipa_service { + struct sdap_service *sdap; + struct krb5_service *krb5_service; +}; + +struct ipa_init_ctx; + +enum ipa_basic_opt { + IPA_DOMAIN = 0, + IPA_SERVER, + IPA_BACKUP_SERVER, + IPA_HOSTNAME, + IPA_HBAC_SEARCH_BASE, + IPA_HOST_SEARCH_BASE, /* only used if ldap_host_search_base is not set */ + IPA_SELINUX_SEARCH_BASE, + IPA_SUBDOMAINS_SEARCH_BASE, + IPA_MASTER_DOMAIN_SEARCH_BASE, + IPA_KRB5_REALM, + IPA_HBAC_REFRESH, + IPA_SELINUX_REFRESH, + IPA_HBAC_SUPPORT_SRCHOST, + IPA_AUTOMOUNT_LOCATION, + IPA_RANGES_SEARCH_BASE, + IPA_ENABLE_DNS_SITES, + IPA_SERVER_MODE, + IPA_VIEWS_SEARCH_BASE, + IPA_KRB5_CONFD_PATH, + IPA_DESKPROFILE_SEARCH_BASE, + IPA_DESKPROFILE_REFRESH, + IPA_DESKPROFILE_REQUEST_INTERVAL, + IPA_SUBID_RANGES_SEARCH_BASE, + IPA_ACCESS_ORDER, + + IPA_OPTS_BASIC /* opts counter */ +}; + +enum ipa_netgroup_attrs { + IPA_OC_NETGROUP = 0, + IPA_AT_NETGROUP_NAME, + IPA_AT_NETGROUP_MEMBER, + IPA_AT_NETGROUP_MEMBER_OF, + IPA_AT_NETGROUP_MEMBER_USER, + IPA_AT_NETGROUP_MEMBER_HOST, + IPA_AT_NETGROUP_EXTERNAL_HOST, + IPA_AT_NETGROUP_DOMAIN, + IPA_AT_NETGROUP_UUID, + + IPA_OPTS_NETGROUP /* attrs counter */ +}; + +enum ipa_hostgroup_attrs { + IPA_OC_HOSTGROUP = 0, + IPA_AT_HOSTGROUP_NAME, + IPA_AT_HOSTGROUP_MEMBER_OF, + IPA_AT_HOSTGROUP_UUID, + + IPA_OPTS_HOSTGROUP /* attrs counter */ +}; + +enum ipa_selinux_usermap_attrs { + IPA_OC_SELINUX_USERMAP = 0, + IPA_AT_SELINUX_USERMAP_NAME, + IPA_AT_SELINUX_USERMAP_MEMBER_USER, + IPA_AT_SELINUX_USERMAP_MEMBER_HOST, + IPA_AT_SELINUX_USERMAP_SEE_ALSO, + IPA_AT_SELINUX_USERMAP_SELINUX_USER, + IPA_AT_SELINUX_USERMAP_ENABLED, + IPA_AT_SELINUX_USERMAP_USERCAT, + IPA_AT_SELINUX_USERMAP_HOSTCAT, + IPA_AT_SELINUX_USERMAP_UUID, + + IPA_OPTS_SELINUX_USERMAP /* attrs counter */ +}; + +enum ipa_view_attrs { + IPA_OC_VIEW = 0, + IPA_AT_VIEW_NAME, + + IPA_OPTS_VIEW +}; + +enum ipa_override_attrs { + IPA_OC_OVERRIDE = 0, + IPA_AT_OVERRIDE_ANCHOR_UUID, + IPA_OC_OVERRIDE_USER, + IPA_OC_OVERRIDE_GROUP, + IPA_AT_OVERRIDE_USER_NAME, + IPA_AT_OVERRIDE_UID_NUMBER, + IPA_AT_OVERRIDE_USER_GID_NUMBER, + IPA_AT_OVERRIDE_GECOS, + IPA_AT_OVERRIDE_HOMEDIR, + IPA_AT_OVERRIDE_SHELL, + IPA_AT_OVERRIDE_GROUP_NAME, + IPA_AT_OVERRIDE_GROUP_GID_NUMBER, + IPA_AT_OVERRIDE_USER_SSH_PUBLIC_KEY, + IPA_AT_OVERRIDE_USER_CERT, + IPA_AT_OVERRIDE_OBJECTCLASS, + + IPA_OPTS_OVERRIDE +}; + +enum ipa_sudorule_attrs { + IPA_OC_SUDORULE = 0, + IPA_AT_SUDORULE_NAME, + IPA_AT_SUDORULE_UUID, + IPA_AT_SUDORULE_ENABLED, + IPA_AT_SUDORULE_OPTION, + IPA_AT_SUDORULE_RUNASUSER, + IPA_AT_SUDORULE_RUNASGROUP, + IPA_AT_SUDORULE_ALLOWCMD, + IPA_AT_SUDORULE_DENYCMD, + IPA_AT_SUDORULE_HOST, + IPA_AT_SUDORULE_USER, + IPA_AT_SUDORULE_NOTAFTER, + IPA_AT_SUDORULE_NOTBEFORE, + IPA_AT_SUDORULE_SUDOORDER, + IPA_AT_SUDORULE_CMDCATEGORY, + IPA_AT_SUDORULE_HOSTCATEGORY, + IPA_AT_SUDORULE_USERCATEGORY, + IPA_AT_SUDORULE_RUNASUSERCATEGORY, + IPA_AT_SUDORULE_RUNASGROUPCATEGORY, + IPA_AT_SUDORULE_RUNASEXTUSER, + IPA_AT_SUDORULE_RUNASEXTGROUP, + IPA_AT_SUDORULE_RUNASEXTUSERGROUP, + IPA_AT_SUDORULE_EXTUSER, + IPA_AT_SUDORULE_ENTRYUSN, + + IPA_OPTS_SUDORULE +}; + +enum ipa_sudocmdgroup_attrs { + IPA_OC_SUDOCMDGROUP = 0, + IPA_AT_SUDOCMDGROUP_UUID, + IPA_AT_SUDOCMDGROUP_NAME, + IPA_AT_SUDOCMDGROUP_MEMBER, + IPA_AT_SUDOCMDGROUP_ENTRYUSN, + + IPA_OPTS_SUDOCMDGROUP +}; + +enum ipa_sudocmd_attrs { + IPA_OC_SUDOCMD = 0, + IPA_AT_SUDOCMD_UUID, + IPA_AT_SUDOCMD_CMD, + IPA_AT_SUDOCMD_MEMBEROF, + + IPA_OPTS_SUDOCMD +}; + +enum ipa_cli_ad_subdom_attrs { + IPA_CLI_AD_SERVER, + IPA_CLI_AD_SITE, + + IPA_OPTS_CLI_AD_SUBDOM +}; + +struct ipa_auth_ctx { + struct krb5_ctx *krb5_auth_ctx; + struct sdap_id_ctx *sdap_id_ctx; + struct sdap_auth_ctx *sdap_auth_ctx; + struct dp_option *ipa_options; +}; + +/* In server mode, each subdomain corresponds to an AD context */ + +struct ipa_id_ctx { + struct sdap_id_ctx *sdap_id_ctx; + struct ipa_options *ipa_options; + + char *view_name; + /* Only used with server mode */ + struct ipa_server_mode_ctx *server_mode; +}; + +struct ipa_options { + struct dp_option *basic; + + struct sdap_attr_map *hostgroup_map; + struct sdap_attr_map *selinuxuser_map; + struct sdap_attr_map *view_map; + struct sdap_attr_map *override_map; + + struct sdap_search_base **hbac_search_bases; + struct sdap_search_base **selinux_search_bases; + struct sdap_search_base **subdomains_search_bases; + struct sdap_search_base **master_domain_search_bases; + struct sdap_search_base **ranges_search_bases; + struct sdap_search_base **views_search_bases; + struct sdap_search_base **deskprofile_search_bases; + struct ipa_service *service; + + /* id provider */ + struct sdap_options *id; + struct ipa_id_ctx *id_ctx; + struct be_resolv_ctx *be_res; + struct be_nsupdate_ctx *dyndns_ctx; + + /* auth and chpass provider */ + struct dp_option *auth; + struct ipa_auth_ctx *auth_ctx; +}; + +#define IPA_RANGE_LOCAL "ipa-local" +#define IPA_RANGE_AD_TRUST "ipa-ad-trust" +#define IPA_RANGE_AD_TRUST_POSIX "ipa-ad-trust-posix" + +/* options parsers */ +int ipa_get_options(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sss_domain_info *dom, + struct ipa_options **_opts); + +int ipa_get_id_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sdap_options **_opts); + +int ipa_get_auth_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct dp_option **_opts); + +int ipa_get_autofs_options(struct ipa_options *ipa_opts, + struct ldb_context *ldb, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options **_opts); + +errno_t ipa_get_dyndns_options(struct be_ctx *be_ctx, + struct ipa_options *ctx); + +errno_t ipa_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods); + +errno_t ipa_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods); + +int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *primary_servers, + const char *backup_servers, + struct ipa_options *options, + struct ipa_service **_service); + +int ipa_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods); + +errno_t get_idmap_data_from_range(struct range_info *r, char *domain_name, + char **_name, char **_sid, uint32_t *_rid, + struct sss_idmap_range *_range, + bool *_external_mapping); + +errno_t ipa_ranges_parse_results(TALLOC_CTX *mem_ctx, + char *domain_name, + size_t count, + struct sysdb_attrs **reply, + struct range_info ***_range_list); + +errno_t ipa_idmap_get_ranges_from_sysdb(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str, + bool allow_collisions); + +errno_t ipa_idmap_init(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_idmap_ctx **_idmap_ctx); + + +struct krb5_ctx *ipa_init_get_krb5_auth_ctx(void *data); + +errno_t ipa_get_host_attrs(struct dp_option *ipa_options, + size_t host_count, + struct sysdb_attrs **hosts, + struct sysdb_attrs **_ipa_host); + +errno_t ipa_refresh_init(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx); + +#endif /* _IPA_COMMON_H_ */ diff --git a/src/providers/ipa/ipa_config.c b/src/providers/ipa/ipa_config.c new file mode 100644 index 0000000..23f0c58 --- /dev/null +++ b/src/providers/ipa/ipa_config.c @@ -0,0 +1,165 @@ +/* + SSSD + + IPA Backend Module -- configuration retrieval + + Authors: + Jan Zeleny + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ipa/ipa_config.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ldap/sdap_async.h" + +struct ipa_get_config_state { + char *base; + const char **attrs; + + struct sysdb_attrs *config; +}; + +static void ipa_get_config_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_get_config_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *domain, + const char **attrs, + const char *filter, + const char *base) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_get_config_state *state; + errno_t ret; + char *ldap_basedn; + + req = tevent_req_create(mem_ctx, &state, struct ipa_get_config_state); + if (req == NULL) { + return NULL; + } + + if (attrs == NULL) { + state->attrs = talloc_zero_array(state, const char *, 4); + if (state->attrs == NULL) { + ret = ENOMEM; + goto done; + } + state->attrs[0] = IPA_CONFIG_MIGRATION_ENABLED; + state->attrs[1] = IPA_CONFIG_SELINUX_DEFAULT_USER_CTX; + state->attrs[2] = IPA_CONFIG_SELINUX_MAP_ORDER; + state->attrs[3] = NULL; + } else { + state->attrs = attrs; + } + + if (filter == NULL) { + filter = IPA_CONFIG_FILTER; + } + + ret = domain_to_basedn(state, domain, &ldap_basedn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "domain_to_basedn failed.\n"); + goto done; + } + + if (base == NULL) { + base = IPA_CONFIG_SEARCH_BASE_TEMPLATE; + } + state->base = talloc_asprintf(state, base, ldap_basedn); + if (state->base == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_get_generic_send(state, ev, opts, + sh, state->base, + LDAP_SCOPE_SUBTREE, filter, + state->attrs, NULL, 0, + dp_opt_get_int(opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_get_config_done, req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ipa_get_config_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_config_state *state = tevent_req_data(req, + struct ipa_get_config_state); + size_t reply_count; + struct sysdb_attrs **reply = NULL; + errno_t ret; + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret) { + goto done; + } + + if (reply_count != 1) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected number of results, expected 1, " + "got %zu.\n", reply_count); + ret = EINVAL; + goto done; + } + + state->config = reply[0]; + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } +} + +errno_t ipa_get_config_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **config) +{ + struct ipa_get_config_state *state = tevent_req_data(req, + struct ipa_get_config_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *config = talloc_steal(mem_ctx, state->config); + + return EOK; +} diff --git a/src/providers/ipa/ipa_config.h b/src/providers/ipa/ipa_config.h new file mode 100644 index 0000000..a3a1523 --- /dev/null +++ b/src/providers/ipa/ipa_config.h @@ -0,0 +1,55 @@ +/* + SSSD + + IPA Backend Module -- configuration retrieval header + + Authors: + Jan Zeleny + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_CONFIG_H_ +#define IPA_CONFIG_H_ + +#include +#include + +#include "providers/ldap/ldap_common.h" +#include "db/sysdb.h" + +#define IPA_CONFIG_SELINUX_DEFAULT_USER_CTX "ipaSELinuxUserMapDefault" +#define IPA_CONFIG_SELINUX_MAP_ORDER "ipaSELinuxUserMapOrder" +#define IPA_CONFIG_MIGRATION_ENABLED "ipaMigrationEnabled" +#define IPA_CONFIG_SEARCH_BASE_TEMPLATE "cn=etc,%s" +#define IPA_CONFIG_FILTER "(&(cn=ipaConfig)(objectClass=ipaGuiConfig))" + +#define IPA_OC_CONFIG "ipaConfig" + +struct tevent_req * ipa_get_config_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *domain, + const char **attrs, + const char *filter, + const char *base); + +errno_t ipa_get_config_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **config); + +#endif /* IPA_CONFIG_H_ */ diff --git a/src/providers/ipa/ipa_deskprofile_config.c b/src/providers/ipa/ipa_deskprofile_config.c new file mode 100644 index 0000000..8c66dda --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_config.c @@ -0,0 +1,156 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio + + Copyright (C) 2017 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_deskprofile_private.h" +#include "providers/ipa/ipa_deskprofile_config.h" +#include "providers/ldap/sdap_async.h" + +struct ipa_deskprofile_config_state { + struct sysdb_attrs *config; +}; + +static void +ipa_deskprofile_get_config_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_deskprofile_get_config_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct dp_option *ipa_opts) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq; + struct ipa_deskprofile_rule_state *state; + char *rule_filter; + const char *attrs[] = { IPA_DESKPROFILE_PRIORITY, NULL }; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_deskprofile_config_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed.\n"); + return NULL; + } + + rule_filter = talloc_asprintf(state, "(objectclass=%s)", + IPA_DESKPROFILE_CONFIG); + if (rule_filter == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = sdap_get_generic_send(state, ev, opts, sh, + dp_opt_get_string(ipa_opts, + IPA_DESKPROFILE_SEARCH_BASE), + LDAP_SCOPE_BASE, rule_filter, + attrs, NULL, 0, + dp_opt_get_int(opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_send failed.\n"); + goto done; + } + + tevent_req_set_callback(subreq, ipa_deskprofile_get_config_done, req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void +ipa_deskprofile_get_config_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_deskprofile_config_state *state; + size_t reply_count; + struct sysdb_attrs **reply = NULL; + errno_t ret; + + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_deskprofile_config_state); + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not retrieve Desktop Profile config\n"); + goto done; + } + + if (reply_count == 0) { + /* + * When connecting to an old server that doesn't support Desktop + * Profile, the reply_count will be zero. + * In order to not throw a unnecessary error and fail let's just + * return ENOENT and print a debug message about it. + */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Server doesn't support Desktop Profile.\n"); + ret = ENOENT; + goto done; + } else if (reply_count != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected number of results, expected 1, got %zu.\n", + reply_count); + ret = EINVAL; + goto done; + } + + state->config = reply[0]; + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +ipa_deskprofile_get_config_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **config) +{ + struct ipa_deskprofile_config_state *state; + + state = tevent_req_data(req, struct ipa_deskprofile_config_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + + *config = talloc_steal(mem_ctx, state->config); + + return EOK; +} diff --git a/src/providers/ipa/ipa_deskprofile_config.h b/src/providers/ipa/ipa_deskprofile_config.h new file mode 100644 index 0000000..c4a05b2 --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_config.h @@ -0,0 +1,45 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio + + Copyright (C) 2017 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_DESKPROFILE_CONFIG_H_ +#define IPA_DESKPROFILE_CONFIG_H_ + +#include +#include + +#include "providers/ldap/ldap_common.h" +#include "db/sysdb.h" + +/* From ipa_deskprofile_config.c */ +struct tevent_req * +ipa_deskprofile_get_config_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct dp_option *ipa_opts); + +errno_t +ipa_deskprofile_get_config_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **config); + +#endif /* IPA_DESKPROFILE_CONFIG_H_ */ diff --git a/src/providers/ipa/ipa_deskprofile_private.h b/src/providers/ipa/ipa_deskprofile_private.h new file mode 100644 index 0000000..1db154b --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_private.h @@ -0,0 +1,50 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio + + Copyright (C) 2017 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_DESKPROFILE_PRIVATE_H_ +#define IPA_DESKPROFILE_PRIVATE_H_ + +#define IPA_DESKPROFILE_CONFIG "ipaDeskProfileConfig" +#define IPA_DESKPROFILE_RULE "ipaDeskProfileRule" +#define IPA_DESKPROFILE_PRIORITY "ipaDeskProfilePriority" +#define IPA_DESKPROFILE_DATA "ipaDeskData" + +#define DESKPROFILE_HOSTS_SUBDIR "deskprofile_hosts" +#define DESKPROFILE_HOSTGROUPS_SUBDIR "deskprofile_hostgroups" + +#define IPA_SESSION_RULE_TYPE "sessionRuleType" + +#define IPA_DESKPROFILE_BASE_TMPL "cn=desktop-profile,%s" + +#define SYSDB_DESKPROFILE_BASE_TMPL "cn=desktop-profile,"SYSDB_TMPL_CUSTOM_BASE + +#define DESKPROFILE_RULES_SUBDIR "deskprofile_rules" + +#define DESKPROFILE_CONFIG_SUBDIR "deskprofile_config" + +struct deskprofile_rule { + const char *name; + int priority; + const char *data; +}; + +#endif /* IPA_DESKPROFILE_PRIVATE_H_ */ diff --git a/src/providers/ipa/ipa_deskprofile_rules.c b/src/providers/ipa/ipa_deskprofile_rules.c new file mode 100644 index 0000000..cce6184 --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_rules.c @@ -0,0 +1,367 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio + + Copyright (C) 2017 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ipa/ipa_deskprofile_private.h" +#include "providers/ipa/ipa_deskprofile_rules.h" +#include "providers/ipa/ipa_deskprofile_rules_util.h" + +struct ipa_deskprofile_rule_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_options *opts; + + int search_base_iter; + struct sdap_search_base **search_bases; + + const char **attrs; + char *rules_filter; + char *cur_filter; + + size_t rule_count; + struct sysdb_attrs **rules; +}; + +static errno_t +ipa_deskprofile_rule_info_next(struct tevent_req *req, + struct ipa_deskprofile_rule_state *state); +static void +ipa_deskprofile_rule_info_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_deskprofile_rule_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sysdb_attrs *ipa_host, + struct sss_domain_info *domain, + const char *username) +{ + struct tevent_req *req = NULL; + struct ipa_deskprofile_rule_state *state; + char *user; + char *group; + char *host_dn_clean; + char *group_clean; + char *host_group_clean; + char *rule_filter; + const char *host_dn; + const char **memberof_list; + char **groups_list; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ipa_deskprofile_rule_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + if (ipa_host == NULL) { + ret = EINVAL; + DEBUG(SSSDBG_CRIT_FAILURE, "Missing host\n"); + goto immediate; + } + + ret = sysdb_attrs_get_string(ipa_host, SYSDB_ORIG_DN, &host_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify IPA hostname\n"); + goto immediate; + } + + ret = sss_filter_sanitize_dn(state, host_dn, &host_dn_clean); + if (ret != EOK) { + goto immediate; + } + + state->ev = ev; + state->sh = sh; + state->opts = opts; + state->search_bases = search_bases; + state->search_base_iter = 0; + state->attrs = deskprofile_get_attrs_to_get_cached_rules(state); + if (state->attrs == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, + "deskprofile_get_attrs_get_cached_rules() failed\n"); + goto immediate; + } + + rule_filter = talloc_asprintf(state, + "(&(objectclass=%s)" + "(%s=%s)" + "(|(%s=%s)(%s=%s)(%s=%s)", + IPA_DESKPROFILE_RULE, + IPA_ENABLED_FLAG, IPA_TRUE_VALUE, + IPA_HOST_CATEGORY, "all", + IPA_USER_CATEGORY, "all", + IPA_MEMBER_HOST, host_dn_clean); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + /* Add all parent groups of ipa_hostname to the filter */ + ret = sysdb_attrs_get_string_array(ipa_host, SYSDB_ORIG_MEMBEROF, + state, &memberof_list); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify.\n"); + } else if (ret == ENOENT) { + /* This host is not a member of any hostgroups */ + memberof_list = talloc_array(state, const char *, 1); + if (memberof_list == NULL) { + ret = ENOMEM; + goto immediate; + } + memberof_list[0] = NULL; + } + + for (size_t i = 0; memberof_list[i] != NULL; i++) { + ret = sss_filter_sanitize(state, + memberof_list[i], + &host_group_clean); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_filter_sanitize() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + rule_filter = talloc_asprintf_append(rule_filter, "(%s=%s)", + IPA_MEMBER_HOST, + host_group_clean); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + } + + /* Add the username to the filter */ + ret = sss_parse_internal_fqname(state, username, &user, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_parse_internal_fqname() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + rule_filter = talloc_asprintf_append(rule_filter, "(%s=%s)", + IPA_MEMBER_USER, user); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + /* Add all parent groups of `username` to the filter */ + ret = get_sysdb_grouplist(state, domain->sysdb, domain, username, + &groups_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "get_sysdb_grouplist() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + for (size_t i = 0; groups_list[i] != NULL; i++) { + ret = sss_filter_sanitize(state, groups_list[i], &group_clean); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_filter_sanitize() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + ret = sss_parse_internal_fqname(state, group_clean, &group, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_parse_internal_fqname() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + rule_filter = talloc_asprintf_append(rule_filter, "(%s=%s)", + IPA_MEMBER_USER, group); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + } + + rule_filter = talloc_asprintf_append(rule_filter, "))"); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + state->rules_filter = talloc_steal(state, rule_filter); + + ret = ipa_deskprofile_rule_info_next(req, state); + if (ret != EAGAIN) { + if (ret == EOK) { + /* ipa_deskprofile_rule_info_next should always have a search base + * when called for the first time. + * + * For the subsequent iterations, not finding any more search bases + * is fine though (thus the function returns EOK). + * + * As, here, it's the first case happening, let's return EINVAL. + */ + DEBUG(SSSDBG_CRIT_FAILURE, "No search base found\n"); + ret = EINVAL; + } + goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t +ipa_deskprofile_rule_info_next(struct tevent_req *req, + struct ipa_deskprofile_rule_state *state) +{ + struct tevent_req *subreq; + struct sdap_search_base *base; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->rules_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Sending request for next search base: [%s][%d][%s]\n", + base->basedn, base->scope, state->cur_filter); + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base->basedn, base->scope, + state->cur_filter, state->attrs, + NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_deskprofile_rule_info_done, req); + + return EAGAIN; +} + +static void +ipa_deskprofile_rule_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req; + struct ipa_deskprofile_rule_state *state; + size_t rule_count; + size_t total_count; + struct sysdb_attrs **rules; + struct sysdb_attrs **target; + int i; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_deskprofile_rule_state); + + ret = sdap_get_generic_recv(subreq, state, + &rule_count, + &rules); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not retrieve Desktop Profile rules\n"); + goto fail; + } + + if (rule_count > 0) { + total_count = rule_count + state->rule_count; + state->rules = talloc_realloc(state, state->rules, + struct sysdb_attrs *, + total_count); + if (state->rules == NULL) { + ret = ENOMEM; + goto fail; + } + + i = 0; + while (state->rule_count < total_count) { + target = &state->rules[state->rule_count]; + *target = talloc_steal(state->rules, rules[i]); + + state->rule_count++; + i++; + } + } + + state->search_base_iter++; + ret = ipa_deskprofile_rule_info_next(req, state); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + goto fail; + } else if (ret == EOK && state->rule_count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No rules apply to this host\n"); + tevent_req_error(req, ENOENT); + return; + } + + /* We went through all search bases and we have some results */ + tevent_req_done(req); + + return; + +fail: + tevent_req_error(req, ret); +} + +errno_t +ipa_deskprofile_rule_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_rule_count, + struct sysdb_attrs ***_rules) +{ + struct ipa_deskprofile_rule_state *state; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + state = tevent_req_data(req, struct ipa_deskprofile_rule_state); + + *_rule_count = state->rule_count; + *_rules = talloc_steal(mem_ctx, state->rules); + + return EOK; +} diff --git a/src/providers/ipa/ipa_deskprofile_rules.h b/src/providers/ipa/ipa_deskprofile_rules.h new file mode 100644 index 0000000..313e526 --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_rules.h @@ -0,0 +1,43 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio + + Copyright (C) 2017 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_DESKPROFILE_RULES_H_ +#define IPA_DESKPROFILE_RULES_H_ + +/* From ipa_deskprofile_rules.c */ +struct tevent_req * +ipa_deskprofile_rule_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sysdb_attrs *ipa_host, + struct sss_domain_info *domain, + const char *username); + +errno_t +ipa_deskprofile_rule_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *rule_count, + struct sysdb_attrs ***rules); + +#endif /* IPA_DESKPROFILE_RULES_H_ */ diff --git a/src/providers/ipa/ipa_deskprofile_rules_util.c b/src/providers/ipa/ipa_deskprofile_rules_util.c new file mode 100644 index 0000000..d6fa3cc --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_rules_util.c @@ -0,0 +1,1147 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio + + Copyright (C) 2017 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ipa/ipa_deskprofile_rules_util.h" +#include "providers/ipa/ipa_deskprofile_private.h" +#include "providers/ipa/ipa_rules_common.h" +#include +#include + +#define DESKPROFILE_GLOBAL_POLICY_MIN_VALUE 1 +#define DESKPROFILE_GLOBAL_POLICY_MAX_VALUE 24 + +enum deskprofile_name { + RULES_DIR = 0, + DOMAIN, + USERNAME, + PRIORITY, + USER, + GROUP, + HOST, + HOSTGROUP, + RULE_NAME, + EXTENSION, + DESKPROFILE_NAME_SENTINEL +}; + +/* + * The rule's filename has to follow a global policy, used by FleetCommander + * client that shows how the profile should be applied. + * + * This global policy is represented by an integer from 1 to 24 (inclusive) and + * has the following meaning: + * 1 = user, group, host, hostgroup + * 2 = user, group, hostgroup, host + * 3 = user, host, group, hostgroup + * 4 = user, host, hostgroup, group + * 5 = user, hostgroup, group, host + * 6 = user, hostgroup, host, group + * 7 = group, user, host, hostgroup + * 8 = group, user, hostgroup, host + * 9 = group, host, user, hostgroup + * 10 = group, host, hostgroup, user + * 11 = group, hostgroup, user, host + * 12 = group, hostgroup, host, user + * 13 = host, user, group, hostgroup + * 14 = host, user, hostgroup, group + * 15 = host, group, user, hostgroup + * 16 = host, group, hostgroup, user + * 17 = host, hostgroup, user, group + * 18 = host, hostgroup, group, user + * 19 = hostgroup, user, group, host + * 20 = hostgroup, user, host, group + * 21 = hostgroup, group, user, host + * 22 = hostgroup, group, host, user + * 23 = hostgroup, host, user, group + * 24 = hostgroup, host, group, user + * + * Having the table above in mind and considering the following example: + * - rule name: testrule + * - policy: 22 + * - priority: 420 + * - client's machine matches: host and group + * + * So, the filename will be: "000420_000000_000420_000420_000000_testrule.json" + * + * The function below not only helps us to create this filename in the correct + * format, but also create the whole path for this rule's file. + * + * An example of the full path would be: + * "/var/lib/sss/deskprofile/ipa.example/user_foobar/000420_000000_000420_000420_000000_testrule.json" + * | RULES DIR | DOMAIN | USERNAME | | |GROUP | HOST | USER | | + * PRIORITY RULE NAME + * HOSTGROUP EXTENSION + * + * In case a element has to be added/remove, please, remember to update: + * - deskprofile_name enum; + * - permuts's matrix; + * - vals array; + */ +errno_t +ipa_deskprofile_get_filename_path(TALLOC_CTX *mem_ctx, + uint16_t config_priority, + const char *rules_dir, + const char *domain, + const char *username, + const char *priority, + const char *user_priority, + const char *group_priority, + const char *host_priority, + const char *hostgroup_priority, + const char *rule_name, + const char *extension, + char **_filename_path) +{ + TALLOC_CTX *tmp_ctx; + static const uint8_t permuts[][DESKPROFILE_NAME_SENTINEL] = { + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, GROUP, HOST, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, GROUP, HOSTGROUP, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, HOST, GROUP, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, HOST, HOSTGROUP, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, HOSTGROUP, GROUP, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, HOSTGROUP, HOST, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, USER, HOST, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, USER, HOSTGROUP, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, HOST, USER, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, HOST, HOSTGROUP, USER, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, HOSTGROUP, USER, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, HOSTGROUP, HOST, USER, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, USER, GROUP, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, USER, HOSTGROUP, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, GROUP, USER, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, GROUP, HOSTGROUP, USER, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, HOSTGROUP, USER, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, HOSTGROUP, GROUP, USER, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, USER, GROUP, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, USER, HOST, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, GROUP, USER, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, GROUP, HOST, USER, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, HOST, USER, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, HOST, GROUP, USER, RULE_NAME, EXTENSION}, + }; + const char *vals[] = { + rules_dir, + domain, + username, + priority, + user_priority, + group_priority, + host_priority, + hostgroup_priority, + rule_name, + extension, + NULL, + }; + const uint8_t *perms; + char *result; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + if (config_priority < DESKPROFILE_GLOBAL_POLICY_MIN_VALUE || + config_priority > DESKPROFILE_GLOBAL_POLICY_MAX_VALUE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "The configuration priority has an invalid value: %d!\n", + config_priority); + ret = EINVAL; + goto done; + } + + perms = permuts[config_priority - 1]; + + result = talloc_strdup(tmp_ctx, ""); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < DESKPROFILE_NAME_SENTINEL; i++) { + switch(perms[i]) { + case RULES_DIR: + case DOMAIN: + case USERNAME: + result = talloc_asprintf_append(result, "%s/", vals[perms[i]]); + break; + case PRIORITY: + case USER: + case GROUP: + case HOST: + case HOSTGROUP: + result = talloc_asprintf_append(result, "%s_", vals[perms[i]]); + break; + case RULE_NAME: + result = talloc_asprintf_append(result, "%s", vals[perms[i]]); + break; + case EXTENSION: + result = talloc_asprintf_append(result, ".%s", vals[perms[i]]); + break; + default: + DEBUG(SSSDBG_MINOR_FAILURE, + "This situation should never happen\n"); + ret = EINVAL; + goto done; + } + + if (result == NULL) { + ret = ENOMEM; + goto done; + } + } + + *_filename_path = talloc_steal(mem_ctx, result); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ipa_deskprofile_rules_create_user_dir( + const char *username, /* fully-qualified */ + uid_t uid, + gid_t gid) +{ + TALLOC_CTX *tmp_ctx; + char *shortname; + char *domain; + char *domain_dir; + errno_t ret; + mode_t old_umask; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmp_ctx, username, &shortname, &domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_parse_internal_fqname() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + old_umask = umask(0026); + ret = sss_create_dir(IPA_DESKPROFILE_RULES_USER_DIR, domain, 0751, + getuid(), getgid()); + umask(old_umask); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to create the directory \"%s/%s\" that would be used to " + "store the Desktop Profile rules users' directory [%d]: %s\n", + IPA_DESKPROFILE_RULES_USER_DIR, domain, + ret, sss_strerror(ret)); + goto done; + } + + domain_dir = talloc_asprintf(tmp_ctx, IPA_DESKPROFILE_RULES_USER_DIR"/%s", + domain); + if (domain_dir == NULL) { + ret = ENOMEM; + goto done; + } + + /* In order to read, create and traverse the directory, we need to have its + * permissions set as 'rwx------' (700). */ + old_umask = umask(0077); + ret = sss_create_dir(domain_dir, shortname, 0700, uid, gid); + umask(old_umask); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to create the directory \"%s/%s/%s\" that would be used " + "to store the Desktop Profile rules for the user \"%s\" [%d]: " + "%s\n", + IPA_DESKPROFILE_RULES_USER_DIR, domain, shortname, username, + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_deskprofile_get_normalized_rule_name(TALLOC_CTX *mem_ctx, + const char *name, + char **_rule_name) +{ + char buffer[PATH_MAX]; + size_t buffer_len; + size_t name_len; + + name_len = strlen(name); + buffer_len = 0; + for (size_t i = 0; i < name_len; i++) { + char character; + bool replace; + + character = name[i]; + replace = false; + + if (isalnum(character) == 0) { + char next_character; + + next_character = name[i+1]; + if (i + 1 >= name_len || isalnum(next_character) == 0) { + continue; + } + + replace = true; + } + + buffer[buffer_len] = replace ? '_' : character; + buffer_len++; + } + buffer[buffer_len] = '\0'; + + *_rule_name = talloc_strdup(mem_ctx, buffer); + if (*_rule_name == NULL) { + return ENOMEM; + } + + return EOK; +} + +static errno_t +ipa_deskprofile_rule_check_memberuser( + TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sysdb_attrs *rule, + const char *rule_name, + const char *rule_prio, + const char *base_dn, + const char *username, /* fully-qualified */ + char **_user_prio, + char **_group_prio) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *el; + struct ldb_result *res; + size_t num_groups; + char **groups = NULL; + const char *fqgroupname = NULL; + char *groupname = NULL; + char *shortname; + char *domainname; + char *data; + char *memberuser; + char *membergroup; + char *user_prio; + char *group_prio; + bool user = false; + bool group = false; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmp_ctx, username, + &shortname, &domainname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_parse_internal_fqname() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_initgroups(tmp_ctx, domain, username, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_initgroups() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (res->count == 0) { + /* This really should NOT happen at this point */ + DEBUG(SSSDBG_MINOR_FAILURE, + "User [%s] not found in cache\n", username); + ret = ENOENT; + goto done; + } + + groups = talloc_array(tmp_ctx, char *, res->count); + if (groups == NULL) { + ret = ENOMEM; + goto done; + } + + num_groups = 0; + /* Start counting from 1 to exclude the user entry */ + for (size_t i = 1; i < res->count; i++) { + fqgroupname = ldb_msg_find_attr_as_string(res->msgs[i], + SYSDB_NAME, + NULL); + if (fqgroupname == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Skipping malformed entry [%s]\n", + ldb_dn_get_linearized(res->msgs[i]->dn)); + continue; + } + + ret = sss_parse_internal_fqname(tmp_ctx, fqgroupname, + &groupname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Malformed name %s, skipping!\n", fqgroupname); + continue; + } + + groups[num_groups] = groupname; + num_groups++; + } + groups[num_groups] = NULL; + + ret = sysdb_attrs_get_el(rule, IPA_MEMBER_USER, &el); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule memberUser for rule " + "\"%s\" [%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + + goto done; + } + + memberuser = talloc_asprintf(tmp_ctx, "uid=%s,cn=users,cn=accounts,%s", + shortname, base_dn); + if (memberuser == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate memberuser\n"); + ret = ENOMEM; + goto done; + } + + for (size_t i = 0; i < el->num_values; i++) { + if (user && group) { + break; + } + + data = (char *)el->values[i].data; + + if (!user && data != NULL && strcmp(memberuser, data) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Desktop Profile rule \"%s\" matches with the user \"%s\" " + "for the \"%s\" domain!\n", + rule_name, shortname, domainname); + user = true; + continue; + } + + if (!group && data != NULL) { + for (size_t j = 0; !group && groups[j] != NULL; j++) { + membergroup = talloc_asprintf(tmp_ctx, + "cn=%s,cn=groups,cn=accounts,%s", + groups[j], base_dn); + if (membergroup == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate membergroup\n"); + ret = ENOMEM; + goto done; + } + + if (strcmp(membergroup, data) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Desktop Profile rule \"%s\" matches with (at least) " + "the group \"%s\" for the \"%s\" domain!\n", + rule_name, groups[j], domainname); + group = true; + } + } + } + } + + user_prio = user ? talloc_strdup(tmp_ctx, rule_prio) : + talloc_asprintf(tmp_ctx, "%06d", 0); + if (user_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the user priority\n"); + ret = ENOMEM; + goto done; + } + + group_prio = group ? talloc_strdup(tmp_ctx, rule_prio) : + talloc_asprintf(tmp_ctx, "%06d", 0); + if (group_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the group priority\n"); + ret = ENOMEM; + goto done; + } + + *_user_prio = talloc_steal(mem_ctx, user_prio); + *_group_prio = talloc_steal(mem_ctx, group_prio); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_deskprofile_rule_check_memberhost(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sysdb_attrs *rule, + const char *rule_name, + const char *rule_prio, + const char *base_dn, + const char *hostname, + char **_host_prio, + char **_hostgroup_prio) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *host_dn; + struct ldb_message_element *el_orig_memberof = NULL; + struct ldb_message_element *el = NULL; + struct ldb_message **msgs; + size_t count; + size_t num_memberhostgroup; + char **memberhostgroups = NULL; + char *data; + char *memberhost; + char *memberhostgroup; + char *name; + char *host_prio; + char *hostgroup_prio; + const char *memberof_attrs[] = { SYSDB_ORIG_MEMBEROF, NULL }; + bool host = false; + bool hostgroup = false; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + host_dn = sysdb_custom_dn(tmp_ctx, domain, hostname, + DESKPROFILE_HOSTS_SUBDIR); + if (host_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, domain->sysdb, host_dn, + LDB_SCOPE_BASE, NULL, + memberof_attrs, + &count, &msgs); + if (ret == ENOENT || count == 0) { + memberhostgroups = talloc_array(tmp_ctx, char *, 1); + memberhostgroups[0] = NULL; + } else if (ret != EOK) { + goto done; + } else if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More than one result for a BASE search!\n"); + ret = EIO; + goto done; + } else { /* ret == EOK && count == 1 */ + el_orig_memberof = ldb_msg_find_element(msgs[0], SYSDB_ORIG_MEMBEROF); + memberhostgroups = talloc_array(tmp_ctx, + char *, + el_orig_memberof->num_values); + } + + if (el_orig_memberof != NULL) { + num_memberhostgroup = 0; + for (size_t i = 0; i < el_orig_memberof->num_values; i++) { + data = (char *)el_orig_memberof->values[i].data; + + ret = ipa_common_get_hostgroupname(tmp_ctx, domain->sysdb, data, + &name); + + /* ERR_UNEXPECTED_ENTRY_TYPE means we had a memberOf entry that + * wasn't a host group, thus we'll just ignore those. + */ + if (ret != EOK && ret != ERR_UNEXPECTED_ENTRY_TYPE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Skipping malformed entry [%s]\n", + data); + continue; + } else if (ret == EOK) { + memberhostgroups[num_memberhostgroup] = name; + num_memberhostgroup++; + } + } + memberhostgroups[num_memberhostgroup] = NULL; + } + + ret = sysdb_attrs_get_el(rule, IPA_MEMBER_HOST, &el); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule memberHost for rule " + "\"%s\" [%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + + goto done; + } + + memberhost = talloc_asprintf(tmp_ctx, "fqdn=%s,cn=computers,cn=accounts,%s", + hostname, base_dn); + if (memberhost == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate memberhost\n"); + goto done; + } + + for (size_t i = 0; i < el->num_values; i++) { + if (host && hostgroup) { + break; + } + + data = (char *)el->values[i].data; + + if (!host && data != NULL && strcmp(memberhost, data) == 0) { + host = true; + DEBUG(SSSDBG_TRACE_FUNC, + "Desktop Profile rule \"%s\" matches with the host \"%s\" " + "for the \"%s\" domain!\n", + rule_name, hostname, domain->name); + continue; + } + + if (!hostgroup && data != NULL) { + for (size_t j = 0; !hostgroup && memberhostgroups[j] != NULL; j++) { + memberhostgroup = talloc_asprintf( + tmp_ctx, + "cn=%s,cn=hostgroups,cn=accounts,%s", + memberhostgroups[j], base_dn); + + if (memberhostgroup == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate memberhostgroup\n"); + ret = ENOMEM; + goto done; + } + + if (strcmp(memberhostgroup, data) == 0) { + hostgroup = true; + DEBUG(SSSDBG_TRACE_FUNC, + "Desktop Profile rule \"%s\" matches with (at least) " + "the hostgroup \"%s\" for the \"%s\" domain!\n", + rule_name, memberhostgroups[j], domain->name); + continue; + } + } + } + } + + host_prio = host ? talloc_strdup(tmp_ctx, rule_prio) : + talloc_asprintf(tmp_ctx, "%06d", 0); + if (host_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the host priority\n"); + ret = ENOMEM; + goto done; + } + + hostgroup_prio = hostgroup ? talloc_strdup(tmp_ctx, rule_prio) : + talloc_asprintf(tmp_ctx, "%06d", 0); + if (hostgroup_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the hostgroup priority\n"); + ret = ENOMEM; + goto done; + } + + *_host_prio = talloc_steal(mem_ctx, host_prio); + *_hostgroup_prio = talloc_steal(mem_ctx, hostgroup_prio); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + + +errno_t +ipa_deskprofile_rules_save_rule_to_disk( + TALLOC_CTX *mem_ctx, + uint16_t priority, + struct sysdb_attrs *rule, + struct sss_domain_info *domain, + const char *hostname, + const char *username, /* fully-qualified */ + uid_t uid, + gid_t gid) +{ + TALLOC_CTX *tmp_ctx; + const char *rule_name; + const char *data; + const char *hostcat; + const char *usercat; + char *shortname; + char *domainname; + char *base_dn; + char *rule_prio; + char *user_prio; + char *group_prio; + char *host_prio; + char *hostgroup_prio; + char *normalized_rule_name = NULL; + char *filename_path = NULL; + const char *extension = "json"; + uint32_t prio; + int fd = -1; + gid_t orig_gid; + uid_t orig_uid; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + orig_gid = getegid(); + orig_uid = geteuid(); + + ret = sysdb_attrs_get_string(rule, IPA_CN, &rule_name); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule name [%d]: %s\n", + ret, sss_strerror(ret)); + + goto done; + } + + ret = sysdb_attrs_get_uint32_t(rule, IPA_DESKPROFILE_PRIORITY, &prio); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule priority for rule " + "\"%s\" [%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_attrs_get_string(rule, IPA_HOST_CATEGORY, &hostcat); + if (ret == ENOENT) { + hostcat = NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule host category for rule " + "\"%s\" [%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_attrs_get_string(rule, IPA_USER_CATEGORY, &usercat); + if (ret == ENOENT) { + usercat = NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule user category for rule " + "\"%s\" [%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + goto done; + } + + rule_prio = talloc_asprintf(tmp_ctx, "%06d", prio); + if (rule_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate rule priority\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(rule, IPA_DESKPROFILE_DATA, &data); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule data for rule \"%s\" " + "[%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + goto done; + } + + ret = sss_parse_internal_fqname(tmp_ctx, username, &shortname, &domainname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_parse_internal_fqname() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = domain_to_basedn(tmp_ctx, domainname, &base_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "domain_to_basedn() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (usercat != NULL && strcasecmp(usercat, "all") == 0) { + user_prio = talloc_strdup(tmp_ctx, rule_prio); + if (user_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate the user priority " + "when user category is \"all\"\n"); + ret = ENOMEM; + goto done; + } + + group_prio = talloc_strdup(tmp_ctx, rule_prio); + if (group_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate the group priority " + "when user category is \"all\"\n"); + ret = ENOMEM; + goto done; + } + } else { + ret = ipa_deskprofile_rule_check_memberuser(tmp_ctx, domain, rule, + rule_name, rule_prio, + base_dn, username, + &user_prio, &group_prio); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_rule_check_memberuser() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + if (hostcat != NULL && strcasecmp(hostcat, "all") == 0) { + host_prio = talloc_strdup(tmp_ctx, rule_prio); + if (host_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate the host priority " + "when host category is \"all\"\n"); + ret = ENOMEM; + goto done; + } + + hostgroup_prio = talloc_strdup(tmp_ctx, rule_prio); + if (hostgroup_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate the hostgroup priority " + "when host category is \"all\"\n"); + ret = ENOMEM; + goto done; + } + } else { + ret = ipa_deskprofile_rule_check_memberhost(tmp_ctx, domain, rule, + rule_name, rule_prio, + base_dn, hostname, + &host_prio, &hostgroup_prio); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_rule_check_memberhost() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + ret = ipa_deskprofile_get_normalized_rule_name(mem_ctx, rule_name, + &normalized_rule_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_get_normalized_rule_name() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_deskprofile_get_filename_path(tmp_ctx, + priority, + IPA_DESKPROFILE_RULES_USER_DIR, + domainname, + shortname, + rule_prio, + user_prio, + group_prio, + host_prio, + hostgroup_prio, + normalized_rule_name, + extension, + &filename_path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_get_filename_path() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = setegid(gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective group id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + gid, ret, sss_strerror(ret)); + goto done; + } + + ret = seteuid(uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective user id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + uid, ret, sss_strerror(ret)); + goto done; + } + + fd = open(filename_path, O_WRONLY | O_CREAT | O_TRUNC, 0400); + if (fd == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to create the Desktop Profile rule file \"%s\" " + "[%d]: %s\n", + filename_path, ret, sss_strerror(ret)); + goto done; + } + + ret = dprintf(fd, "%s", data); + if (ret < 0) { + ret = EIO; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to write the content of the Desktop Profile rule for " + "the \"%s\" file.\n", + filename_path); + goto done; + } + + ret = seteuid(orig_uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set the effect user id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + orig_uid, ret, sss_strerror(ret)); + goto done; + } + + ret = setegid(orig_gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set the effect group id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + orig_gid, ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (fd != -1) { + close(fd); + } + if (geteuid() != orig_uid) { + ret = seteuid(orig_uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective user id (%"PRIu32") of the " + "domain's process [%d]: %s\n", + orig_uid, ret, sss_strerror(ret)); + DEBUG(SSSDBG_CRIT_FAILURE, + "Sending SIGUSR2 to the process: %d\n", getpid()); + kill(getpid(), SIGUSR2); + } + } + if (getegid() != orig_gid) { + ret = setegid(orig_gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective group id (%"PRIu32") of the " + "domain's process. Let's have the process restarted!\n", + orig_gid); + DEBUG(SSSDBG_CRIT_FAILURE, + "Sending SIGUSR2 to the process: %d\n", getpid()); + kill(getpid(), SIGUSR2); + } + } + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ipa_deskprofile_rules_remove_user_dir(const char *user_dir, + uid_t uid, + gid_t gid) +{ + gid_t orig_gid; + uid_t orig_uid; + errno_t ret; + + orig_gid = getegid(); + orig_uid = geteuid(); + + ret = setegid(gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective group id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + gid, ret, sss_strerror(ret)); + goto done; + } + + ret = seteuid(uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective user id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + uid, ret, sss_strerror(ret)); + goto done; + } + + ret = sss_remove_subtree(user_dir); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot remove \"%s\" directory [%d]: %s\n", + user_dir, ret, sss_strerror(ret)); + goto done; + } + + ret = seteuid(orig_uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set the effect user id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + orig_uid, ret, sss_strerror(ret)); + goto done; + } + + ret = setegid(orig_gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set the effect group id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + orig_gid, ret, sss_strerror(ret)); + goto done; + } + + ret = sss_remove_tree(user_dir); + if ((ret != EOK) && (ret != ENOENT)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot remove \"%s\" directory [%d]: %s\n", + user_dir, ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (geteuid() != orig_uid) { + ret = seteuid(orig_uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "unable to set effective user id (%"PRIu32") of the " + "domain's process [%d]: %s\n", + orig_uid, ret, sss_strerror(ret)); + DEBUG(SSSDBG_CRIT_FAILURE, + "Sending SIGUSR2 to the process: %d\n", getpid()); + kill(getpid(), SIGUSR2); + } + } + if (getegid() != orig_gid) { + ret = setegid(orig_gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective user id (%"PRIu32") of the " + "domain's process [%d]: %s\n", + orig_uid, ret, sss_strerror(ret)); + DEBUG(SSSDBG_CRIT_FAILURE, + "Sending SIGUSR2 to the process: %d\n", getpid()); + kill(getpid(), SIGUSR2); + } + } + return ret; +} + +errno_t +deskprofile_get_cached_priority(struct sss_domain_info *domain, + uint16_t *_priority) +{ + TALLOC_CTX *tmp_ctx; + const char *attrs[] = { IPA_DESKPROFILE_PRIORITY, NULL }; + struct ldb_message **resp; + size_t resp_count; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_search_custom_by_name(tmp_ctx, + domain, + IPA_DESKPROFILE_PRIORITY, + DESKPROFILE_CONFIG_SUBDIR, + attrs, &resp_count, &resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_custom_by_name() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (resp_count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_custom_by_name() got more attributes than " + "expected. Expected (1), got (%zu)\n", resp_count); + ret = EINVAL; + goto done; + } + + *_priority = ldb_msg_find_attr_as_uint(resp[0], + IPA_DESKPROFILE_PRIORITY, + 0); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +const char ** +deskprofile_get_attrs_to_get_cached_rules(TALLOC_CTX *mem_ctx) +{ + const char **attrs = talloc_zero_array(mem_ctx, const char *, 11); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array() failed\n"); + goto done; + } + + attrs[0] = OBJECTCLASS; + attrs[1] = IPA_CN; + attrs[2] = IPA_UNIQUE_ID; + attrs[3] = IPA_ENABLED_FLAG; + attrs[4] = IPA_MEMBER_USER; + attrs[5] = IPA_USER_CATEGORY; + attrs[6] = IPA_MEMBER_HOST; + attrs[7] = IPA_HOST_CATEGORY; + attrs[8] = IPA_DESKPROFILE_PRIORITY; + attrs[9] = IPA_DESKPROFILE_DATA; + attrs[10] = NULL; + +done: + return attrs; +} diff --git a/src/providers/ipa/ipa_deskprofile_rules_util.h b/src/providers/ipa/ipa_deskprofile_rules_util.h new file mode 100644 index 0000000..063bbd2 --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_rules_util.h @@ -0,0 +1,74 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio + + Copyright (C) 2017 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_DESKPROFILE_RULES_UTIL_H_ +#define IPA_DESKPROFILE_RULES_UTIL_H_ + +#include "db/sysdb.h" + +#ifndef IPA_DESKPROFILE_RULES_USER_DIR +#define IPA_DESKPROFILE_RULES_USER_DIR SSS_STATEDIR"/deskprofile" +#endif /* IPA_DESKPROFILE_RULES_USER_DIR */ + +errno_t +ipa_deskprofile_get_filename_path(TALLOC_CTX *mem_ctx, + uint16_t config_priority, + const char *rules_dir, + const char *domain, + const char *username, + const char *priority, + const char *user_priority, + const char *group_priority, + const char *host_priority, + const char *hostgroup_priority, + const char *rule_name, + const char *extension, + char **_filename_path); + +errno_t +ipa_deskprofile_rules_create_user_dir( + const char *username, /* fully-qualified */ + uid_t uid, + gid_t gid); +errno_t +ipa_deskprofile_rules_save_rule_to_disk( + TALLOC_CTX *mem_ctx, + uint16_t priority, + struct sysdb_attrs *rule, + struct sss_domain_info *domain, + const char *hostname, + const char *username, /* fully-qualified */ + uid_t uid, + gid_t gid); +errno_t +ipa_deskprofile_rules_remove_user_dir(const char *user_dir, + uid_t uid, + gid_t gid); + +errno_t +deskprofile_get_cached_priority(struct sss_domain_info *domain, + uint16_t *_priority); + +const char ** +deskprofile_get_attrs_to_get_cached_rules(TALLOC_CTX *mem_ctx); + +#endif /* IPA_DESKPROFILE_RULES_UTIL_H_ */ diff --git a/src/providers/ipa/ipa_dn.c b/src/providers/ipa/ipa_dn.c new file mode 100644 index 0000000..c58e014 --- /dev/null +++ b/src/providers/ipa/ipa_dn.c @@ -0,0 +1,145 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include "db/sysdb.h" +#include "providers/ipa/ipa_dn.h" + +static bool check_dn(struct ldb_dn *dn, + const char *rdn_attr, + va_list in_ap) +{ + const struct ldb_val *ldbval; + const char *strval; + const char *ldbattr; + const char *attr; + const char *val; + va_list ap; + int num_comp; + int comp; + + /* check RDN attribute */ + ldbattr = ldb_dn_get_rdn_name(dn); + if (ldbattr == NULL || strcasecmp(ldbattr, rdn_attr) != 0) { + return false; + } + + /* Check DN components. First we check if all attr=value pairs match input. + * Then we check that the next attribute is a domain component. + */ + + comp = 1; + num_comp = ldb_dn_get_comp_num(dn); + + va_copy(ap, in_ap); + while ((attr = va_arg(ap, const char *)) != NULL) { + val = va_arg(ap, const char *); + if (val == NULL) { + goto vafail; + } + + if (comp > num_comp) { + goto vafail; + } + + ldbattr = ldb_dn_get_component_name(dn, comp); + if (ldbattr == NULL || strcasecmp(ldbattr, attr) != 0) { + goto vafail; + } + + ldbval = ldb_dn_get_component_val(dn, comp); + if (ldbval == NULL) { + goto vafail; + } + + strval = (const char *)ldbval->data; + if (strval == NULL || strncasecmp(strval, val, ldbval->length) != 0) { + goto vafail; + } + + comp++; + } + va_end(ap); + + ldbattr = ldb_dn_get_component_name(dn, comp); + if (ldbattr == NULL || strcmp(ldbattr, "dc") != 0) { + return false; + } + + return true; + +vafail: + va_end(ap); + return false; +} + +errno_t _ipa_get_rdn(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *obj_dn, + char **_rdn_val, + const char *rdn_attr, + ...) +{ + const struct ldb_val *val; + struct ldb_dn *dn; + errno_t ret; + bool bret; + va_list ap; + char *rdn; + + dn = ldb_dn_new(mem_ctx, sysdb_ctx_get_ldb(sysdb), obj_dn); + if (dn == NULL) { + return ENOMEM; + } + + va_start(ap, rdn_attr); + bret = check_dn(dn, rdn_attr, ap); + va_end(ap); + if (bret == false) { + ret = ENOENT; + goto done; + } + + if (_rdn_val == NULL) { + ret = EOK; + goto done; + } + + val = ldb_dn_get_rdn_val(dn); + if (val == NULL || val->data == NULL) { + ret = EINVAL; + goto done; + } + + rdn = talloc_strndup(mem_ctx, (const char*)val->data, val->length); + if (rdn == NULL) { + ret = ENOMEM; + goto done; + } + + *_rdn_val = rdn; + + ret = EOK; + +done: + talloc_free(dn); + return ret; +} diff --git a/src/providers/ipa/ipa_dn.h b/src/providers/ipa/ipa_dn.h new file mode 100644 index 0000000..f889c3e --- /dev/null +++ b/src/providers/ipa/ipa_dn.h @@ -0,0 +1,43 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_DN_H_ +#define IPA_DN_H_ + +#include +#include "db/sysdb.h" + +errno_t _ipa_get_rdn(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *obj_dn, + char **_rdn_val, + const char *rdn_attr, + ...); + +#define ipa_get_rdn(mem_ctx, sysdb, dn, _rdn_val, rdn_attr, ...) \ + _ipa_get_rdn(mem_ctx, sysdb, dn, _rdn_val, rdn_attr, ##__VA_ARGS__, NULL) + +#define ipa_check_rdn(sysdb, dn, rdn_attr, ...) \ + _ipa_get_rdn(NULL, sysdb, dn, NULL, rdn_attr, ##__VA_ARGS__, NULL) + +#define ipa_check_rdn_bool(sysdb, dn, rdn_attr, ...) \ + ((bool)(ipa_check_rdn(sysdb, dn, rdn_attr, ##__VA_ARGS__) == EOK)) + +#endif /* IPA_DN_H_ */ diff --git a/src/providers/ipa/ipa_dyndns.c b/src/providers/ipa/ipa_dyndns.c new file mode 100644 index 0000000..2807c28 --- /dev/null +++ b/src/providers/ipa/ipa_dyndns.c @@ -0,0 +1,269 @@ +/* + SSSD + + ipa_dyndns.c + + Authors: + Stephen Gallagher + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include "util/util.h" +#include "providers/ldap/sdap_dyndns.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_dyndns.h" +#include "providers/data_provider.h" +#include "providers/be_dyndns.h" + +struct ipa_dyndns_update_state { + struct ipa_options *ipa_ctx; + struct sdap_id_op *sdap_op; +}; + +static void +ipa_dyndns_sdap_update_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_dyndns_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); + +static errno_t +ipa_dyndns_update_recv(struct tevent_req *req); + +static void +ipa_dyndns_update_connect_done(struct tevent_req *subreq); + + +errno_t ipa_dyndns_init(struct be_ctx *be_ctx, + struct ipa_options *ctx) +{ + errno_t ret; + const time_t ptask_first_delay = 10; + int period; + int offset; + uint32_t extraflags = 0; + + ctx->be_res = be_ctx->be_res; + if (ctx->be_res == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Resolver must be initialized in order " + "to use the IPA dynamic DNS updates\n"); + return EINVAL; + } + + period = dp_opt_get_int(ctx->dyndns_ctx->opts, DP_OPT_DYNDNS_REFRESH_INTERVAL); + if (period == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "DNS will not be updated periodically, " + "dyndns_refresh_interval is 0\n"); + extraflags |= BE_PTASK_NO_PERIODIC; + offset = 0; + } else { + offset = dp_opt_get_int(ctx->dyndns_ctx->opts, DP_OPT_DYNDNS_REFRESH_OFFSET); + } + + ret = be_ptask_create(ctx, be_ctx, period, ptask_first_delay, 0, offset, + period, 0, + ipa_dyndns_update_send, ipa_dyndns_update_recv, ctx, + "Dyndns update", + extraflags | + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_SCHEDULE_FROM_LAST, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup ptask " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + return ret; +} + +static struct tevent_req * +ipa_dyndns_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + int ret; + struct ipa_options *ctx; + struct ipa_dyndns_update_state *state; + struct tevent_req *req, *subreq; + struct sdap_id_ctx *sdap_ctx; + + DEBUG(SSSDBG_TRACE_FUNC, "Performing update\n"); + + ctx = talloc_get_type(pvt, struct ipa_options); + sdap_ctx = ctx->id_ctx->sdap_id_ctx; + + req = tevent_req_create(ctx, &state, struct ipa_dyndns_update_state); + if (req == NULL) { + return NULL; + } + state->ipa_ctx = ctx; + + if (ctx->dyndns_ctx->last_refresh + 60 > time(NULL) || + ctx->dyndns_ctx->timer_in_progress) { + DEBUG(SSSDBG_FUNC_DATA, "Last periodic update ran recently or timer " + "in progress, not scheduling another update\n"); + tevent_req_done(req); + tevent_req_post(req, sdap_ctx->be->ev); + return req; + } + state->ipa_ctx->dyndns_ctx->last_refresh = time(NULL); + + /* Make sure to have a valid LDAP connection */ + state->sdap_op = sdap_id_op_create(state, sdap_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret)); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_dyndns_update_connect_done, req); + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, sdap_ctx->be->ev); + } + return req; +} + +static void +ipa_dyndns_update_connect_done(struct tevent_req *subreq) +{ + int dp_error; + int ret; + struct ipa_options *ctx; + struct tevent_req *req; + struct ipa_dyndns_update_state *state; + struct sdap_id_ctx *sdap_ctx; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_dyndns_update_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, "No server is available, " + "dynamic DNS update is skipped in offline mode.\n"); + tevent_req_error(req, ERR_DYNDNS_OFFLINE); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to LDAP server: [%d](%s)\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ERR_NETWORK_IO); + } + return; + } + + ctx = state->ipa_ctx; + sdap_ctx = ctx->id_ctx->sdap_id_ctx; + + /* The following three checks are here to prevent SEGFAULT + * from ticket #3076. */ + if (ctx->service == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "service structure not initialized\n"); + ret = EINVAL; + goto done; + } + + if (ctx->service->sdap == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap structure not initialized\n"); + ret = EINVAL; + goto done; + } + + if (ctx->service->sdap->uri == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "LDAP uri not set\n"); + ret = EINVAL; + goto done; + } + + if (strncmp(ctx->service->sdap->uri, + "ldap://", 7) != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected format of LDAP URI.\n"); + ret = EIO; + goto done; + } + + subreq = sdap_dyndns_update_send(state, sdap_ctx->be->ev, + sdap_ctx->be, + ctx->dyndns_ctx->opts, + sdap_ctx, + ctx->dyndns_ctx->auth_type, + ctx->dyndns_ctx->auth_ptr_type, + dp_opt_get_string(ctx->dyndns_ctx->opts, + DP_OPT_DYNDNS_IFACE), + dp_opt_get_string(ctx->basic, + IPA_HOSTNAME), + dp_opt_get_string(ctx->basic, + IPA_KRB5_REALM), + dp_opt_get_int(ctx->dyndns_ctx->opts, + DP_OPT_DYNDNS_TTL), + true); + if (!subreq) { + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, + "sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + tevent_req_set_callback(subreq, ipa_dyndns_sdap_update_done, req); + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, sdap_ctx->be->ev); + } +} + +static void ipa_dyndns_sdap_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + errno_t ret; + + ret = sdap_dyndns_update_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Dynamic DNS update failed [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_dyndns_update_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ipa/ipa_dyndns.h b/src/providers/ipa/ipa_dyndns.h new file mode 100644 index 0000000..d6873b7 --- /dev/null +++ b/src/providers/ipa/ipa_dyndns.h @@ -0,0 +1,35 @@ +/* + SSSD + + ipa_dyndns.h + + Authors: + Stephen Gallagher + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_DYNDNS_H_ +#define IPA_DYNDNS_H_ + +#include "util/util_errors.h" +#include "providers/ipa/ipa_common.h" +#include "providers/backend.h" + +errno_t ipa_dyndns_init(struct be_ctx *be_ctx, + struct ipa_options *ctx); + +#endif /* IPA_DYNDNS_H_ */ diff --git a/src/providers/ipa/ipa_hbac_common.c b/src/providers/ipa/ipa_hbac_common.c new file mode 100644 index 0000000..1fee41a --- /dev/null +++ b/src/providers/ipa/ipa_hbac_common.c @@ -0,0 +1,748 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_rules_common.h" + +errno_t +replace_attribute_name(const char *old_name, + const char *new_name, const size_t count, + struct sysdb_attrs **list) +{ + int ret; + int i; + + for (i = 0; i < count; i++) { + ret = sysdb_attrs_replace_name(list[i], old_name, new_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_replace_name failed.\n"); + return ret; + } + } + + return EOK; +} + +static errno_t +create_empty_grouplist(struct hbac_request_element *el) +{ + el->groups = talloc_array(el, const char *, 1); + if (!el->groups) return ENOMEM; + + el->groups[0] = NULL; + return EOK; +} + +/******************************************** + * Functions for handling conversion to the * + * HBAC evaluator format * + ********************************************/ + +static errno_t +hbac_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + size_t index, + struct hbac_rule **rule); + +static errno_t +hbac_ctx_to_eval_request(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + struct hbac_eval_req **request); + +errno_t +hbac_ctx_to_rules(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + struct hbac_rule ***rules, + struct hbac_eval_req **request) +{ + errno_t ret; + struct hbac_rule **new_rules; + struct hbac_eval_req *new_request = NULL; + size_t i; + TALLOC_CTX *tmp_ctx = NULL; + + if (!rules || !request) return EINVAL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + /* First create an array of rules */ + new_rules = talloc_array(tmp_ctx, struct hbac_rule *, + hbac_ctx->rule_count + 1); + if (new_rules == NULL) { + ret = ENOMEM; + goto done; + } + + /* Create each rule one at a time */ + for (i = 0; i < hbac_ctx->rule_count ; i++) { + ret = hbac_attrs_to_rule(new_rules, hbac_ctx, i, &(new_rules[i])); + if (ret == EPERM) { + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not construct rules\n"); + goto done; + } + } + new_rules[i] = NULL; + + /* Create the eval request */ + ret = hbac_ctx_to_eval_request(tmp_ctx, hbac_ctx, &new_request); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not construct eval request\n"); + goto done; + } + + *rules = talloc_steal(mem_ctx, new_rules); + *request = talloc_steal(mem_ctx, new_request); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +hbac_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + size_t idx, + struct hbac_rule **rule) +{ + errno_t ret; + struct hbac_rule *new_rule; + struct ldb_message_element *el; + const char *rule_type; + + new_rule = talloc_zero(mem_ctx, struct hbac_rule); + if (new_rule == NULL) return ENOMEM; + + ret = sysdb_attrs_get_el(hbac_ctx->rules[idx], + IPA_CN, &el); + if (ret != EOK || el->num_values == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "rule has no name, assuming '(none)'.\n"); + new_rule->name = talloc_strdup(new_rule, "(none)"); + } else { + new_rule->name = talloc_strndup(new_rule, + (const char*) el->values[0].data, + el->values[0].length); + } + + DEBUG(SSSDBG_TRACE_LIBS, "Processing rule [%s]\n", new_rule->name); + + ret = sysdb_attrs_get_bool(hbac_ctx->rules[idx], IPA_ENABLED_FLAG, + &new_rule->enabled); + if (ret != EOK) goto done; + + if (!new_rule->enabled) { + ret = EOK; + goto done; + } + + ret = sysdb_attrs_get_string(hbac_ctx->rules[idx], + IPA_ACCESS_RULE_TYPE, + &rule_type); + if (ret != EOK) goto done; + + if (strcasecmp(rule_type, IPA_HBAC_ALLOW) != 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Rule [%s] is not an ALLOW rule\n", new_rule->name); + ret = EPERM; + goto done; + } + + /* Get the users */ + ret = hbac_user_attrs_to_rule(new_rule, hbac_ctx->be_ctx->domain, + new_rule->name, + hbac_ctx->rules[idx], + &new_rule->users); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not parse users for rule [%s]\n", + new_rule->name); + goto done; + } + + /* Get the services */ + ret = hbac_service_attrs_to_rule(new_rule, hbac_ctx->be_ctx->domain, + new_rule->name, + hbac_ctx->rules[idx], + &new_rule->services); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not parse services for rule [%s]\n", + new_rule->name); + goto done; + } + + /* Get the target hosts */ + ret = hbac_thost_attrs_to_rule(new_rule, hbac_ctx->be_ctx->domain, + new_rule->name, + hbac_ctx->rules[idx], + &new_rule->targethosts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not parse target hosts for rule [%s]\n", + new_rule->name); + goto done; + } + + /* Get the source hosts */ + + ret = hbac_shost_attrs_to_rule(new_rule, hbac_ctx->be_ctx->domain, + new_rule->name, + hbac_ctx->rules[idx], + dp_opt_get_bool(hbac_ctx->ipa_options, + IPA_HBAC_SUPPORT_SRCHOST), + &new_rule->srchosts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not parse source hosts for rule [%s]\n", + new_rule->name); + goto done; + } + + *rule = new_rule; + ret = EOK; + +done: + if (ret != EOK) talloc_free(new_rule); + return ret; +} + +errno_t +hbac_get_category(struct sysdb_attrs *attrs, + const char *category_attr, + uint32_t *_categories) +{ + errno_t ret; + size_t i; + uint32_t cats = HBAC_CATEGORY_NULL; + const char **categories; + + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + ret = sysdb_attrs_get_string_array(attrs, category_attr, + tmp_ctx, &categories); + if (ret != EOK && ret != ENOENT) goto done; + + if (ret != ENOENT) { + for (i = 0; categories[i]; i++) { + if (strcasecmp("all", categories[i]) == 0) { + DEBUG(SSSDBG_FUNC_DATA, "Category is set to 'all'.\n"); + cats |= HBAC_CATEGORY_ALL; + continue; + } + DEBUG(SSSDBG_TRACE_ALL, "Unsupported user category [%s].\n", + categories[i]); + } + } + + *_categories = cats; + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +hbac_eval_user_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username, + struct hbac_request_element **user_element); + +static errno_t +hbac_eval_service_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *servicename, + struct hbac_request_element **svc_element); + +static errno_t +hbac_eval_host_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *hostname, + struct hbac_request_element **host_element); + +static errno_t +hbac_ctx_to_eval_request(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + struct hbac_eval_req **request) +{ + errno_t ret; + struct pam_data *pd = hbac_ctx->pd; + TALLOC_CTX *tmp_ctx; + struct hbac_eval_req *eval_req; + struct sss_domain_info *domain = hbac_ctx->be_ctx->domain; + const char *rhost; + const char *thost; + struct sss_domain_info *user_dom; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + eval_req = talloc_zero(tmp_ctx, struct hbac_eval_req); + if (eval_req == NULL) { + ret = ENOMEM; + goto done; + } + + eval_req->request_time = time(NULL); + + /* Get user the user name and groups, + * take care of subdomain users as well */ + if (strcasecmp(pd->domain, domain->name) != 0) { + user_dom = find_domain_by_name(domain, pd->domain, true); + if (user_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); + ret = ENOMEM; + goto done; + } + ret = hbac_eval_user_element(eval_req, user_dom, pd->user, + &eval_req->user); + } else { + ret = hbac_eval_user_element(eval_req, domain, pd->user, + &eval_req->user); + } + if (ret != EOK) goto done; + + /* Get the PAM service and service groups */ + ret = hbac_eval_service_element(eval_req, domain, pd->service, + &eval_req->service); + if (ret != EOK) goto done; + + /* Get the source host */ + if (pd->rhost == NULL || pd->rhost[0] == '\0') { + /* If we haven't been passed an rhost, + * the rhost is unknown. This will fail + * to match any rule requiring the + * source host. + */ + rhost = NULL; + } else { + rhost = pd->rhost; + } + + ret = hbac_eval_host_element(eval_req, domain, rhost, + &eval_req->srchost); + if (ret != EOK) goto done; + + /* The target host is always the current machine */ + thost = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME); + if (thost == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing ipa_hostname, this should never happen.\n"); + ret = EINVAL; + goto done; + } + + ret = hbac_eval_host_element(eval_req, domain, thost, + &eval_req->targethost); + if (ret != EOK) goto done; + + *request = talloc_steal(mem_ctx, eval_req); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +hbac_eval_user_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username, + struct hbac_request_element **user_element) +{ + errno_t ret; + unsigned int num_groups = 0; + TALLOC_CTX *tmp_ctx; + struct hbac_request_element *users; + char *shortname; + const char *fqgroupname = NULL; + struct sss_domain_info *ipa_domain; + struct ldb_dn *ipa_groups_basedn; + struct ldb_result *res; + int exp_comp; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + users = talloc_zero(tmp_ctx, struct hbac_request_element); + if (users == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_parse_internal_fqname(tmp_ctx, username, &shortname, NULL); + if (ret != EOK) { + ret = ERR_WRONG_NAME_FORMAT; + goto done; + } + users->name = talloc_steal(users, shortname); + + ipa_domain = get_domains_head(domain); + if (ipa_domain == NULL) { + ret = EINVAL; + goto done; + } + + ipa_groups_basedn = ldb_dn_new_fmt(tmp_ctx, sysdb_ctx_get_ldb(domain->sysdb), + SYSDB_TMPL_GROUP_BASE, ipa_domain->name); + if (ipa_groups_basedn == NULL) { + ret = ENOMEM; + goto done; + } + + /* +1 because there will be a RDN preceding the base DN */ + exp_comp = ldb_dn_get_comp_num(ipa_groups_basedn) + 1; + + /* + * Get all the groups the user is a member of. + * This includes both POSIX and non-POSIX groups. + */ + ret = sysdb_initgroups(tmp_ctx, domain, username, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_initgroups() failed [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + if (res->count == 0) { + /* This should not happen at this point */ + DEBUG(SSSDBG_MINOR_FAILURE, + "User [%s] not found in cache.\n", username); + ret = ENOENT; + goto done; + } else if (res->count == 1) { + /* The first item is the user entry */ + DEBUG(SSSDBG_TRACE_LIBS, "No groups for [%s]\n", users->name); + ret = create_empty_grouplist(users); + goto done; + } + DEBUG(SSSDBG_TRACE_LIBS, + "[%u] groups for [%s]\n", res->count - 1, username); + + /* This also includes the sentinel, b/c we'll skip the user entry below */ + users->groups = talloc_array(users, const char *, res->count); + if (users->groups == NULL) { + ret = ENOMEM; + goto done; + } + + /* Start counting from 1 to exclude the user entry */ + for (size_t i = 1; i < res->count; i++) { + /* Only groups from the IPA domain can be referenced from HBAC rules. To + * avoid evaluating groups which might even have the same name, but come + * from a trusted domain, we first copy the DN to a temporary one.. + */ + if (ldb_dn_get_comp_num(res->msgs[i]->dn) != exp_comp + || ldb_dn_compare_base(ipa_groups_basedn, + res->msgs[i]->dn) != 0) { + DEBUG(SSSDBG_FUNC_DATA, + "Skipping non-IPA group %s\n", + ldb_dn_get_linearized(res->msgs[i]->dn)); + continue; + } + + fqgroupname = ldb_msg_find_attr_as_string(res->msgs[i], SYSDB_NAME, NULL); + if (fqgroupname == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Skipping malformed entry [%s]\n", + ldb_dn_get_linearized(res->msgs[i]->dn)); + continue; + } + + ret = sss_parse_internal_fqname(tmp_ctx, fqgroupname, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Malformed name %s, skipping!\n", fqgroupname); + continue; + } + + users->groups[num_groups] = talloc_steal(users->groups, shortname); + DEBUG(SSSDBG_TRACE_LIBS, "Added group [%s] for user [%s]\n", + users->groups[num_groups], users->name); + num_groups++; + } + users->groups[num_groups] = NULL; + + if (num_groups < (res->count - 1)) { + /* Shrink the array memory */ + users->groups = talloc_realloc(users, users->groups, const char *, + num_groups+1); + if (users->groups == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = EOK; +done: + if (ret == EOK) { + *user_element = talloc_steal(mem_ctx, users); + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +hbac_eval_service_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *servicename, + struct hbac_request_element **svc_element) +{ + errno_t ret; + size_t i, j, count; + TALLOC_CTX *tmp_ctx; + struct hbac_request_element *svc; + struct ldb_message **msgs; + struct ldb_message_element *el; + struct ldb_dn *svc_dn; + const char *memberof_attrs[] = { SYSDB_ORIG_MEMBEROF, NULL }; + char *name; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + svc = talloc_zero(tmp_ctx, struct hbac_request_element); + if (svc == NULL) { + ret = ENOMEM; + goto done; + } + + svc->name = servicename; + + svc_dn = sysdb_custom_dn(tmp_ctx, domain, svc->name, HBAC_SERVICES_SUBDIR); + if (svc_dn == NULL) { + ret = ENOMEM; + goto done; + } + + /* Look up the service to get its originalMemberOf entries */ + ret = sysdb_search_entry(tmp_ctx, domain->sysdb, svc_dn, + LDB_SCOPE_BASE, NULL, + memberof_attrs, + &count, &msgs); + if (ret == ENOENT || count == 0) { + /* We won't be able to identify any groups + * This rule will only match the name or + * a service category of ALL + */ + ret = create_empty_grouplist(svc); + goto done; + } else if (ret != EOK) { + goto done; + } else if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one result for a BASE search!\n"); + ret = EIO; + goto done; + } + + el = ldb_msg_find_element(msgs[0], SYSDB_ORIG_MEMBEROF); + if (!el) { + /* Service is not a member of any groups + * This rule will only match the name or + * a service category of ALL + */ + ret = create_empty_grouplist(svc); + goto done; + } + + + svc->groups = talloc_array(svc, const char *, el->num_values + 1); + if (svc->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = j = 0; i < el->num_values; i++) { + ret = get_ipa_servicegroupname(tmp_ctx, domain->sysdb, + (const char *)el->values[i].data, + &name); + if (ret != EOK && ret != ERR_UNEXPECTED_ENTRY_TYPE) { + DEBUG(SSSDBG_MINOR_FAILURE, "Skipping malformed entry [%s]\n", + (const char *)el->values[i].data); + continue; + } + + /* ERR_UNEXPECTED_ENTRY_TYPE means we had a memberOf entry that wasn't a + * service group. We'll just ignore those (could be + * HBAC rules) + */ + + if (ret == EOK) { + svc->groups[j] = talloc_steal(svc->groups, name); + j++; + } + } + svc->groups[j] = NULL; + + ret = EOK; + +done: + if (ret == EOK) { + *svc_element = talloc_steal(mem_ctx, svc); + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +hbac_eval_host_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *hostname, + struct hbac_request_element **host_element) +{ + errno_t ret; + size_t i, j, count; + TALLOC_CTX *tmp_ctx; + struct hbac_request_element *host; + struct ldb_message **msgs; + struct ldb_message_element *el; + struct ldb_dn *host_dn; + const char *memberof_attrs[] = { SYSDB_ORIG_MEMBEROF, NULL }; + char *name; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + host = talloc_zero(tmp_ctx, struct hbac_request_element); + if (host == NULL) { + ret = ENOMEM; + goto done; + } + + host->name = hostname; + + if (host->name == NULL) { + /* We don't know the host (probably an rhost) + * So we can't determine it's groups either. + */ + ret = create_empty_grouplist(host); + goto done; + } + + host_dn = sysdb_custom_dn(tmp_ctx, domain, host->name, HBAC_HOSTS_SUBDIR); + if (host_dn == NULL) { + ret = ENOMEM; + goto done; + } + + /* Look up the host to get its originalMemberOf entries */ + ret = sysdb_search_entry(tmp_ctx, domain->sysdb, host_dn, + LDB_SCOPE_BASE, NULL, + memberof_attrs, + &count, &msgs); + if (ret == ENOENT || count == 0) { + /* We won't be able to identify any groups + * This rule will only match the name or + * a host category of ALL + */ + ret = create_empty_grouplist(host); + goto done; + } else if (ret != EOK) { + goto done; + } else if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one result for a BASE search!\n"); + ret = EIO; + goto done; + } + + el = ldb_msg_find_element(msgs[0], SYSDB_ORIG_MEMBEROF); + if (!el) { + /* Host is not a member of any groups + * This rule will only match the name or + * a host category of ALL + */ + ret = create_empty_grouplist(host); + goto done; + } + + + host->groups = talloc_array(host, const char *, el->num_values + 1); + if (host->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = j = 0; i < el->num_values; i++) { + ret = ipa_common_get_hostgroupname(tmp_ctx, domain->sysdb, + (const char *)el->values[i].data, + &name); + if (ret != EOK && ret != ERR_UNEXPECTED_ENTRY_TYPE) { + DEBUG(SSSDBG_MINOR_FAILURE, "Skipping malformed entry [%s]\n", + (const char *)el->values[i].data); + continue; + } + + /* ERR_UNEXPECTED_ENTRY_TYPE means we had a memberOf entry that wasn't a + * host group. We'll just ignore those (could be + * HBAC rules) + */ + + if (ret == EOK) { + host->groups[j] = talloc_steal(host->groups, name); + j++; + } + } + host->groups[j] = NULL; + + ret = EOK; + +done: + if (ret == EOK) { + *host_element = talloc_steal(mem_ctx, host); + } + talloc_free(tmp_ctx); + return ret; +} + +const char ** +hbac_get_attrs_to_get_cached_rules(TALLOC_CTX *mem_ctx) +{ + const char **attrs = talloc_zero_array(mem_ctx, const char *, 16); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array() failed\n"); + goto done; + } + + attrs[0] = OBJECTCLASS; + attrs[1] = IPA_CN; + attrs[2] = SYSDB_ORIG_DN; + attrs[3] = IPA_UNIQUE_ID; + attrs[4] = IPA_ENABLED_FLAG; + attrs[5] = IPA_ACCESS_RULE_TYPE; + attrs[6] = IPA_MEMBER_USER; + attrs[7] = IPA_USER_CATEGORY; + attrs[8] = IPA_MEMBER_SERVICE; + attrs[9] = IPA_SERVICE_CATEGORY; + attrs[10] = IPA_SOURCE_HOST; + attrs[11] = IPA_SOURCE_HOST_CATEGORY; + attrs[12] = IPA_EXTERNAL_HOST; + attrs[13] = IPA_MEMBER_HOST; + attrs[14] = IPA_HOST_CATEGORY; + attrs[15] = NULL; + +done: + return attrs; +} diff --git a/src/providers/ipa/ipa_hbac_hosts.c b/src/providers/ipa/ipa_hbac_hosts.c new file mode 100644 index 0000000..f85ce53 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_hosts.c @@ -0,0 +1,335 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ldap/sdap_async.h" + +/* + * Functions to convert sysdb_attrs to the hbac_rule format + */ +static errno_t hbac_host_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + const char *category_attr, + const char *member_attr, + size_t *host_count, + struct hbac_rule_element **hosts) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct hbac_rule_element *new_hosts; + const char *attrs[] = { SYSDB_FQDN, SYSDB_NAME, NULL }; + struct ldb_message_element *el; + size_t num_hosts = 0; + size_t num_hostgroups = 0; + size_t i; + char *member_dn; + char *filter; + size_t count; + struct ldb_message **msgs; + const char *name; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + new_hosts = talloc_zero(tmp_ctx, struct hbac_rule_element); + if (new_hosts == NULL) { + ret = ENOMEM; + goto done; + } + + /* First check for host category */ + ret = hbac_get_category(rule_attrs, category_attr, &new_hosts->category); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify host categories\n"); + goto done; + } + if (new_hosts->category & HBAC_CATEGORY_ALL) { + /* Short-cut to the exit */ + ret = EOK; + goto done; + } + + /* Get the list of DNs from the member_attr */ + ret = sysdb_attrs_get_el(rule_attrs, member_attr, &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_get_el failed.\n"); + goto done; + } + if (ret == ENOENT || el->num_values == 0) { + el->num_values = 0; + DEBUG(SSSDBG_CONF_SETTINGS, + "No host specified, rule will never apply.\n"); + } + + /* Assume maximum size; We'll trim it later */ + new_hosts->names = talloc_array(new_hosts, + const char *, + el->num_values +1); + if (new_hosts->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_hosts->groups = talloc_array(new_hosts, + const char *, + el->num_values + 1); + if (new_hosts->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < el->num_values; i++) { + ret = sss_filter_sanitize(tmp_ctx, + (const char *)el->values[i].data, + &member_dn); + if (ret != EOK) goto done; + + filter = talloc_asprintf(member_dn, "(%s=%s)", + SYSDB_ORIG_DN, member_dn); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + /* First check if this is a specific host */ + ret = sysdb_search_custom(tmp_ctx, domain, filter, + HBAC_HOSTS_SUBDIR, attrs, + &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple hosts. Skipping \n"); + talloc_zfree(member_dn); + continue; + } + + /* Original DN matched a single host. Get the hostname */ + name = ldb_msg_find_attr_as_string(msgs[0], + SYSDB_FQDN, + NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "FQDN is missing!\n"); + ret = EFAULT; + goto done; + } + + new_hosts->names[num_hosts] = talloc_strdup(new_hosts->names, + name); + if (new_hosts->names[num_hosts] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Added host [%s] to rule [%s]\n", + name, rule_name); + num_hosts++; + } else { /* ret == ENOENT */ + /* Check if this is a hostgroup */ + ret = sysdb_search_custom(tmp_ctx, domain, filter, + HBAC_HOSTGROUPS_SUBDIR, attrs, + &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple hostgroups. " + "Skipping\n"); + talloc_zfree(member_dn); + continue; + } + + /* Original DN matched a single group. Get the groupname */ + name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Hostgroup name is missing!\n"); + ret = EFAULT; + goto done; + } + + new_hosts->groups[num_hostgroups] = + talloc_strdup(new_hosts->groups, name); + if (new_hosts->groups[num_hostgroups] == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added hostgroup [%s] to rule [%s]\n", + name, rule_name); + num_hostgroups++; + } else { /* ret == ENOENT */ + /* Neither a host nor a hostgroup? Skip it */ + DEBUG(SSSDBG_TRACE_LIBS, + "[%s] does not map to either a host or hostgroup. " + "Skipping\n", member_dn); + } + } + talloc_zfree(member_dn); + } + new_hosts->names[num_hosts] = NULL; + new_hosts->groups[num_hostgroups] = NULL; + + /* Shrink the arrays down to their real sizes */ + new_hosts->names = talloc_realloc(new_hosts, new_hosts->names, + const char *, num_hosts + 1); + if (new_hosts->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_hosts->groups = talloc_realloc(new_hosts, new_hosts->groups, + const char *, num_hostgroups + 1); + if (new_hosts->groups == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *hosts = talloc_steal(mem_ctx, new_hosts); + if (host_count) *host_count = num_hosts; + } + talloc_free(tmp_ctx); + return ret; +} + +errno_t +hbac_thost_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **thosts) +{ + DEBUG(SSSDBG_TRACE_LIBS, + "Processing target hosts for rule [%s]\n", rule_name); + + return hbac_host_attrs_to_rule(mem_ctx, domain, + rule_name, rule_attrs, + IPA_HOST_CATEGORY, IPA_MEMBER_HOST, + NULL, thosts); +} + +errno_t +hbac_shost_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + bool support_srchost, + struct hbac_rule_element **source_hosts) +{ + errno_t ret; + size_t host_count; + TALLOC_CTX *tmp_ctx; + size_t idx; + struct ldb_message_element *el; + struct hbac_rule_element *shosts; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + DEBUG(SSSDBG_TRACE_FUNC, "Processing source hosts for rule [%s]\n", rule_name); + + if (!support_srchost) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Source hosts disabled, setting ALL\n"); + shosts = talloc_zero(tmp_ctx, struct hbac_rule_element); + if (shosts == NULL) { + ret = ENOMEM; + goto done; + } + + shosts->category = HBAC_CATEGORY_ALL; + ret = EOK; + goto done; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "WARNING: Using deprecated option " + "ipa_hbac_support_srchost.\n"); + sss_log(SSS_LOG_NOTICE, "WARNING: Using deprecated option " + "ipa_hbac_support_srchost.\n"); + } + + ret = hbac_host_attrs_to_rule(tmp_ctx, domain, + rule_name, rule_attrs, + IPA_SOURCE_HOST_CATEGORY, IPA_SOURCE_HOST, + &host_count, &shosts); + if (ret != EOK) { + goto done; + } + + if (shosts->category & HBAC_CATEGORY_ALL) { + /* All hosts (including external) are + * allowed. + */ + goto done; + } + + /* Include external (non-IPA-managed) source hosts */ + ret = sysdb_attrs_get_el(rule_attrs, IPA_EXTERNAL_HOST, &el); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && el->num_values == 0) ret = ENOENT; + + if (ret != ENOENT) { + shosts->names = talloc_realloc(shosts, shosts->names, const char *, + host_count + el->num_values + 1); + if (shosts->names == NULL) { + ret = ENOMEM; + goto done; + } + + for (idx = host_count; idx < host_count + el->num_values; idx++) { + shosts->names[idx] = + talloc_strdup(shosts->names, + (const char *)el->values[idx - host_count].data); + if (shosts->names[idx] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added external source host [%s] to rule [%s]\n", + shosts->names[idx], rule_name); + } + shosts->names[idx] = NULL; + } + + ret = EOK; + +done: + if (ret == EOK) { + *source_hosts = talloc_steal(mem_ctx, shosts); + } + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ipa/ipa_hbac_private.h b/src/providers/ipa/ipa_hbac_private.h new file mode 100644 index 0000000..8ca7d09 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_private.h @@ -0,0 +1,132 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_HBAC_PRIVATE_H_ +#define IPA_HBAC_PRIVATE_H_ + +#include "providers/ipa/ipa_access.h" +#include "lib/ipa_hbac/ipa_hbac.h" + +#define IPA_HBAC_RULE "ipaHBACRule" + +#define IPA_HBAC_SERVICE "ipaHBACService" +#define IPA_HBAC_SERVICE_GROUP "ipaHBACServiceGroup" + +#define IPA_MEMBER "member" +#define HBAC_HOSTS_SUBDIR "hbac_hosts" +#define HBAC_HOSTGROUPS_SUBDIR "hbac_hostgroups" + +#define IPA_MEMBEROF "memberOf" +#define IPA_ACCESS_RULE_TYPE "accessRuleType" +#define IPA_HBAC_ALLOW "allow" +#define IPA_SERVICE_NAME "serviceName" +#define IPA_SOURCE_HOST "sourceHost" +#define IPA_SOURCE_HOST_CATEGORY "sourceHostCategory" +#define IPA_MEMBER_SERVICE "memberService" +#define IPA_SERVICE_CATEGORY "serviceCategory" + +#define IPA_HBAC_BASE_TMPL "cn=hbac,%s" +#define IPA_SERVICES_BASE_TMPL "cn=hbacservices,cn=accounts,%s" + +#define SYSDB_HBAC_BASE_TMPL "cn=hbac,"SYSDB_TMPL_CUSTOM_BASE + +#define HBAC_RULES_SUBDIR "hbac_rules" +#define HBAC_SERVICES_SUBDIR "hbac_services" +#define HBAC_SERVICEGROUPS_SUBDIR "hbac_servicegroups" + +/* From ipa_hbac_common.c */ +errno_t +replace_attribute_name(const char *old_name, + const char *new_name, const size_t count, + struct sysdb_attrs **list); + +errno_t hbac_ctx_to_rules(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + struct hbac_rule ***rules, + struct hbac_eval_req **request); + +errno_t +hbac_get_category(struct sysdb_attrs *attrs, + const char *category_attr, + uint32_t *_categories); + +errno_t +hbac_thost_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **thosts); + +errno_t +hbac_shost_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + bool support_srchost, + struct hbac_rule_element **source_hosts); + +const char ** +hbac_get_attrs_to_get_cached_rules(TALLOC_CTX *mem_ctx); + +/* From ipa_hbac_services.c */ +struct tevent_req * +ipa_hbac_service_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases); + +errno_t +ipa_hbac_service_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *service_count, + struct sysdb_attrs ***services, + size_t *servicegroup_count, + struct sysdb_attrs ***servicegroups); + +errno_t +hbac_service_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **services); +errno_t +get_ipa_servicegroupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *service_dn, + char **servicename); + +/* From ipa_hbac_users.c */ +errno_t +hbac_user_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **users); + +errno_t +get_ipa_groupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *group_dn, + const char **groupname); + +#endif /* IPA_HBAC_PRIVATE_H_ */ diff --git a/src/providers/ipa/ipa_hbac_rules.c b/src/providers/ipa/ipa_hbac_rules.c new file mode 100644 index 0000000..e2c97ae --- /dev/null +++ b/src/providers/ipa/ipa_hbac_rules.c @@ -0,0 +1,313 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_hbac_rules.h" +#include "providers/ldap/sdap_async.h" + +struct ipa_hbac_rule_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_options *opts; + + int search_base_iter; + struct sdap_search_base **search_bases; + + const char **attrs; + char *rules_filter; + char *cur_filter; + + size_t rule_count; + struct sysdb_attrs **rules; +}; + +static errno_t +ipa_hbac_rule_info_next(struct tevent_req *req, + struct ipa_hbac_rule_state *state); +static void +ipa_hbac_rule_info_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_hbac_rule_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sysdb_attrs *ipa_host) +{ + errno_t ret; + size_t i; + struct tevent_req *req = NULL; + struct ipa_hbac_rule_state *state; + const char *host_dn; + char *host_dn_clean; + char *host_group_clean; + char *rule_filter; + const char **memberof_list; + + req = tevent_req_create(mem_ctx, &state, struct ipa_hbac_rule_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + if (ipa_host == NULL) { + ret = EINVAL; + DEBUG(SSSDBG_CRIT_FAILURE, "Missing host\n"); + goto immediate; + } + + ret = sysdb_attrs_get_string(ipa_host, SYSDB_ORIG_DN, &host_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify IPA hostname\n"); + goto immediate; + } + + ret = sss_filter_sanitize_dn(state, host_dn, &host_dn_clean); + if (ret != EOK) goto immediate; + + state->ev = ev; + state->sh = sh; + state->opts = opts; + state->search_bases = search_bases; + state->search_base_iter = 0; + state->attrs = talloc_zero_array(state, const char *, 15); + if (state->attrs == NULL) { + ret = ENOMEM; + goto immediate; + } + state->attrs[0] = OBJECTCLASS; + state->attrs[1] = IPA_CN; + state->attrs[2] = IPA_UNIQUE_ID; + state->attrs[3] = IPA_ENABLED_FLAG; + state->attrs[4] = IPA_ACCESS_RULE_TYPE; + state->attrs[5] = IPA_MEMBER_USER; + state->attrs[6] = IPA_USER_CATEGORY; + state->attrs[7] = IPA_MEMBER_SERVICE; + state->attrs[8] = IPA_SERVICE_CATEGORY; + state->attrs[9] = IPA_SOURCE_HOST; + state->attrs[10] = IPA_SOURCE_HOST_CATEGORY; + state->attrs[11] = IPA_EXTERNAL_HOST; + state->attrs[12] = IPA_MEMBER_HOST; + state->attrs[13] = IPA_HOST_CATEGORY; + state->attrs[14] = NULL; + + rule_filter = talloc_asprintf(state, + "(&(objectclass=%s)" + "(%s=%s)(%s=%s)" + "(|(%s=%s)(%s=%s)", + IPA_HBAC_RULE, + IPA_ENABLED_FLAG, IPA_TRUE_VALUE, + IPA_ACCESS_RULE_TYPE, IPA_HBAC_ALLOW, + IPA_HOST_CATEGORY, "all", + IPA_MEMBER_HOST, host_dn_clean); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + /* Add all parent groups of ipa_hostname to the filter */ + ret = sysdb_attrs_get_string_array(ipa_host, SYSDB_ORIG_MEMBEROF, + state, &memberof_list); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify.\n"); + } else if (ret == ENOENT) { + /* This host is not a member of any hostgroups */ + memberof_list = talloc_array(state, const char *, 1); + if (memberof_list == NULL) { + ret = ENOMEM; + goto immediate; + } + memberof_list[0] = NULL; + } + + for (i = 0; memberof_list[i]; i++) { + ret = sss_filter_sanitize(state, + memberof_list[i], + &host_group_clean); + if (ret != EOK) goto immediate; + + rule_filter = talloc_asprintf_append(rule_filter, "(%s=%s)", + IPA_MEMBER_HOST, + host_group_clean); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + } + + rule_filter = talloc_asprintf_append(rule_filter, "))"); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + state->rules_filter = talloc_steal(state, rule_filter); + + ret = ipa_hbac_rule_info_next(req, state); + if (ret != EAGAIN) { + if (ret == EOK) { + /* ipa_hbac_rule_info_next should always have a search base when + * called for the first time. + * + * For the subsequent iterations, not finding any more search bases + * is fine though (thus the function returns EOK). + * + * As, here, it's the first case happening, let's return EINVAL. + */ + DEBUG(SSSDBG_CRIT_FAILURE, "No search base found\n"); + ret = EINVAL; + } + goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t +ipa_hbac_rule_info_next(struct tevent_req *req, + struct ipa_hbac_rule_state *state) +{ + struct tevent_req *subreq; + struct sdap_search_base *base; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->rules_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Sending request for next search base: " + "[%s][%d][%s]\n", base->basedn, base->scope, + state->cur_filter); + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base->basedn, base->scope, + state->cur_filter, state->attrs, + NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_hbac_rule_info_done, req); + + return EAGAIN; +} + +static void +ipa_hbac_rule_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_hbac_rule_state *state = + tevent_req_data(req, struct ipa_hbac_rule_state); + int i; + size_t rule_count; + size_t total_count; + struct sysdb_attrs **rules; + struct sysdb_attrs **target; + + ret = sdap_get_generic_recv(subreq, state, + &rule_count, + &rules); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not retrieve HBAC rules\n"); + goto fail; + } + + if (rule_count > 0) { + total_count = rule_count + state->rule_count; + state->rules = talloc_realloc(state, state->rules, + struct sysdb_attrs *, + total_count); + if (state->rules == NULL) { + ret = ENOMEM; + goto fail; + } + + i = 0; + while (state->rule_count < total_count) { + target = &state->rules[state->rule_count]; + *target = talloc_steal(state->rules, rules[i]); + + state->rule_count++; + i++; + } + } + + state->search_base_iter++; + ret = ipa_hbac_rule_info_next(req, state); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + goto fail; + } else if (ret == EOK && state->rule_count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No rules apply to this host\n"); + tevent_req_error(req, ENOENT); + return; + } + + /* We went through all search bases and we have some results */ + tevent_req_done(req); + + return; + +fail: + tevent_req_error(req, ret); +} + +errno_t +ipa_hbac_rule_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_rule_count, + struct sysdb_attrs ***_rules) +{ + struct ipa_hbac_rule_state *state = + tevent_req_data(req, struct ipa_hbac_rule_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_rule_count = state->rule_count; + *_rules = talloc_steal(mem_ctx, state->rules); + + return EOK; +} diff --git a/src/providers/ipa/ipa_hbac_rules.h b/src/providers/ipa/ipa_hbac_rules.h new file mode 100644 index 0000000..d8e5a14 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_rules.h @@ -0,0 +1,41 @@ +/* + SSSD + + Authors: + Jan Zeleny + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_HBAC_RULES_H_ +#define IPA_HBAC_RULES_H_ + +/* From ipa_hbac_rules.c */ +struct tevent_req * +ipa_hbac_rule_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sysdb_attrs *ipa_host); + +errno_t +ipa_hbac_rule_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_rule_count, + struct sysdb_attrs ***_rules); + +#endif /* IPA_HBAC_RULES_H_ */ diff --git a/src/providers/ipa/ipa_hbac_services.c b/src/providers/ipa/ipa_hbac_services.c new file mode 100644 index 0000000..387e915 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_services.c @@ -0,0 +1,686 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ldap/sdap_async.h" + +struct ipa_hbac_service_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_options *opts; + const char **attrs; + + char *service_filter; + char *cur_filter; + + struct sdap_search_base **search_bases; + int search_base_iter; + + /* Return values */ + size_t service_count; + struct sysdb_attrs **services; + + size_t servicegroup_count; + struct sysdb_attrs **servicegroups; +}; + +static errno_t +ipa_hbac_service_info_next(struct tevent_req *req, + struct ipa_hbac_service_state *state); +static void +ipa_hbac_service_info_done(struct tevent_req *subreq); +static errno_t +ipa_hbac_servicegroup_info_next(struct tevent_req *req, + struct ipa_hbac_service_state *state); +static void +ipa_hbac_servicegroup_info_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_hbac_service_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases) +{ + errno_t ret; + struct ipa_hbac_service_state *state; + struct tevent_req *req; + char *service_filter; + + req = tevent_req_create(mem_ctx, &state, struct ipa_hbac_service_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->sh = sh; + state->opts = opts; + + state->search_bases = search_bases; + state->search_base_iter = 0; + + service_filter = talloc_asprintf(state, "(objectClass=%s)", + IPA_HBAC_SERVICE); + if (service_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + state->service_filter = service_filter; + state->cur_filter = NULL; + + state->attrs = talloc_array(state, const char *, 6); + if (state->attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate service attribute list.\n"); + ret = ENOMEM; + goto immediate; + } + state->attrs[0] = OBJECTCLASS; + state->attrs[1] = IPA_CN; + state->attrs[2] = IPA_UNIQUE_ID; + state->attrs[3] = IPA_MEMBER; + state->attrs[4] = IPA_MEMBEROF; + state->attrs[5] = NULL; + + ret = ipa_hbac_service_info_next(req, state); + if (ret == EOK) { + ret = EINVAL; + } + + if (ret != EAGAIN) { + goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_hbac_service_info_next(struct tevent_req *req, + struct ipa_hbac_service_state *state) +{ + struct tevent_req *subreq; + struct sdap_search_base *base; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->service_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Sending request for next search base: " + "[%s][%d][%s]\n", base->basedn, base->scope, + state->cur_filter); + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base->basedn, base->scope, + state->cur_filter, + state->attrs, NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error requesting service info\n"); + return EIO; + } + tevent_req_set_callback(subreq, ipa_hbac_service_info_done, req); + + return EAGAIN; +} + +static void +ipa_hbac_service_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_hbac_service_state *state = + tevent_req_data(req, struct ipa_hbac_service_state); + char *servicegroup_filter; + + ret = sdap_get_generic_recv(subreq, state, + &state->service_count, + &state->services); + talloc_zfree(subreq); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + if (ret == ENOENT || state->service_count == 0) { + /* If there are no services, we'll shortcut out + * This is still valid, as rules can apply to + * all services + * + * There's no reason to try to process groups + */ + + state->search_base_iter++; + ret = ipa_hbac_service_info_next(req, state); + if (ret == EAGAIN) { + return; + } + + state->service_count = 0; + state->services = NULL; + goto done; + } + + ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF, + state->service_count, + state->services); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not replace attribute names\n"); + goto done; + } + + servicegroup_filter = talloc_asprintf(state, "(objectClass=%s)", + IPA_HBAC_SERVICE_GROUP); + if (servicegroup_filter == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(state->service_filter); + state->service_filter = servicegroup_filter; + + state->search_base_iter = 0; + ret = ipa_hbac_servicegroup_info_next(req, state); + if (ret == EOK) { + ret = EINVAL; + } + + if (ret != EAGAIN) { + goto done; + } + + return; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t +ipa_hbac_servicegroup_info_next(struct tevent_req *req, + struct ipa_hbac_service_state *state) +{ + struct tevent_req *subreq; + struct sdap_search_base *base; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->service_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + /* Look up service groups */ + DEBUG(SSSDBG_TRACE_FUNC, "Sending request for next search base: " + "[%s][%d][%s]\n", base->basedn, base->scope, + state->cur_filter); + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base->basedn, base->scope, + state->cur_filter, state->attrs, NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error requesting servicegroup info\n"); + return EIO; + } + tevent_req_set_callback(subreq, ipa_hbac_servicegroup_info_done, req); + + return EAGAIN; +} + +static void +ipa_hbac_servicegroup_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_hbac_service_state *state = + tevent_req_data(req, struct ipa_hbac_service_state); + size_t total_count; + size_t group_count; + struct sysdb_attrs **groups; + struct sysdb_attrs **target; + int i; + + ret = sdap_get_generic_recv(subreq, state, + &group_count, + &groups); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + if (group_count > 0) { + ret = replace_attribute_name(IPA_MEMBER, SYSDB_ORIG_MEMBER, + group_count, + groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not replace attribute names\n"); + goto done; + } + + ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF, + state->servicegroup_count, + state->servicegroups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not replace attribute names\n"); + goto done; + } + + total_count = state->servicegroup_count + group_count; + state->servicegroups = talloc_realloc(state, state->servicegroups, + struct sysdb_attrs *, + total_count); + if (state->servicegroups == NULL) { + ret = ENOMEM; + goto done; + } + + i = 0; + while (state->servicegroup_count < total_count) { + target = &state->servicegroups[state->servicegroup_count]; + *target = talloc_steal(state->servicegroups, groups[i]); + + state->servicegroup_count++; + i++; + } + } + + state->search_base_iter++; + ret = ipa_hbac_servicegroup_info_next(req, state); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + goto done; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Error [%d][%s]\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } +} + +errno_t +ipa_hbac_service_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *service_count, + struct sysdb_attrs ***services, + size_t *servicegroup_count, + struct sysdb_attrs ***servicegroups) +{ + size_t c; + struct ipa_hbac_service_state *state = + tevent_req_data(req, struct ipa_hbac_service_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *service_count = state->service_count; + *services = talloc_steal(mem_ctx, state->services); + for (c = 0; c < state->service_count; c++) { + /* Guarantee the memory heirarchy of the list */ + talloc_steal(state->services, state->services[c]); + } + + *servicegroup_count = state->servicegroup_count; + *servicegroups = talloc_steal(mem_ctx, state->servicegroups); + + return EOK; +} + +errno_t +hbac_service_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **services) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct hbac_rule_element *new_services; + const char *attrs[] = { IPA_CN, NULL }; + struct ldb_message_element *el; + size_t num_services = 0; + size_t num_servicegroups = 0; + size_t i; + char *member_dn; + char *filter; + size_t count; + struct ldb_message **msgs; + const char *name; + + DEBUG(SSSDBG_TRACE_LIBS, + "Processing PAM services for rule [%s]\n", rule_name); + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + new_services = talloc_zero(tmp_ctx, struct hbac_rule_element); + if (new_services == NULL) { + ret = ENOMEM; + goto done; + } + + /* First check for service category */ + ret = hbac_get_category(rule_attrs, IPA_SERVICE_CATEGORY, + &new_services->category); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify service categories\n"); + goto done; + } + if (new_services->category & HBAC_CATEGORY_ALL) { + /* Short-cut to the exit */ + ret = EOK; + goto done; + } + + /* Get the list of DNs from the member attr */ + ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_SERVICE, &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_get_el failed.\n"); + goto done; + } + if (ret == ENOENT || el->num_values == 0) { + el->num_values = 0; + DEBUG(SSSDBG_CONF_SETTINGS, + "No services specified, rule will never apply.\n"); + } + + /* Assume maximum size; We'll trim it later */ + new_services->names = talloc_array(new_services, + const char *, + el->num_values +1); + if (new_services->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_services->groups = talloc_array(new_services, + const char *, + el->num_values + 1); + if (new_services->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < el->num_values; i++) { + ret = sss_filter_sanitize(tmp_ctx, + (const char *)el->values[i].data, + &member_dn); + if (ret != EOK) goto done; + + filter = talloc_asprintf(member_dn, "(%s=%s)", + SYSDB_ORIG_DN, member_dn); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + /* First check if this is a specific service */ + ret = sysdb_search_custom(tmp_ctx, domain, filter, + HBAC_SERVICES_SUBDIR, attrs, + &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple services. " + "Skipping \n"); + talloc_zfree(member_dn); + continue; + } + + /* Original DN matched a single service. Get the service name */ + name = ldb_msg_find_attr_as_string(msgs[0], IPA_CN, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Attribute IPA_CN is missing!\n"); + ret = EFAULT; + goto done; + } + + new_services->names[num_services] = + talloc_strdup(new_services->names, name); + if (new_services->names[num_services] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Added service [%s] to rule [%s]\n", + name, rule_name); + num_services++; + } else { /* ret == ENOENT */ + /* Check if this is a service group */ + ret = sysdb_search_custom(tmp_ctx, domain, filter, + HBAC_SERVICEGROUPS_SUBDIR, attrs, + &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple service groups. " + "Skipping\n"); + talloc_zfree(member_dn); + continue; + } + + /* Original DN matched a single group. Get the groupname */ + name = ldb_msg_find_attr_as_string(msgs[0], IPA_CN, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Attribute IPA_CN is missing!\n"); + ret = EFAULT; + goto done; + } + + new_services->groups[num_servicegroups] = + talloc_strdup(new_services->groups, name); + if (new_services->groups[num_servicegroups] == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added service group [%s] to rule [%s]\n", + name, rule_name); + num_servicegroups++; + } else { /* ret == ENOENT */ + /* Neither a service nor a service group? Skip it */ + DEBUG(SSSDBG_CRIT_FAILURE, + "[%s] does not map to either a service or " + "service group. Skipping\n", member_dn); + } + } + talloc_zfree(member_dn); + } + new_services->names[num_services] = NULL; + new_services->groups[num_servicegroups] = NULL; + + /* Shrink the arrays down to their real sizes */ + new_services->names = talloc_realloc(new_services, new_services->names, + const char *, num_services + 1); + if (new_services->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_services->groups = talloc_realloc(new_services, new_services->groups, + const char *, num_servicegroups + 1); + if (new_services->groups == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *services = talloc_steal(mem_ctx, new_services); + } + talloc_free(tmp_ctx); + return ret; +} + +errno_t +get_ipa_servicegroupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *service_dn, + char **servicegroupname) +{ + errno_t ret; + struct ldb_dn *dn; + const char *rdn_name; + const char *svc_comp_name; + const char *hbac_comp_name; + const struct ldb_val *rdn_val; + const struct ldb_val *svc_comp_val; + const struct ldb_val *hbac_comp_val; + + /* This is an IPA-specific hack. It may not + * work for non-IPA servers and will need to + * be changed if SSSD ever supports HBAC on + * a non-IPA server. + */ + *servicegroupname = NULL; + + dn = ldb_dn_new(mem_ctx, sysdb_ctx_get_ldb(sysdb), service_dn); + if (dn == NULL) { + ret = ENOMEM; + goto done; + } + + if (!ldb_dn_validate(dn)) { + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (ldb_dn_get_comp_num(dn) < 4) { + /* RDN, services, hbac, and at least one DC= */ + /* If it's fewer, it's not a group DN */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* If the RDN name is 'cn' */ + rdn_name = ldb_dn_get_rdn_name(dn); + if (rdn_name == NULL) { + /* Shouldn't happen if ldb_dn_validate() + * passed, but we'll be careful. + */ + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (strcasecmp("cn", rdn_name) != 0) { + /* RDN has the wrong attribute name. + * It's not a service. + */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the second component is "cn=hbacservicegroups" */ + svc_comp_name = ldb_dn_get_component_name(dn, 1); + if (strcasecmp("cn", svc_comp_name) != 0) { + /* The second component name is not "cn" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + svc_comp_val = ldb_dn_get_component_val(dn, 1); + if (strncasecmp("hbacservicegroups", + (const char *) svc_comp_val->data, + svc_comp_val->length) != 0) { + /* The second component value is not "hbacservicegroups" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the third component is "hbac" */ + hbac_comp_name = ldb_dn_get_component_name(dn, 2); + if (strcasecmp("cn", hbac_comp_name) != 0) { + /* The third component name is not "cn" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + hbac_comp_val = ldb_dn_get_component_val(dn, 2); + if (strncasecmp("hbac", + (const char *) hbac_comp_val->data, + hbac_comp_val->length) != 0) { + /* The third component value is not "hbac" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* Then the value of the RDN is the group name */ + rdn_val = ldb_dn_get_rdn_val(dn); + *servicegroupname = talloc_strndup(mem_ctx, + (const char *)rdn_val->data, + rdn_val->length); + if (*servicegroupname == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + talloc_free(dn); + return ret; +} diff --git a/src/providers/ipa/ipa_hbac_users.c b/src/providers/ipa/ipa_hbac_users.c new file mode 100644 index 0000000..2f9e986 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_users.c @@ -0,0 +1,369 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ldap/sdap_async.h" + +/* Returns EOK and populates groupname if + * the group_dn is actually a group. + * Returns ENOENT if group_dn does not point + * at a group. + * Returns EINVAL if there is a parsing error. + * Returns ENOMEM as appropriate + */ +errno_t +get_ipa_groupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *group_dn, + const char **groupname) +{ + errno_t ret; + struct ldb_dn *dn; + const char *rdn_name; + const char *group_comp_name; + const char *account_comp_name; + const struct ldb_val *rdn_val; + const struct ldb_val *group_comp_val; + const struct ldb_val *account_comp_val; + + /* This is an IPA-specific hack. It may not + * work for non-IPA servers and will need to + * be changed if SSSD ever supports HBAC on + * a non-IPA server. + */ + *groupname = NULL; + + DEBUG(SSSDBG_TRACE_LIBS, "Parsing %s\n", group_dn); + + dn = ldb_dn_new(mem_ctx, sysdb_ctx_get_ldb(sysdb), group_dn); + if (dn == NULL) { + ret = ENOMEM; + goto done; + } + + if (!ldb_dn_validate(dn)) { + DEBUG(SSSDBG_CRIT_FAILURE, "DN %s does not validate\n", group_dn); + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (ldb_dn_get_comp_num(dn) < 4) { + /* RDN, groups, accounts, and at least one DC= */ + /* If it's fewer, it's not a group DN */ + DEBUG(SSSDBG_CRIT_FAILURE, "DN %s has too few components\n", group_dn); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* If the RDN name is 'cn' */ + rdn_name = ldb_dn_get_rdn_name(dn); + if (rdn_name == NULL) { + /* Shouldn't happen if ldb_dn_validate() + * passed, but we'll be careful. + */ + DEBUG(SSSDBG_CRIT_FAILURE, "No RDN name in %s\n", group_dn); + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (strcasecmp("cn", rdn_name) != 0) { + /* RDN has the wrong attribute name. + * It's not a group. + */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Expected cn in RDN, got %s\n", rdn_name); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the second component is "cn=groups" */ + group_comp_name = ldb_dn_get_component_name(dn, 1); + if (strcasecmp("cn", group_comp_name) != 0) { + /* The second component name is not "cn" */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected cn in second component, got %s\n", group_comp_name); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + group_comp_val = ldb_dn_get_component_val(dn, 1); + if (strncasecmp("groups", + (const char *) group_comp_val->data, + group_comp_val->length) != 0) { + /* The second component value is not "groups" */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected groups second component, got %s\n", + (const char *) group_comp_val->data); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the third component is "accounts" */ + account_comp_name = ldb_dn_get_component_name(dn, 2); + if (strcasecmp("cn", account_comp_name) != 0) { + /* The third component name is not "cn" */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected cn in third component, got %s\n", account_comp_name); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + account_comp_val = ldb_dn_get_component_val(dn, 2); + if (strncasecmp("accounts", + (const char *) account_comp_val->data, + account_comp_val->length) != 0) { + /* The third component value is not "accounts" */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected accounts third component, got %s\n", + (const char *) account_comp_val->data); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* Then the value of the RDN is the group name */ + rdn_val = ldb_dn_get_rdn_val(dn); + *groupname = talloc_strndup(mem_ctx, + (const char *)rdn_val->data, + rdn_val->length); + if (*groupname == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_LIBS, "Parsed %s out of the DN\n", *groupname); + + ret = EOK; + +done: + talloc_free(dn); + return ret; +} + +errno_t +hbac_user_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **users) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct hbac_rule_element *new_users = NULL; + struct ldb_message_element *el = NULL; + struct ldb_message **msgs = NULL; + const char *member_dn; + const char *attrs[] = { SYSDB_NAME, NULL }; + size_t num_users = 0; + size_t num_groups = 0; + const char *sysdb_name; + char *shortname; + + size_t count; + size_t i; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + new_users = talloc_zero(tmp_ctx, struct hbac_rule_element); + if (new_users == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Processing users for rule [%s]\n", rule_name); + + ret = hbac_get_category(rule_attrs, IPA_USER_CATEGORY, + &new_users->category); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify user categories\n"); + goto done; + } + if (new_users->category & HBAC_CATEGORY_ALL) { + /* Short-cut to the exit */ + ret = EOK; + goto done; + } + + ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_USER, &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_get_el failed.\n"); + goto done; + } + if (ret == ENOENT || el->num_values == 0) { + el->num_values = 0; + DEBUG(SSSDBG_CONF_SETTINGS, + "No user specified, rule will never apply.\n"); + } + + new_users->names = talloc_array(new_users, + const char *, + el->num_values + 1); + if (new_users->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_users->groups = talloc_array(new_users, + const char *, + el->num_values + 1); + if (new_users->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < el->num_values; i++) { + member_dn = (const char *)el->values[i].data; + + /* First check if this is a user */ + ret = sysdb_search_users_by_orig_dn(tmp_ctx, domain, member_dn, attrs, + &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple users. Skipping \n"); + continue; + } + + /* Original DN matched a single user. Get the username */ + sysdb_name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL); + if (sysdb_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Attribute is missing!\n"); + ret = EFAULT; + goto done; + } + + ret = sss_parse_internal_fqname(tmp_ctx, sysdb_name, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse %s, skipping\n", sysdb_name); + continue; + } + + new_users->names[num_users] = talloc_strdup(new_users->names, + shortname); + if (new_users->names[num_users] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added user [%s] to rule [%s]\n", sysdb_name, rule_name); + num_users++; + } else { + /* Check if it is a group instead */ + ret = sysdb_search_groups_by_orig_dn(tmp_ctx, domain, member_dn, + attrs, &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple groups. " + "Skipping\n"); + continue; + } + + /* Original DN matched a single group. Get the groupname */ + sysdb_name = ldb_msg_find_attr_as_string(msgs[0], + SYSDB_NAME, NULL); + if (sysdb_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Attribute is missing!\n"); + ret = EFAULT; + goto done; + } + + ret = sss_parse_internal_fqname(tmp_ctx, sysdb_name, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse %s, skipping\n", sysdb_name); + continue; + } + + new_users->groups[num_groups] = + talloc_strdup(new_users->groups, shortname); + if (new_users->groups[num_groups] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added POSIX group [%s] to rule [%s]\n", + sysdb_name, rule_name); + num_groups++; + } else { + /* If the group still matches the group pattern, + * we can assume it is a non-POSIX group. + */ + ret = get_ipa_groupname(new_users->groups, domain->sysdb, + member_dn, + &new_users->groups[num_groups]); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added non-POSIX group [%s] to rule [%s]\n", + new_users->groups[num_groups], rule_name); + num_groups++; + } else { + /* Not a group, so we don't care about it */ + DEBUG(SSSDBG_TRACE_FUNC, + "[%s] does not map to either a user or group. " + "Maybe it is an object which is currently not in the " + "cache. Skipping\n", member_dn); + } + } + } + } + new_users->names[num_users] = NULL; + new_users->groups[num_groups] = NULL; + + /* Shrink the arrays down to their real sizes */ + new_users->names = talloc_realloc(new_users, new_users->names, + const char *, num_users + 1); + if (new_users->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_users->groups = talloc_realloc(new_users, new_users->groups, + const char *, num_groups + 1); + if (new_users->groups == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; +done: + if (ret == EOK) { + *users = talloc_steal(mem_ctx, new_users); + } + talloc_free(tmp_ctx); + + return ret; +} diff --git a/src/providers/ipa/ipa_hostid.c b/src/providers/ipa/ipa_hostid.c new file mode 100644 index 0000000..891536f --- /dev/null +++ b/src/providers/ipa/ipa_hostid.c @@ -0,0 +1,30 @@ +/* + Authors: + Hristo Venev + + Copyright (C) 2017 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ipa/ipa_common.h" +#include "providers/ldap/sdap_hostid.h" + +errno_t ipa_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + return sdap_hostid_init(mem_ctx, be_ctx, id_ctx->sdap_id_ctx, dp_methods); +} diff --git a/src/providers/ipa/ipa_hosts.c b/src/providers/ipa/ipa_hosts.c new file mode 100644 index 0000000..e209bca --- /dev/null +++ b/src/providers/ipa/ipa_hosts.c @@ -0,0 +1,365 @@ +/* + SSSD + + Authors: + Jan Zeleny + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ipa/ipa_hosts.h" +#include "providers/ipa/ipa_common.h" + +struct ipa_host_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_options *opts; + const char **attrs; + struct sdap_attr_map *hostgroup_map; + + struct sdap_search_base **search_bases; + int search_base_iter; + + char *cur_filter; + char *host_filter; + + const char *hostname; + + /* Return values */ + size_t host_count; + struct sysdb_attrs **hosts; + + size_t hostgroup_count; + struct sysdb_attrs **hostgroups; +}; + +static void +ipa_host_info_done(struct tevent_req *subreq); +static void +ipa_hostgroup_info_done(struct tevent_req *subreq); +static errno_t +ipa_hostgroup_info_next(struct tevent_req *req, + struct ipa_host_state *state); + +/** + * hostname == NULL -> look up all hosts / host groups + * hostname != NULL -> look up only given host and groups + * it's member of + * hostgroup_map == NULL -> skip looking up hostgroups + */ +struct tevent_req * +ipa_host_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *hostname, + struct sdap_attr_map *host_map, + struct sdap_attr_map *hostgroup_map, + struct sdap_search_base **search_bases) +{ + struct ipa_host_state *state; + struct tevent_req *req, *subreq; + + req = tevent_req_create(mem_ctx, &state, struct ipa_host_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->sh = sh; + state->opts = opts; + state->hostname = hostname; + state->search_bases = search_bases; + state->search_base_iter = 0; + state->cur_filter = NULL; + state->hostgroup_map = hostgroup_map; + + subreq = sdap_host_info_send(mem_ctx, ev, sh, opts, hostname, host_map, + search_bases); + if (subreq == NULL) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, ipa_host_info_done, req); + + return req; +} + +static void +ipa_host_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_host_state *state = + tevent_req_data(req, struct ipa_host_state); + const char *host_dn; + struct sdap_attr_map_info *maps; + const int num_maps = 1; + + ret = sdap_host_info_recv(subreq, state, + &state->host_count, + &state->hosts); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->hostgroup_map) { + ret = build_attrs_from_map(state, state->hostgroup_map, + IPA_OPTS_HOSTGROUP, NULL, + &state->attrs, NULL); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Look up host groups */ + if (state->hostname == NULL) { + talloc_zfree(state->host_filter); + state->host_filter = talloc_asprintf(state, "(objectClass=%s)", + state->hostgroup_map[IPA_OC_HOSTGROUP].name); + if (state->host_filter == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + state->search_base_iter = 0; + + ret = ipa_hostgroup_info_next(req, state); + if (ret == EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "No host search base configured?\n"); + tevent_req_error(req, EINVAL); + return; + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + } else { + ret = sysdb_attrs_get_string(state->hosts[0], SYSDB_ORIG_DN, &host_dn); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (!sdap_has_deref_support_ex(state->sh, state->opts, true)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Server does not support deref\n"); + tevent_req_error(req, EIO); + return; + } + + maps = talloc_array(state, struct sdap_attr_map_info, num_maps + 1); + if (maps == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + maps[0].map = state->hostgroup_map; + maps[0].num_attrs = IPA_OPTS_HOSTGROUP; + maps[1].map = NULL; + + subreq = sdap_deref_search_send(state, state->ev, state->opts, state->sh, + host_dn, + state->hostgroup_map[IPA_AT_HOSTGROUP_MEMBER_OF].name, + state->attrs, + num_maps, maps, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT)); + if (subreq == NULL) { + talloc_free(maps); + DEBUG(SSSDBG_CRIT_FAILURE, "Error requesting host info\n"); + tevent_req_error(req, EIO); + return; + } + tevent_req_set_callback(subreq, ipa_hostgroup_info_done, req); + } + } else { + /* Nothing else to do, just complete the req */ + tevent_req_done(req); + } +} + +static errno_t ipa_hostgroup_info_next(struct tevent_req *req, + struct ipa_host_state *state) +{ + struct sdap_search_base *base; + struct tevent_req *subreq; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->host_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base->basedn, base->scope, + state->cur_filter, state->attrs, + state->hostgroup_map, + IPA_OPTS_HOSTGROUP, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error requesting hostgroup info\n"); + talloc_zfree(state->cur_filter); + return EIO; + } + tevent_req_set_callback(subreq, ipa_hostgroup_info_done, req); + + return EAGAIN; +} + +static void +ipa_hostgroup_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_host_state *state = + tevent_req_data(req, struct ipa_host_state); + + size_t hostgroups_total; + size_t hostgroup_count; + struct sysdb_attrs **hostgroups; + struct sdap_deref_attrs **deref_result; + const char *hostgroup_name; + const char *hostgroup_dn; + int i, j; + + if (state->hostname == NULL) { + ret = sdap_get_generic_recv(subreq, state, + &hostgroup_count, + &hostgroups); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_get_generic_recv failed: [%d]\n", ret); + tevent_req_error(req, ret); + return; + } + + /* Merge the two arrays */ + if (hostgroup_count > 0) { + hostgroups_total = hostgroup_count + state->hostgroup_count; + state->hostgroups = talloc_realloc(state, state->hostgroups, + struct sysdb_attrs *, + hostgroups_total); + if (state->hostgroups == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + i = 0; + while(state->hostgroup_count < hostgroups_total) { + state->hostgroups[state->hostgroup_count] = + talloc_steal(state->hostgroups, hostgroups[i]); + state->hostgroup_count++; + i++; + } + } + + /* Now look in the next base */ + state->search_base_iter++; + ret = ipa_hostgroup_info_next(req, state); + if (ret != EOK && ret != EAGAIN) { + tevent_req_error(req, ret); + } + + if (ret != EOK) { + /* Only continue if no error occurred + * and no req was created */ + return; + } + } else { + ret = sdap_deref_search_recv(subreq, state, + &state->hostgroup_count, + &deref_result); + talloc_zfree(subreq); + if (ret != EOK) goto done; + + if (state->hostgroup_count == 0) { + DEBUG(SSSDBG_FUNC_DATA, "No host groups were dereferenced\n"); + } else { + state->hostgroups = talloc_zero_array(state, struct sysdb_attrs *, + state->hostgroup_count); + if (state->hostgroups == NULL) { + ret = ENOMEM; + goto done; + } + + j = 0; + for (i = 0; i < state->hostgroup_count; i++) { + ret = sysdb_attrs_get_string(deref_result[i]->attrs, + SYSDB_ORIG_DN, &hostgroup_dn); + if (ret != EOK) goto done; + + if (!sss_ldap_dn_in_search_bases(state, hostgroup_dn, + state->search_bases, + NULL)) { + continue; + } + + ret = sysdb_attrs_get_string(deref_result[i]->attrs, + state->hostgroup_map[IPA_AT_HOSTGROUP_NAME].sys_name, + &hostgroup_name); + if (ret != EOK) goto done; + + DEBUG(SSSDBG_FUNC_DATA, "Dereferenced host group: %s\n", + hostgroup_name); + state->hostgroups[j] = talloc_steal(state->hostgroups, + deref_result[i]->attrs); + j++; + } + state->hostgroup_count = j; + } + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Error [%d][%s]\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } +} + +errno_t ipa_host_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *host_count, + struct sysdb_attrs ***hosts, + size_t *hostgroup_count, + struct sysdb_attrs ***hostgroups) +{ + struct ipa_host_state *state = + tevent_req_data(req, struct ipa_host_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *host_count = state->host_count; + *hosts = talloc_steal(mem_ctx, state->hosts); + + if (hostgroup_count) *hostgroup_count = state->hostgroup_count; + if (hostgroups) *hostgroups = talloc_steal(mem_ctx, state->hostgroups); + + return EOK; +} diff --git a/src/providers/ipa/ipa_hosts.h b/src/providers/ipa/ipa_hosts.h new file mode 100644 index 0000000..a1ea7a2 --- /dev/null +++ b/src/providers/ipa/ipa_hosts.h @@ -0,0 +1,44 @@ +/* + SSSD + + Authors: + Jan Zeleny + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_HOSTS_H_ +#define IPA_HOSTS_H_ + +struct tevent_req * +ipa_host_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *hostname, + struct sdap_attr_map *host_map, + struct sdap_attr_map *hostgroup_map, + struct sdap_search_base **search_bases); + +errno_t +ipa_host_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *host_count, + struct sysdb_attrs ***hosts, + size_t *hostgroup_count, + struct sysdb_attrs ***hostgroups); + +#endif /* IPA_HOSTS_H_ */ diff --git a/src/providers/ipa/ipa_id.c b/src/providers/ipa/ipa_id.c new file mode 100644 index 0000000..fcac56c --- /dev/null +++ b/src/providers/ipa/ipa_id.c @@ -0,0 +1,1562 @@ +/* + SSSD + + IPA Identity Backend Module + + Authors: + Jan Zeleny + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ipa/ipa_id.h" + +static bool is_object_overridable(struct dp_id_data *ar) +{ + bool ret = false; + + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: + case BE_REQ_GROUP: + case BE_REQ_INITGROUPS: + case BE_REQ_BY_SECID: + case BE_REQ_USER_AND_GROUP: + case BE_REQ_BY_UUID: + case BE_REQ_BY_CERT: + ret = true; + break; + default: + break; + } + + return ret; +} + +struct ipa_resolve_user_list_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct ldb_message_element *users; + const char *domain_name; + struct sss_domain_info *domain; + struct sss_domain_info *user_domain; + size_t user_idx; + + int dp_error; +}; + +static errno_t ipa_resolve_user_list_get_user_step(struct tevent_req *req); +static void ipa_resolve_user_list_get_user_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_resolve_user_list_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + const char *domain_name, + struct ldb_message_element *users) +{ + int ret; + struct tevent_req *req; + struct ipa_resolve_user_list_state *state; + + req = tevent_req_create(memctx, &state, + struct ipa_resolve_user_list_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->domain_name = domain_name; + state->domain = find_domain_by_name(state->ipa_ctx->sdap_id_ctx->be->domain, + state->domain_name, true); + state->users = users; + state->user_idx = 0; + state->dp_error = DP_ERR_FATAL; + + ret = ipa_resolve_user_list_get_user_step(req); + if (ret == EAGAIN) { + return req; + } else if (ret == EOK) { + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_resolve_user_list_get_user_step failed.\n"); + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_resolve_user_list_get_user_step(struct tevent_req *req) +{ + int ret; + struct tevent_req *subreq; + struct dp_id_data *ar; + struct ipa_resolve_user_list_state *state = tevent_req_data(req, + struct ipa_resolve_user_list_state); + + if (state->user_idx >= state->users->num_values) { + return EOK; + } + + ret = get_dp_id_data_for_user_name(state, + (char *) state->users->values[state->user_idx].data, + state->domain_name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_user_name failed.\n"); + return ret; + } + + DEBUG(SSSDBG_TRACE_ALL, "Trying to resolve user [%s].\n", ar->filter_value); + + state->user_domain = find_domain_by_object_name_ex( + state->ipa_ctx->sdap_id_ctx->be->domain, + ar->filter_value, true, + SSS_GND_DESCEND); + /* Use provided domain as fallback because no known domain was found in the + * user name. */ + if (state->user_domain == NULL) { + state->user_domain = state->domain; + } + ar->domain = state->user_domain->name; + + if (state->user_domain != state->ipa_ctx->sdap_id_ctx->be->domain) { + subreq = ipa_subdomain_account_send(state, state->ev, state->ipa_ctx, + ar); + } else { + subreq = ipa_id_get_account_info_send(state, state->ev, state->ipa_ctx, + ar); + } + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_handle_acct_req_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_resolve_user_list_get_user_done, req); + + return EAGAIN; +} + +static void ipa_resolve_user_list_get_user_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_resolve_user_list_state *state = tevent_req_data(req, + struct ipa_resolve_user_list_state); + int ret; + + if (state->user_domain != state->ipa_ctx->sdap_id_ctx->be->domain) { + ret = ipa_subdomain_account_recv(subreq, &state->dp_error); + } else { + ret = ipa_id_get_account_info_recv(subreq, &state->dp_error); + } + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_handle_acct request failed: %d\n", ret); + goto done; + } + + state->user_idx++; + + ret = ipa_resolve_user_list_get_user_step(req); + if (ret == EAGAIN) { + return; + } + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_resolve_user_list_get_user_step failed.\n"); + } + +done: + if (ret == EOK) { + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + } else { + if (state->dp_error == DP_ERR_OK) { + state->dp_error = DP_ERR_FATAL; + } + tevent_req_error(req, ret); + } + return; +} + +int ipa_resolve_user_list_recv(struct tevent_req *req, int *dp_error) +{ + struct ipa_resolve_user_list_state *state = tevent_req_data(req, + struct ipa_resolve_user_list_state); + + if (dp_error) { + *dp_error = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_initgr_get_overrides_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sss_domain_info *user_dom; + const char *realm; + + struct ldb_message **groups; + size_t group_count; + const char *groups_id_attr; + size_t group_idx; + struct dp_id_data *ar; + + int dp_error; +}; + +static int ipa_initgr_get_overrides_step(struct tevent_req *req); + +struct tevent_req * +ipa_initgr_get_overrides_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *user_dom, + size_t groups_count, + struct ldb_message **groups, + const char *groups_id_attr) +{ + int ret; + struct tevent_req *req; + struct ipa_initgr_get_overrides_state *state; + + req = tevent_req_create(memctx, &state, + struct ipa_initgr_get_overrides_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->user_dom = user_dom; + state->groups = groups; + state->group_count = groups_count; + state->group_idx = 0; + state->ar = NULL; + state->realm = dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM); + if (state->realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm for IPA?\n"); + ret = EINVAL; + goto done; + } + state->groups_id_attr = talloc_strdup(state, groups_id_attr); + if (state->groups_id_attr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = ipa_initgr_get_overrides_step(req); +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ipa_initgr_get_overrides_override_done(struct tevent_req *subreq); + +static int ipa_initgr_get_overrides_step(struct tevent_req *req) +{ + int ret; + struct tevent_req *subreq; + const char *ipa_uuid; + const char *dn; + struct ipa_initgr_get_overrides_state *state = tevent_req_data(req, + struct ipa_initgr_get_overrides_state); + + for (; state->group_idx < state->group_count; state->group_idx++) { + dn = ldb_dn_get_linearized(state->groups[state->group_idx]->dn); + + DEBUG(SSSDBG_TRACE_LIBS, "Processing group %s (%zu/%zu)\n", + dn, state->group_idx, state->group_count); + + ipa_uuid = ldb_msg_find_attr_as_string(state->groups[state->group_idx], + state->groups_id_attr, NULL); + if (ipa_uuid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "The group %s has no UUID attribute %s, error!\n", + dn, state->groups_id_attr); + continue; + } + + talloc_free(state->ar); /* Avoid spiking memory with many groups */ + + if (strcmp(state->groups_id_attr, SYSDB_UUID) == 0) { + ret = get_dp_id_data_for_uuid(state, ipa_uuid, + state->user_dom->name, &state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + return ret; + } + } else if (strcmp(state->groups_id_attr, SYSDB_SID_STR) == 0) { + ret = get_dp_id_data_for_sid(state, ipa_uuid, + state->user_dom->name, &state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + return ret; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported groups ID type [%s].\n", + state->groups_id_attr); + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Fetching group %s: %s\n", dn, ipa_uuid); + + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + state->realm, + state->ipa_ctx->view_name, + state->ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, + ipa_initgr_get_overrides_override_done, req); + return EAGAIN; + } + + return EOK; +} + +static void ipa_initgr_get_overrides_override_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_initgr_get_overrides_state *state = tevent_req_data(req, + struct ipa_initgr_get_overrides_state); + int ret; + struct sysdb_attrs *override_attrs = NULL; + + ret = ipa_get_ad_override_recv(subreq, &state->dp_error, state, + &override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + tevent_req_error(req, ret); + return; + } + + if (is_default_view(state->ipa_ctx->view_name)) { + ret = sysdb_apply_default_override(state->user_dom, override_attrs, + state->groups[state->group_idx]->dn); + } else { + ret = sysdb_store_override(state->user_dom, + state->ipa_ctx->view_name, + SYSDB_MEMBER_GROUP, + override_attrs, + state->groups[state->group_idx]->dn); + } + talloc_free(override_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_store_override failed.\n"); + tevent_req_error(req, ret); + return; + } + + state->group_idx++; + + ret = ipa_initgr_get_overrides_step(req); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int ipa_initgr_get_overrides_recv(struct tevent_req *req, int *dp_error) +{ + struct ipa_initgr_get_overrides_state *state = tevent_req_data(req, + struct ipa_initgr_get_overrides_state); + + if (dp_error) { + *dp_error = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +/* Given a user name, retrieve an array of group UUIDs of groups that have + * no overrideDN attribute but do have an UUID attribute. + */ +static errno_t ipa_id_get_group_uuids(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + size_t *_msgs_count, + struct ldb_message ***_msgs) +{ + const char *filter; + TALLOC_CTX *tmp_ctx; + char **uuid_list = NULL; + errno_t ret; + struct ldb_dn *base_dn; + const char *attrs[] = { SYSDB_UUID, NULL }; + size_t msgs_count; + struct ldb_message **msgs; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = talloc_asprintf(tmp_ctx, + "(&(%s=%s)(!(%s=*))(%s=*))", + SYSDB_OBJECTCATEGORY, + SYSDB_GROUP_CLASS, SYSDB_OVERRIDE_DN, + SYSDB_UUID); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + base_dn = sysdb_base_dn(sysdb, tmp_ctx); + if (base_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, sysdb, base_dn, + LDB_SCOPE_SUBTREE, filter, attrs, + &msgs_count, &msgs); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, + "No groups without %s in sysdb\n", SYSDB_OVERRIDE_DN); + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + + uuid_list = talloc_zero_array(tmp_ctx, char *, msgs_count); + if (uuid_list == NULL) { + goto done; + } + + *_msgs_count = msgs_count; + *_msgs = talloc_steal(mem_ctx, msgs); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +struct ipa_id_get_account_info_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sdap_id_ctx *ctx; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct dp_id_data *ar; + struct dp_id_data *orig_ar; + const char *realm; + + struct sysdb_attrs *override_attrs; + struct ldb_message *obj_msg; + struct ldb_message_element *ghosts; + + struct ldb_message **user_groups; + size_t group_cnt; + size_t group_idx; + + struct ldb_result *res; + size_t res_index; + int dp_error; +}; + +static void ipa_id_get_account_info_connected(struct tevent_req *subreq); +static void ipa_id_get_account_info_got_override(struct tevent_req *subreq); +static errno_t ipa_id_get_account_info_get_original_step(struct tevent_req *req, + struct dp_id_data *ar); +static void ipa_id_get_account_info_orig_done(struct tevent_req *subreq); +static void ipa_id_get_account_info_done(struct tevent_req *subreq); +static void ipa_id_get_user_list_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_id_get_account_info_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct dp_id_data *ar) +{ + int ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_id_get_account_info_state *state; + + req = tevent_req_create(memctx, &state, + struct ipa_id_get_account_info_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->ctx = ipa_ctx->sdap_id_ctx; + state->dp_error = DP_ERR_FATAL; + + state->op = sdap_id_op_create(state, state->ctx->conn->conn_cache); + if (state->op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = find_domain_by_name(state->ctx->be->domain, + ar->domain, true); + if (state->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); + ret = ENOMEM; + goto fail; + } + state->sysdb = state->domain->sysdb; + state->ar = ar; + state->realm = dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM); + if (state->realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm for IPA?\n"); + ret = EINVAL; + goto fail; + } + + /* We can skip the override lookup and go directly to the original object + * if + * - the lookup is by SID + * - there is no view set of it is the default view + * - if the EXTRA_INPUT_MAYBE_WITH_VIEW flag is not set + */ + if (is_default_view(state->ipa_ctx->view_name) + || state->ar->filter_type == BE_FILTER_SECID + || state->ar->extra_value == NULL + || strcmp(state->ar->extra_value, + EXTRA_INPUT_MAYBE_WITH_VIEW) != 0 + || ! is_object_overridable(state->ar)) { + ret = ipa_id_get_account_info_get_original_step(req, ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_subdomain_account_get_original_step failed.\n"); + goto fail; + } + } else { + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed.\n"); + goto fail; + } + tevent_req_set_callback(subreq, ipa_id_get_account_info_connected, req); + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ipa_id_get_account_info_connected(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect request failed.\n"); + goto fail; + } + + subreq = ipa_get_ad_override_send(state, state->ev, state->ctx, + state->ipa_ctx->ipa_options, state->realm, + state->ipa_ctx->view_name, state->ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_id_get_account_info_got_override, req); + + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static void ipa_id_get_account_info_got_override(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + const char *anchor = NULL; + char *anchor_domain; + char *ipa_uuid; + + ret = ipa_get_ad_override_recv(subreq, &dp_error, state, + &state->override_attrs); + talloc_zfree(subreq); + + if (ret != EOK) { + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed.\n"); + goto fail; + } + tevent_req_set_callback(subreq, ipa_id_get_account_info_connected, + req); + return; + } + + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + goto fail; + } + + if (state->override_attrs != NULL) { + ret = sysdb_attrs_get_string(state->override_attrs, + SYSDB_OVERRIDE_ANCHOR_UUID, + &anchor); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto fail; + } + + ret = split_ipa_anchor(state, anchor, &anchor_domain, &ipa_uuid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported override anchor [%s].\n", anchor); + ret = EINVAL; + goto fail; + } + + if (strcmp(state->ar->domain, anchor_domain) == 0) { + + state->orig_ar = state->ar; + + ret = get_dp_id_data_for_uuid(state, ipa_uuid, + state->ar->domain, + &state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_uuid failed.\n"); + goto fail; + } + + if ((state->orig_ar->entry_type & BE_REQ_TYPE_MASK) + == BE_REQ_INITGROUPS) { + DEBUG(SSSDBG_TRACE_ALL, + "Switching back to BE_REQ_INITGROUPS.\n"); + state->ar->entry_type = BE_REQ_INITGROUPS; + state->ar->filter_type = BE_FILTER_UUID; + } + + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Anchor from a different domain [%s], expected [%s]. " \ + "This is currently not supported, continue lookup in " \ + "local IPA domain.\n", + anchor_domain, state->ar->domain); + } + } + + ret = ipa_id_get_account_info_get_original_step(req, state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_subdomain_account_get_original_step failed.\n"); + goto fail; + } + + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static errno_t ipa_id_get_account_info_get_original_step(struct tevent_req *req, + struct dp_id_data *ar) +{ + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + struct tevent_req *subreq; + +#ifdef BUILD_SUBID + if ((ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_SUBID_RANGES) { + if (!state->ctx->opts->sdom->subid_ranges_search_bases || + !state->ctx->opts->sdom->subid_ranges_search_bases[0] || + !state->ctx->opts->sdom->subid_ranges_search_bases[0]->basedn) { + DEBUG(SSSDBG_OP_FAILURE, "subid_ranges_search_bases isn't set\n"); + return EINVAL; + } + ar->extra_value = talloc_asprintf(ar, + "%s=%s,"SYSDB_USERS_CONTAINER",%s", + state->ctx->opts->user_map[SDAP_AT_USER_NAME].name, + ar->filter_value, + state->ctx->opts->sdom->user_search_bases[0]->basedn); + } +#endif + + subreq = sdap_handle_acct_req_send(state, state->ctx->be, ar, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->sdap_id_ctx->opts->sdom, + state->ipa_ctx->sdap_id_ctx->conn, true); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_handle_acct_req_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_id_get_account_info_orig_done, req); + + return EOK; +} + +static int ipa_id_get_account_info_post_proc_step(struct tevent_req *req); +static void ipa_id_get_user_groups_done(struct tevent_req *subreq); + +static void ipa_id_get_account_info_orig_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + const char *attrs[] = { SYSDB_NAME, + SYSDB_UIDNUM, + SYSDB_SID_STR, + SYSDB_OBJECTCATEGORY, + SYSDB_UUID, + SYSDB_GHOST, + SYSDB_HOMEDIR, + NULL }; + + ret = sdap_handle_acct_req_recv(subreq, &dp_error, NULL, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_handle_acct request failed: %d\n", ret); + goto fail; + } + + if (! is_object_overridable(state->ar)) { + DEBUG(SSSDBG_FUNC_DATA, "Object not overridable, ending request\n"); + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + } + + /* Lookups by certificate can return muliple results and need special + * handling because get_object_from_cache() expects a unique match */ + state->res = NULL; + state->res_index = 0; + if (state->ar->filter_type == BE_FILTER_CERT) { + ret = sysdb_search_object_by_cert(state, state->domain, + state->ar->filter_value, attrs, + &(state->res)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to make request to our cache: [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto fail; + } + if (state->res->count == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Object not found in our cache.\n"); + ret = ENOENT; + goto fail; + } + + state->obj_msg = state->res->msgs[0]; + if (state->res->count == 1) { + /* Just process the unique result, no need to iterate */ + state->res = NULL; + } + } else { + ret = get_object_from_cache(state, state->domain, state->ar, + &state->obj_msg); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Object not found, ending request\n"); + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_object_from_cache failed.\n"); + goto fail; + } + } + + ret = ipa_id_get_account_info_post_proc_step(req); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_id_get_account_info_post_proc_step failed.\n"); + goto fail; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static int ipa_id_get_account_info_post_proc_step(struct tevent_req *req) +{ + int ret; + const char *uuid; + const char *class; + enum sysdb_member_type type; + struct tevent_req *subreq; + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + + class = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_OBJECTCATEGORY, + NULL); + if (class == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find an objectclass.\n"); + ret = EINVAL; + goto done; + } + + + if (!is_default_view(state->ipa_ctx->view_name)) { + + if ((state->ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_GROUP + || ((state->ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_BY_UUID + && strcmp(class, SYSDB_GROUP_CLASS) == 0)) { + /* check for ghost members because ghost members are not allowed + * if a view other than the default view is applied.*/ + state->ghosts = ldb_msg_find_element(state->obj_msg, SYSDB_GHOST); + } else if ((state->ar->entry_type & BE_REQ_TYPE_MASK) == \ + BE_REQ_INITGROUPS) { + /* Get UUID list of groups that have no overrideDN set. */ + ret = ipa_id_get_group_uuids(state, state->sysdb, + &state->group_cnt, + &state->user_groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get UUID list: %d\n", ret); + goto done; + } + } + } + + + if (state->override_attrs == NULL) { + uuid = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_UUID, NULL); + if (uuid == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find a UUID.\n"); + ret = EINVAL; + goto done; + } + + ret = get_dp_id_data_for_uuid(state, uuid, state->domain->name, + &state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto done; + } + + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + state->realm, + state->ipa_ctx->view_name, + state->ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_id_get_account_info_done, req); + ret = EAGAIN; + goto done; + } else { + if (strcmp(class, SYSDB_USER_CLASS) == 0) { + type = SYSDB_MEMBER_USER; + } else { + type = SYSDB_MEMBER_GROUP; + } + + ret = sysdb_store_override(state->domain, state->ipa_ctx->view_name, + type, + state->override_attrs, state->obj_msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_store_override failed.\n"); + goto done; + } + } + + if (state->ghosts != NULL) { + /* Resolve ghost members */ + subreq = ipa_resolve_user_list_send(state, state->ev, + state->ipa_ctx, + state->domain->name, + state->ghosts); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_id_get_user_list_done, req); + ret = EAGAIN; + goto done; + } + + if (state->user_groups != NULL) { + subreq = ipa_initgr_get_overrides_send(state, state->ev, state->ipa_ctx, + state->domain, state->group_cnt, + state->user_groups, + SYSDB_UUID); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_id_get_user_groups_done, req); + ret = EAGAIN; + goto done; + } + + ret = EOK; + +done: + if (ret == EOK && state->res != NULL + && ++state->res_index < state->res->count) { + state->obj_msg = state->res->msgs[state->res_index]; + ret = ipa_id_get_account_info_post_proc_step(req); + } + + return ret; +} + +static void ipa_id_get_account_info_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + const char *class; + enum sysdb_member_type type; + + ret = ipa_get_ad_override_recv(subreq, &dp_error, state, + &state->override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + goto fail; + } + + class = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_OBJECTCATEGORY, + NULL); + if (class == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find an objectclass.\n"); + ret = EINVAL; + goto fail; + } + + if (strcmp(class, SYSDB_USER_CLASS) == 0) { + type = SYSDB_MEMBER_USER; + } else { + type = SYSDB_MEMBER_GROUP; + } + + ret = sysdb_store_override(state->domain, state->ipa_ctx->view_name, + type, + state->override_attrs, state->obj_msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_store_override failed.\n"); + goto fail; + } + + if (state->ghosts != NULL) { + /* Resolve ghost members */ + subreq = ipa_resolve_user_list_send(state, state->ev, + state->ipa_ctx, + state->domain->name, + state->ghosts); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_id_get_user_list_done, req); + return; + } + + if (state->user_groups != NULL) { + subreq = ipa_initgr_get_overrides_send(state, state->ev, state->ipa_ctx, + state->domain, state->group_cnt, + state->user_groups, + SYSDB_UUID); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_id_get_user_groups_done, req); + return; + } + + if (state->res != NULL && ++state->res_index < state->res->count) { + state->obj_msg = state->res->msgs[state->res_index]; + ret = ipa_id_get_account_info_post_proc_step(req); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_id_get_account_info_post_proc_step failed.\n"); + goto fail; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static void ipa_id_get_user_list_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = ipa_resolve_user_list_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA resolve user list %d\n", ret); + goto fail; + } + + if (state->res != NULL && ++state->res_index < state->res->count) { + state->obj_msg = state->res->msgs[state->res_index]; + ret = ipa_id_get_account_info_post_proc_step(req); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_id_get_account_info_post_proc_step failed.\n"); + goto fail; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static void ipa_id_get_user_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = ipa_initgr_get_overrides_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA resolve user groups %d\n", ret); + goto fail; + } + + if (state->res != NULL && ++state->res_index < state->res->count) { + state->obj_msg = state->res->msgs[state->res_index]; + ret = ipa_id_get_account_info_post_proc_step(req); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_id_get_account_info_post_proc_step failed.\n"); + goto fail; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +int ipa_id_get_account_info_recv(struct tevent_req *req, int *dp_error) +{ + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + + if (dp_error) { + *dp_error = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* Request for netgroups + * - first start here and then go to ipa_netgroups.c + */ +struct ipa_id_get_netgroup_state { + struct tevent_context *ev; + struct ipa_id_ctx *ctx; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *name; + int timeout; + + char *filter; + const char **attrs; + + size_t count; + struct sysdb_attrs **netgroups; + + int dp_error; +}; + +static void ipa_id_get_netgroup_connected(struct tevent_req *subreq); +static void ipa_id_get_netgroup_done(struct tevent_req *subreq); + +static struct tevent_req *ipa_id_get_netgroup_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + const char *name) +{ + struct tevent_req *req; + struct ipa_id_get_netgroup_state *state; + struct tevent_req *subreq; + struct sdap_id_ctx *ctx; + char *clean_name; + int ret; + + ctx = ipa_ctx->sdap_id_ctx; + + req = tevent_req_create(memctx, &state, struct ipa_id_get_netgroup_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ipa_ctx; + state->dp_error = DP_ERR_FATAL; + + state->op = sdap_id_op_create(state, ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->sysdb = ctx->be->domain->sysdb; + state->domain = ctx->be->domain; + state->name = name; + state->timeout = dp_opt_get_int(ctx->opts->basic, SDAP_SEARCH_TIMEOUT); + + ret = sss_filter_sanitize(state, name, &clean_name); + if (ret != EOK) { + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + ctx->opts->netgroup_map[IPA_AT_NETGROUP_NAME].name, + clean_name, + ctx->opts->netgroup_map[IPA_OC_NETGROUP].name); + if (!state->filter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto fail; + } + talloc_zfree(clean_name); + + ret = build_attrs_from_map(state, ctx->opts->netgroup_map, + IPA_OPTS_NETGROUP, NULL, + &state->attrs, NULL); + if (ret != EOK) goto fail; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + goto fail; + } + tevent_req_set_callback(subreq, ipa_id_get_netgroup_connected, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ipa_id_get_netgroup_connected(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_id_get_netgroup_state *state = + tevent_req_data(req, struct ipa_id_get_netgroup_state); + int dp_error = DP_ERR_FATAL; + int ret; + struct sdap_id_ctx *sdap_ctx = state->ctx->sdap_id_ctx; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = ipa_get_netgroups_send(state, state->ev, state->sysdb, + state->domain, sdap_ctx->opts, + state->ctx->ipa_options, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + state->timeout); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ipa_id_get_netgroup_done, req); + + return; +} + +static void ipa_id_get_netgroup_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_id_get_netgroup_state *state = + tevent_req_data(req, struct ipa_id_get_netgroup_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = ipa_get_netgroups_recv(subreq, state, + &state->count, &state->netgroups); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, ipa_id_get_netgroup_connected, req); + return; + } + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == EOK && state->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Found more than one netgroup with the name [%s].\n", + state->name); + tevent_req_error(req, EINVAL); + return; + } + + if (ret == ENOENT) { + ret = sysdb_delete_netgroup(state->domain, state->name); + if (ret != EOK && ret != ENOENT) { + tevent_req_error(req, ret); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; +} + +static int ipa_id_get_netgroup_recv(struct tevent_req *req, int *dp_error) +{ + struct ipa_id_get_netgroup_state *state = + tevent_req_data(req, struct ipa_id_get_netgroup_state); + + if (dp_error) { + *dp_error = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +enum ipa_account_info_type { + IPA_ACCOUNT_INFO_SUBDOMAIN, + IPA_ACCOUNT_INFO_NETGROUP, + IPA_ACCOUNT_INFO_OTHER +}; + +static enum ipa_account_info_type +ipa_decide_account_info_type(struct dp_id_data *data, struct be_ctx *be_ctx) +{ + if (strcasecmp(data->domain, be_ctx->domain->name) != 0) { + return IPA_ACCOUNT_INFO_SUBDOMAIN; + } else if ((data->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_NETGROUP) { + return IPA_ACCOUNT_INFO_NETGROUP; + } + + return IPA_ACCOUNT_INFO_OTHER; +} + +struct ipa_account_info_state { + enum ipa_account_info_type type; + + const char *err_msg; + int dp_error; +}; + +static void ipa_account_info_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_account_info_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_id_data *data) +{ + struct ipa_account_info_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_account_info_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->type = ipa_decide_account_info_type(data, be_ctx); + + switch (state->type) { + case IPA_ACCOUNT_INFO_SUBDOMAIN: + /* Subdomain lookups are handled differently on server and client. */ + subreq = ipa_subdomain_account_send(state, be_ctx->ev, id_ctx, data); + break; + case IPA_ACCOUNT_INFO_NETGROUP: + if (data->filter_type != BE_FILTER_NAME) { + ret = EINVAL; + goto immediately; + } + + subreq = ipa_id_get_netgroup_send(state, be_ctx->ev, id_ctx, + data->filter_value); + break; + case IPA_ACCOUNT_INFO_OTHER: + subreq = ipa_id_get_account_info_send(state, be_ctx->ev, id_ctx, data); + break; + } + + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, ipa_account_info_done, req); + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, be_ctx->ev); + return req; +} + +static void ipa_account_info_done(struct tevent_req *subreq) +{ + struct ipa_account_info_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_account_info_state); + + switch (state->type) { + case IPA_ACCOUNT_INFO_SUBDOMAIN: + ret = ipa_subdomain_account_recv(subreq, &state->dp_error); + break; + case IPA_ACCOUNT_INFO_NETGROUP: + ret = ipa_id_get_netgroup_recv(subreq, &state->dp_error); + break; + case IPA_ACCOUNT_INFO_OTHER: + ret = ipa_id_get_account_info_recv(subreq, &state->dp_error); + break; + default: + ret = EINVAL; + break; + } + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t ipa_account_info_recv(struct tevent_req *req, + int *_dp_error) +{ + struct ipa_account_info_state *state = NULL; + + state = tevent_req_data(req, struct ipa_account_info_state); + + /* Fail the request after collecting the dp_error */ + if (_dp_error) { + *_dp_error = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ipa_account_info_handler_state { + struct dp_reply_std reply; +}; + +static void ipa_account_info_handler_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params) +{ + struct ipa_account_info_handler_state *state; + struct tevent_req *subreq = NULL; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_account_info_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sdap_is_enum_request(data)) { + DEBUG(SSSDBG_TRACE_LIBS, "Skipping enumeration on demand\n"); + ret = EOK; + goto immediately; + } + + subreq = ipa_account_info_send(state, params->be_ctx, id_ctx, data); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, ipa_account_info_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_account_info_handler_done(struct tevent_req *subreq) +{ + struct ipa_account_info_handler_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret = ERR_INTERNAL; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_account_info_handler_state); + + ret = ipa_account_info_recv(subreq, &dp_error); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +errno_t ipa_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct ipa_account_info_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_account_info_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/ipa/ipa_id.h b/src/providers/ipa/ipa_id.h new file mode 100644 index 0000000..c18e709 --- /dev/null +++ b/src/providers/ipa/ipa_id.h @@ -0,0 +1,159 @@ +/* + SSSD + + IPA Identity Backend Module + + Authors: + Jan Zeleny + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + + +#ifndef _IPA_ID_H_ +#define _IPA_ID_H_ + +#include "providers/ldap/ldap_common.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ldap/sdap.h" +#include "providers/ipa/ipa_subdomains.h" + +#define IPA_DEFAULT_VIEW_NAME "Default Trust View" + +struct tevent_req * +ipa_account_info_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_id_data *data); +errno_t ipa_account_info_recv(struct tevent_req *req, + int *_dp_error); + +struct tevent_req * +ipa_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params); + +errno_t ipa_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +struct tevent_req *ipa_get_netgroups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct ipa_options *ipa_options, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout); + +int ipa_get_netgroups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply); + +struct tevent_req *ipa_s2n_get_acct_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *override_attrs, + struct sdap_handle *sh, + int entry_type, + struct req_input *req_input); +int ipa_s2n_get_acct_info_recv(struct tevent_req *req); + +struct tevent_req *ipa_get_subdom_acct_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sysdb_attrs *override_attrs, + struct dp_id_data *ar); +int ipa_get_subdom_acct_recv(struct tevent_req *req, int *dp_error_out); + +errno_t get_dp_id_data_for_sid(TALLOC_CTX *mem_ctx, const char *sid, + const char *domain_name, + struct dp_id_data **_ar); + +errno_t get_dp_id_data_for_uuid(TALLOC_CTX *mem_ctx, const char *uuid, + const char *domain_name, + struct dp_id_data **_ar); + +errno_t get_dp_id_data_for_user_name(TALLOC_CTX *mem_ctx, + const char *user_name, + const char *domain_name, + struct dp_id_data **_ar); + +struct tevent_req *ipa_get_ad_override_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_id_ctx, + struct ipa_options *ipa_options, + const char *ipa_realm, + const char *view_name, + struct dp_id_data *ar); + +errno_t ipa_get_ad_override_recv(struct tevent_req *req, int *dp_error_out, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **override_attrs); + +struct tevent_req *ipa_subdomain_account_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct dp_id_data *ar); + +errno_t ipa_subdomain_account_recv(struct tevent_req *req, int *dp_error_out); + +errno_t split_ipa_anchor(TALLOC_CTX *mem_ctx, const char *anchor, + char **_anchor_domain, char **_ipa_uuid); + +errno_t get_object_from_cache(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct dp_id_data *ar, + struct ldb_message **_msg); + +struct tevent_req * +ipa_initgr_get_overrides_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *user_dom, + size_t groups_count, + struct ldb_message **groups, + const char *groups_id_attr); +int ipa_initgr_get_overrides_recv(struct tevent_req *req, int *dp_error); + +struct tevent_req *ipa_get_subdom_acct_process_pac_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *dom, + struct ldb_message *user_msg); + +errno_t ipa_get_subdom_acct_process_pac_recv(struct tevent_req *req); + +struct tevent_req * +ipa_resolve_user_list_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + const char *domain_name, + struct ldb_message_element *users); +int ipa_resolve_user_list_recv(struct tevent_req *req, int *dp_error); + +struct tevent_req * +ipa_id_get_account_info_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct dp_id_data *ar); +int ipa_id_get_account_info_recv(struct tevent_req *req, int *dp_error); +#endif diff --git a/src/providers/ipa/ipa_idmap.c b/src/providers/ipa/ipa_idmap.c new file mode 100644 index 0000000..5d8d56b --- /dev/null +++ b/src/providers/ipa/ipa_idmap.c @@ -0,0 +1,521 @@ +/* + SSSD + + Authors: + Sumit Bose + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + + +#include "util/util.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ipa/ipa_common.h" +#include "util/util_sss_idmap.h" + +static errno_t ipa_idmap_check_posix_child(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str, + size_t range_count, + struct range_info **range_list) +{ + bool has_algorithmic_mapping; + enum idmap_error_code err; + struct sss_domain_info *dom; + struct sss_domain_info *forest_root; + size_t c; + struct sss_idmap_range range; + struct range_info *r; + char *range_id; + TALLOC_CTX *tmp_ctx; + bool found = false; + int ret; + + err = sss_idmap_domain_has_algorithmic_mapping(idmap_ctx->map, dom_sid_str, + &has_algorithmic_mapping); + if (err == IDMAP_SUCCESS) { + DEBUG(SSSDBG_TRACE_ALL, + "Idmap of domain [%s] already known, nothing to do.\n", + dom_sid_str); + return EOK; + } else { + err = sss_idmap_domain_by_name_has_algorithmic_mapping(idmap_ctx->map, + dom_name, + &has_algorithmic_mapping); + if (err == IDMAP_SUCCESS) { + DEBUG(SSSDBG_TRACE_ALL, + "Idmap of domain [%s] already known, nothing to do.\n", + dom_sid_str); + return EOK; + } + } + DEBUG(SSSDBG_TRACE_ALL, "Trying to add idmap for domain [%s].\n", + dom_sid_str); + + if (err != IDMAP_SID_UNKNOWN && err != IDMAP_NAME_UNKNOWN) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_idmap_domain_has_algorithmic_mapping failed.\n"); + return EINVAL; + } + + dom = find_domain_by_sid(idmap_ctx->id_ctx->be->domain, dom_sid_str); + if (dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "find_domain_by_sid failed with SID [%s].\n", dom_sid_str); + return EINVAL; + } + + if (dom->forest == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No forest available for domain [%s].\n", + dom_sid_str); + return EINVAL; + } + + forest_root = find_domain_by_name(idmap_ctx->id_ctx->be->domain, + dom->forest, true); + if (forest_root == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "find_domain_by_name failed to find forest root [%s].\n", + dom->forest); + return ENOENT; + } + + if (forest_root->domain_id == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Forest root [%s] does not have a SID.\n", + dom->forest); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + for (c = 0; c < range_count; c++) { + r = range_list[c]; + if (r->trusted_dom_sid != NULL + && strcmp(r->trusted_dom_sid, forest_root->domain_id) == 0) { + + if (r->range_type == NULL + || strcmp(r->range_type, IPA_RANGE_AD_TRUST_POSIX) != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Forest root does not have range type [%s].\n", + IPA_RANGE_AD_TRUST_POSIX); + ret = EINVAL; + goto done; + } + + range.min = r->base_id; + range.max = r->base_id + r->id_range_size -1; + range_id = talloc_asprintf(tmp_ctx, "%s-%s", dom_sid_str, r->name); + if (range_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + err = sss_idmap_add_domain_ex(idmap_ctx->map, dom_name, dom_sid_str, + &range, range_id, 0, true); + if (err != IDMAP_SUCCESS && err != IDMAP_COLLISION) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add range [%s] to ID map\n", range_id); + ret = EIO; + goto done; + } + + found = true; + } + } + + if (!found) { + DEBUG(SSSDBG_MINOR_FAILURE, "No idrange found for forest root [%s].\n", + forest_root->domain_id); + ret = ENOENT; + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t get_idmap_data_from_range(struct range_info *r, char *domain_name, + char **_name, char **_sid, uint32_t *_rid, + struct sss_idmap_range *_range, + bool *_external_mapping) +{ + if (r->range_type == NULL) { + /* Older IPA servers might not have the range_type attribute, but + * only support local ranges and trusts with algorithmic mapping. */ + + if (r->trusted_dom_sid == NULL && r->secondary_base_rid != 0) { + /* local IPA domain */ + *_rid = 0; + *_external_mapping = true; + *_name = domain_name; + *_sid = NULL; + } else if (r->trusted_dom_sid != NULL + && r->secondary_base_rid == 0) { + /* trusted domain */ + *_rid = r->base_rid; + *_external_mapping = false; + *_name = r->trusted_dom_sid; + *_sid = r->trusted_dom_sid; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot determine range type, " \ + "for id range [%s].\n", + r->name); + return EINVAL; + } + } else { + if (strcmp(r->range_type, IPA_RANGE_LOCAL) == 0) { + *_rid = 0; + *_external_mapping = true; + *_name = domain_name; + *_sid = NULL; + } else if (strcmp(r->range_type, IPA_RANGE_AD_TRUST_POSIX) == 0) { + *_rid = 0; + *_external_mapping = true; + *_name = r->trusted_dom_sid; + *_sid = r->trusted_dom_sid; + } else if (strcmp(r->range_type, IPA_RANGE_AD_TRUST) == 0) { + *_rid = r->base_rid; + *_external_mapping = false; + *_name = r->trusted_dom_sid; + *_sid = r->trusted_dom_sid; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Range type [%s] of id range " \ + "[%s] not supported.\n", \ + r->range_type, r->name); + return ERR_UNSUPPORTED_RANGE_TYPE; + } + } + + _range->min = r->base_id; + _range->max = r->base_id + r->id_range_size -1; + + return EOK; +} + +errno_t ipa_ranges_parse_results(TALLOC_CTX *mem_ctx, + char *domain_name, + size_t count, + struct sysdb_attrs **reply, + struct range_info ***_range_list) +{ + struct range_info **range_list = NULL; + struct range_info *r; + const char *value; + size_t c; + size_t rc = 0; + size_t d; + int ret; + enum idmap_error_code err; + char *name1; + char *name2; + char *sid1; + char *sid2; + uint32_t rid1; + uint32_t rid2; + struct sss_idmap_range range1; + struct sss_idmap_range range2; + bool mapping1; + bool mapping2; + + range_list = talloc_array(mem_ctx, struct range_info *, count + 1); + if (range_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + + for (c = 0; c < count; c++) { + r = talloc_zero(range_list, struct range_info); + if (r == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_CN, &value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + r->name = talloc_strdup(r, value); + if (r->name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_TRUSTED_DOMAIN_SID, &value); + if (ret == EOK) { + r->trusted_dom_sid = talloc_strdup(r, value); + if (r->trusted_dom_sid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(reply[c], IPA_BASE_ID, + &r->base_id); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(reply[c], IPA_ID_RANGE_SIZE, + &r->id_range_size); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(reply[c], IPA_BASE_RID, + &r->base_rid); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(reply[c], IPA_SECONDARY_BASE_RID, + &r->secondary_base_rid); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_RANGE_TYPE, &value); + if (ret == EOK) { + r->range_type = talloc_strdup(r, value); + if (r->range_type == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else if (ret == ENOENT) { + /* Older IPA servers might not have the range_type attribute, but + * only support local ranges and trusts with algorithmic mapping. */ + if (r->trusted_dom_sid == NULL) { + r->range_type = talloc_strdup(r, IPA_RANGE_LOCAL); + } else { + r->range_type = talloc_strdup(r, IPA_RANGE_AD_TRUST); + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + if (r->range_type == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_ID_RANGE_MPG, &value); + if (ret == EOK) { + r->mpg_mode = str_to_domain_mpg_mode(value); + } else if (ret == ENOENT) { + r->mpg_mode = MPG_DEFAULT; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = get_idmap_data_from_range(r, domain_name, &name1, &sid1, &rid1, + &range1, &mapping1); + if (ret == ERR_UNSUPPORTED_RANGE_TYPE) { + talloc_free(r); + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_idmap_data_from_range failed.\n"); + goto done; + } + for (d = 0; d < rc; d++) { + ret = get_idmap_data_from_range(range_list[d], domain_name, &name2, + &sid2, &rid2, &range2, &mapping2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_idmap_data_from_range failed.\n"); + goto done; + } + + err = sss_idmap_check_collision_ex(name1, sid1, &range1, rid1, + r->name, mapping1, + name2, sid2, &range2, rid2, + range_list[d]->name, mapping2); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Collision of ranges [%s] and [%s] detected.\n", + r->name, range_list[d]->name); + ret = EINVAL; + goto done; + } + } + + range_list[rc++] = r; + } + + range_list[rc] = NULL; + + *_range_list = range_list; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(range_list); + } + + return ret; +} + +errno_t ipa_idmap_get_ranges_from_sysdb(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str, + bool allow_collisions) +{ + int ret; + size_t range_count; + struct range_info **range_list; + TALLOC_CTX *tmp_ctx; + size_t c; + enum idmap_error_code err; + struct sss_idmap_range range; + uint32_t rid; + bool external_mapping; + char *name; + char *sid; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = sysdb_get_ranges(tmp_ctx, idmap_ctx->id_ctx->be->domain->sysdb, + &range_count, &range_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_ranges failed.\n"); + goto done; + } + + for (c = 0; c < range_count; c++) { + ret = get_idmap_data_from_range(range_list[c], + idmap_ctx->id_ctx->be->domain->name, + &name, &sid, &rid, &range, + &external_mapping); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_idmap_data_from_range failed for " \ + "id range [%s], skipping.\n", + range_list[c]->name); + continue; + } + + err = sss_idmap_add_domain_ex(idmap_ctx->map, name, sid, &range, + range_list[c]->name, rid, + external_mapping); + if (err != IDMAP_SUCCESS) { + if (!allow_collisions || err != IDMAP_COLLISION) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not add range [%s] to ID map\n", + range_list[c]->name); + ret = EIO; + goto done; + } + } + } + + if (dom_name != NULL || dom_sid_str != NULL) { + ret = ipa_idmap_check_posix_child(idmap_ctx, dom_name, dom_sid_str, + range_count, range_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_idmap_check_posix_child failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t ipa_idmap_find_new_domain(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str) +{ + return ipa_idmap_get_ranges_from_sysdb(idmap_ctx, dom_name, dom_sid_str, + true); +} + +errno_t ipa_idmap_init(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_idmap_ctx **_idmap_ctx) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + enum idmap_error_code err; + struct sdap_idmap_ctx *idmap_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + idmap_ctx = talloc_zero(tmp_ctx, struct sdap_idmap_ctx); + if (!idmap_ctx) { + ret = ENOMEM; + goto done; + } + idmap_ctx->id_ctx = id_ctx; + idmap_ctx->find_new_domain = ipa_idmap_find_new_domain; + + /* Initialize the map */ + err = sss_idmap_init(sss_idmap_talloc, idmap_ctx, + sss_idmap_talloc_free, + &idmap_ctx->map); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not initialize the ID map: [%s]\n", + idmap_error_string(err)); + if (err == IDMAP_OUT_OF_MEMORY) { + ret = ENOMEM; + } else { + ret = EINVAL; + } + goto done; + } + + ret = ipa_idmap_get_ranges_from_sysdb(idmap_ctx, NULL, NULL, false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_idmap_get_ranges_from_sysdb failed.\n"); + goto done; + } + + *_idmap_ctx = talloc_steal(mem_ctx, idmap_ctx); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ipa/ipa_init.c b/src/providers/ipa/ipa_init.c new file mode 100644 index 0000000..5ea92ec --- /dev/null +++ b/src/providers/ipa/ipa_init.c @@ -0,0 +1,960 @@ +/* + SSSD + + IPA Provider Initialization functions + + Authors: + Simo Sorce + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include + +#include "util/child_common.h" +#include "providers/ipa/ipa_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_init_shared.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ipa/ipa_auth.h" +#include "providers/ipa/ipa_access.h" +#include "providers/ipa/ipa_dyndns.h" +#include "providers/ipa/ipa_selinux.h" +#include "providers/ldap/sdap_access.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_srv.h" +#include "providers/be_dyndns.h" +#include "providers/ipa/ipa_session.h" + +#define DNS_SRV_MISCONFIGURATION "SRV discovery is enabled on the IPA " \ + "server while using custom dns_discovery_domain. DNS discovery of " \ + "trusted AD domain will likely fail. It is recommended not to use " \ + "SRV discovery or the dns_discovery_domain option for the IPA " \ + "domain while running on the server itself\n" + +#define PREAUTH_INDICATOR_ERROR "Failed to create preauth indicator file, " \ + "special password prompting might not be available.\n" + +struct ipa_init_ctx { + struct ipa_options *options; + struct ipa_id_ctx *id_ctx; + struct ipa_auth_ctx *auth_ctx; +}; + + +struct krb5_ctx *ipa_init_get_krb5_auth_ctx(void *data) +{ + struct ipa_init_ctx *ipa_init_ctx; + + ipa_init_ctx = talloc_get_type(data, struct ipa_init_ctx); + if (ipa_init_ctx == NULL || ipa_init_ctx->auth_ctx == NULL) { + return NULL; + } + + return ipa_init_ctx->auth_ctx->krb5_auth_ctx; +} + +static bool srv_in_server_list(const char *servers) +{ + TALLOC_CTX *tmp_ctx; + char **list = NULL; + int ret = 0; + bool has_srv = false; + + if (servers == NULL) return true; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return false; + } + + /* split server parm into a list */ + ret = split_on_separator(tmp_ctx, servers, ',', true, true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse server list!\n"); + goto done; + } + + for (int i = 0; list[i]; i++) { + has_srv = be_fo_is_srv_identifier(list[i]); + if (has_srv == true) { + break; + } + } + +done: + talloc_free(tmp_ctx); + return has_srv; +} + +static errno_t ipa_init_options(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_options **_ipa_options) +{ + struct ipa_options *ipa_options; + const char *ipa_servers; + const char *ipa_backup_servers; + errno_t ret; + + ret = ipa_get_options(mem_ctx, be_ctx->cdb, be_ctx->conf_path, + be_ctx->domain, &ipa_options); + if (ret != EOK) { + return ret; + } + + ipa_servers = dp_opt_get_string(ipa_options->basic, IPA_SERVER); + ipa_backup_servers = dp_opt_get_string(ipa_options->basic, IPA_BACKUP_SERVER); + + ret = ipa_service_init(ipa_options, be_ctx, ipa_servers, + ipa_backup_servers, ipa_options, + &ipa_options->service); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to init IPA service [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(ipa_options); + return ret; + } + + *_ipa_options = ipa_options; + return EOK; +} + +static errno_t ipa_init_id_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx **_ipa_id_ctx) +{ + struct ipa_id_ctx *ipa_id_ctx = NULL; + struct sdap_id_ctx *sdap_id_ctx = NULL; + errno_t ret; + + ipa_id_ctx = talloc_zero(mem_ctx, struct ipa_id_ctx); + if (ipa_id_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + sdap_id_ctx = sdap_id_ctx_new(mem_ctx, be_ctx, ipa_options->service->sdap); + if (sdap_id_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ipa_id_ctx->ipa_options = ipa_options; + ipa_id_ctx->sdap_id_ctx = sdap_id_ctx; + ipa_options->id_ctx = ipa_id_ctx; + + ret = ipa_get_id_options(ipa_options, + be_ctx->cdb, + be_ctx->conf_path, + be_ctx->provider, + &sdap_id_ctx->opts); + if (ret != EOK) { + goto done; + } + + *_ipa_id_ctx = ipa_id_ctx; + + ret = EOK; + +done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init id context [%d]: %s\n", + ret, sss_strerror(ret)); + + talloc_free(ipa_id_ctx); + talloc_free(sdap_id_ctx); + } + + return ret; +} + + +static errno_t ipa_init_dyndns(struct be_ctx *be_ctx, + struct ipa_options *ipa_options) +{ + bool enabled; + errno_t ret; + + ret = ipa_get_dyndns_options(be_ctx, ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get dyndns options [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + enabled = dp_opt_get_bool(ipa_options->dyndns_ctx->opts, + DP_OPT_DYNDNS_UPDATE); + if (!enabled) { + DEBUG(SSSDBG_CONF_SETTINGS, "Dynamic DNS updates are off.\n"); + return EOK; + } + + /* Perform automatic DNS updates when the IP address changes. + * Register a callback for successful LDAP reconnections. + * This is the easiest way to identify that we have gone online. + */ + + DEBUG(SSSDBG_CONF_SETTINGS, + "Dynamic DNS updates are on. Checking for nsupdate...\n"); + + ret = be_nsupdate_check(); + if (ret != EOK) { + DEBUG(SSSDBG_CONF_SETTINGS, "nsupdate is not availabe, " + "dynamic DNS updates will not work\n"); + return EOK; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "nsupdate is available\n"); + + ret = ipa_dyndns_init(be_ctx, ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failure setting up automatic DNS update\n"); + /* We will continue without DNS updating */ + } + + return EOK; +} + +static errno_t ipa_init_server_mode(struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx *ipa_id_ctx) +{ + const char *ipa_servers; + const char *dnsdomain; + const char *hostname; + bool sites_enabled; + errno_t ret; + + ipa_id_ctx->view_name = talloc_strdup(ipa_id_ctx, SYSDB_DEFAULT_VIEW_NAME); + if (ipa_id_ctx->view_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup() failed.\n"); + return ENOMEM; + } + + ret = sysdb_update_view_name(be_ctx->domain->sysdb, ipa_id_ctx->view_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add/update view name to sysdb.\n"); + return ret; + } + + hostname = dp_opt_get_string(ipa_options->basic, IPA_HOSTNAME); + ipa_servers = dp_opt_get_string(ipa_options->basic, IPA_SERVER); + sites_enabled = dp_opt_get_bool(ipa_options->basic, IPA_ENABLE_DNS_SITES); + dnsdomain = dp_opt_get_string(be_ctx->be_res->opts, DP_RES_OPT_DNS_DOMAIN); + + if (srv_in_server_list(ipa_servers) || sites_enabled) { + DEBUG(SSSDBG_IMPORTANT_INFO, "SSSD configuration uses either DNS " + "SRV resolution or IPA site discovery to locate IPA servers. " + "On IPA server itself, it is recommended that SSSD is " + "configured to only connect to the IPA server it's running at. "); + + /* If SRV discovery is enabled on the server and + * dns_discovery_domain is set explicitly, then + * the current failover code would use the dns_discovery + * domain to try to find AD servers and fail. + */ + if (dnsdomain != NULL) { + sss_log(SSS_LOG_ERR, DNS_SRV_MISCONFIGURATION); + DEBUG(SSSDBG_CRIT_FAILURE, DNS_SRV_MISCONFIGURATION); + } + + ret = be_fo_set_dns_srv_lookup_plugin(be_ctx, hostname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set SRV lookup plugin " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + return EOK; + } else { + /* In server mode we need to ignore the dns_discovery_domain if set + * and only discover servers based on AD domains. */ + ret = dp_opt_set_string(be_ctx->be_res->opts, DP_RES_OPT_DNS_DOMAIN, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not reset the " + "dns_discovery_domain, trusted AD domains discovery " + "might fail. Please remove dns_discovery_domain " + "from the config file and restart the SSSD\n"); + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "The value of dns_discovery_domain " + "will be ignored in ipa_server_mode\n"); + } + } + + return EOK; +} + +static errno_t ipa_init_client_mode(struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx *ipa_id_ctx) +{ + struct ipa_srv_plugin_ctx *srv_ctx; + const char *ipa_domain; + const char *hostname; + bool sites_enabled; + errno_t ret; + + ret = sysdb_get_view_name(ipa_id_ctx, be_ctx->domain->sysdb, + &ipa_id_ctx->view_name); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot find view name in the cache. " + "Will do online lookup later.\n"); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_get_view_name() failed [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + hostname = dp_opt_get_string(ipa_options->basic, IPA_HOSTNAME); + sites_enabled = dp_opt_get_bool(ipa_options->basic, IPA_ENABLE_DNS_SITES); + + if (sites_enabled) { + /* use IPA plugin */ + ipa_domain = dp_opt_get_string(ipa_options->basic, IPA_DOMAIN); + srv_ctx = ipa_srv_plugin_ctx_init(be_ctx, be_ctx->be_res->resolv, + hostname, ipa_domain); + if (srv_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n"); + return ENOMEM; + } + + be_fo_set_srv_lookup_plugin(be_ctx, ipa_srv_plugin_send, + ipa_srv_plugin_recv, srv_ctx, "IPA"); + } else { + /* fall back to standard plugin on clients. */ + ret = be_fo_set_dns_srv_lookup_plugin(be_ctx, hostname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set SRV lookup plugin " + "[%d]: %s\n", ret, strerror(ret)); + return ret; + } + } + + return EOK; +} + +static errno_t ipa_init_ipa_auth_ctx(TALLOC_CTX *mem_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx *ipa_id_ctx, + struct ipa_auth_ctx **_ipa_auth_ctx) +{ + struct ipa_auth_ctx *ipa_auth_ctx; + errno_t ret; + + ipa_auth_ctx = talloc_zero(mem_ctx, struct ipa_auth_ctx); + if (ipa_auth_ctx == NULL) { + return ENOMEM; + } + + ipa_auth_ctx->sdap_id_ctx = ipa_id_ctx->sdap_id_ctx; + + ret = dp_copy_options(ipa_auth_ctx, ipa_options->basic, + IPA_OPTS_BASIC, &ipa_auth_ctx->ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_copy_options failed.\n"); + talloc_free(ipa_auth_ctx); + return ret; + } + + *_ipa_auth_ctx = ipa_auth_ctx; + + return EOK; +} + +static errno_t ipa_init_krb5_auth_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct krb5_ctx **_krb5_auth_ctx) +{ + struct krb5_ctx *krb5_auth_ctx; + bool server_mode; + errno_t ret; + + krb5_auth_ctx = talloc_zero(mem_ctx, struct krb5_ctx); + if (krb5_auth_ctx == NULL) { + return ENOMEM; + } + + krb5_auth_ctx->service = ipa_options->service->krb5_service; + + server_mode = dp_opt_get_bool(ipa_options->basic, IPA_SERVER_MODE); + krb5_auth_ctx->config_type = server_mode ? K5C_IPA_SERVER : K5C_IPA_CLIENT; + + ret = ipa_get_auth_options(ipa_options, be_ctx->cdb, be_ctx->conf_path, + &krb5_auth_ctx->opts); + if (ret != EOK) { + talloc_free(krb5_auth_ctx); + return ret; + } + + *_krb5_auth_ctx = krb5_auth_ctx; + return EOK; +} + +static errno_t ipa_init_sdap_auth_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct sdap_auth_ctx **_sdap_auth_ctx) +{ + struct sdap_auth_ctx *sdap_auth_ctx; + + sdap_auth_ctx = talloc_zero(mem_ctx, struct sdap_auth_ctx); + if (sdap_auth_ctx == NULL) { + return ENOMEM; + } + + sdap_auth_ctx->be = be_ctx; + sdap_auth_ctx->service = ipa_options->service->sdap; + + if (ipa_options->id == NULL) { + talloc_free(sdap_auth_ctx); + return EINVAL; + } + + sdap_auth_ctx->opts = ipa_options->id; + + *_sdap_auth_ctx = sdap_auth_ctx; + + return EOK; +} + +static struct sdap_ext_member_ctx * +ipa_create_ext_members_ctx(TALLOC_CTX *mem_ctx, + struct ipa_id_ctx *id_ctx) +{ + struct sdap_ext_member_ctx *ext_ctx = NULL; + + ext_ctx = talloc_zero(mem_ctx, struct sdap_ext_member_ctx); + if (ext_ctx == NULL) { + return NULL; + } + + ext_ctx->pvt = id_ctx; + ext_ctx->ext_member_resolve_send = ipa_ext_group_member_send; + ext_ctx->ext_member_resolve_recv = ipa_ext_group_member_recv; + + return ext_ctx; +} + +static errno_t ipa_init_auth_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx *id_ctx, + struct ipa_auth_ctx **_auth_ctx) +{ + struct sdap_auth_ctx *sdap_auth_ctx; + struct ipa_auth_ctx *ipa_auth_ctx; + struct krb5_ctx *krb5_auth_ctx; + errno_t ret; + + ret = ipa_init_ipa_auth_ctx(mem_ctx, ipa_options, id_ctx, &ipa_auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init IPA auth context\n"); + return ret; + } + + ipa_options->auth_ctx = ipa_auth_ctx; + + ret = ipa_init_krb5_auth_ctx(ipa_auth_ctx, be_ctx, ipa_options, + &krb5_auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init KRB5 auth context\n"); + goto done; + } + ipa_options->auth_ctx->krb5_auth_ctx = krb5_auth_ctx; + + ret = ipa_init_sdap_auth_ctx(ipa_auth_ctx, be_ctx, ipa_options, + &sdap_auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init SDAP auth context\n"); + goto done; + } + ipa_options->auth_ctx->sdap_auth_ctx = sdap_auth_ctx; + + setup_ldap_debug(sdap_auth_ctx->opts->basic); + + ret = setup_tls_config(sdap_auth_ctx->opts->basic); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "setup_tls_config failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Initialize features needed by the krb5_child */ + ret = krb5_child_init(krb5_auth_ctx, be_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not initialize krb5_child " + "settings [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = create_preauth_indicator(); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, PREAUTH_INDICATOR_ERROR); + sss_log(SSSDBG_CRIT_FAILURE, PREAUTH_INDICATOR_ERROR); + } + + *_auth_ctx = ipa_auth_ctx; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(ipa_auth_ctx); + } + + return ret; +} + +static bool ipa_check_fqdn(const char *str) +{ + return strchr(str, '.'); +} + +static errno_t ipa_init_misc(struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx *ipa_id_ctx, + struct sdap_id_ctx *sdap_id_ctx) +{ + errno_t ret; + + if (!ipa_check_fqdn(dp_opt_get_string(ipa_options->basic, + IPA_HOSTNAME))) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_hostname is not Fully Qualified Domain Name.\n"); + } + + ret = ipa_init_dyndns(be_ctx, ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init dyndns [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + setup_ldap_debug(sdap_id_ctx->opts->basic); + + ret = setup_tls_config(sdap_id_ctx->opts->basic); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get TLS options [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = ipa_idmap_init(sdap_id_ctx, sdap_id_ctx, + &sdap_id_ctx->opts->idmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not initialize ID mapping. In case ID mapping properties " + "changed on the server, please remove the SSSD database\n"); + return ret; + } + + ret = ldap_id_setup_tasks(sdap_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup background tasks " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + if (dp_opt_get_bool(ipa_options->basic, IPA_SERVER_MODE)) { + ret = ipa_init_server_mode(be_ctx, ipa_options, ipa_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init server mode " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } else { + ret = ipa_init_client_mode(be_ctx, ipa_options, ipa_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init client mode " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } + + ret = ipa_refresh_init(be_ctx, ipa_id_ctx); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh " + "will not work [%d]: %s\n", ret, sss_strerror(ret)); + } + + ipa_id_ctx->sdap_id_ctx->opts->ext_ctx = ipa_create_ext_members_ctx( + ipa_id_ctx->sdap_id_ctx->opts, ipa_id_ctx); + if (ipa_id_ctx->sdap_id_ctx->opts->ext_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set the extrernal group ctx\n"); + return ENOMEM; + } + + ret = sdap_init_certmap(sdap_id_ctx, sdap_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialized certificate mapping.\n"); + return ret; + } + + /* We must ignore entries in the views search base + * (default: cn=views,cn=accounts,$BASEDN) */ + sdap_id_ctx->opts->sdom->ignore_user_search_bases = \ + ipa_id_ctx->ipa_options->views_search_bases; + + return EOK; +} + +errno_t sssm_ipa_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data) +{ + struct ipa_init_ctx *init_ctx; + errno_t ret; + + init_ctx = talloc_zero(mem_ctx, struct ipa_init_ctx); + if (init_ctx == NULL) { + return ENOMEM; + } + + /* Always initialize options since it is needed everywhere. */ + ret = ipa_init_options(init_ctx, be_ctx, &init_ctx->options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init IPA options " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Always initialize id_ctx since it is needed everywhere. */ + ret = ipa_init_id_ctx(init_ctx, be_ctx, init_ctx->options, + &init_ctx->id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init IPA ID context " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Setup miscellaneous things. */ + ret = ipa_init_misc(be_ctx, init_ctx->options, init_ctx->id_ctx, + init_ctx->id_ctx->sdap_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init IPA module " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Initialize auth_ctx only if one of the target is enabled. */ + if (dp_target_enabled(provider, module_name, DPT_AUTH, DPT_CHPASS)) { + ret = ipa_init_auth_ctx(init_ctx, be_ctx, init_ctx->options, + init_ctx->id_ctx, &init_ctx->auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init IPA auth context " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } + + *_module_data = init_ctx; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(init_ctx); + } + + return ret; +} + +errno_t sssm_ipa_id_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ipa_init_ctx *init_ctx; + struct ipa_id_ctx *id_ctx; + + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + id_ctx = init_ctx->id_ctx; + + dp_set_method(dp_methods, DPM_ACCOUNT_HANDLER, + ipa_account_info_handler_send, ipa_account_info_handler_recv, id_ctx, + struct ipa_id_ctx, struct dp_id_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_CHECK_ONLINE, + sdap_online_check_handler_send, sdap_online_check_handler_recv, id_ctx->sdap_id_ctx, + struct sdap_id_ctx, void, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_ACCT_DOMAIN_HANDLER, + default_account_domain_send, default_account_domain_recv, NULL, + void, struct dp_get_acct_domain_data, struct dp_reply_std); + + return EOK; +} + +errno_t sssm_ipa_auth_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ipa_init_ctx *init_ctx; + struct ipa_auth_ctx *auth_ctx; + + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + auth_ctx = init_ctx->auth_ctx; + + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + ipa_pam_auth_handler_send, ipa_pam_auth_handler_recv, auth_ctx, + struct ipa_auth_ctx, struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_ipa_chpass_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + return sssm_ipa_auth_init(mem_ctx, be_ctx, module_data, dp_methods); +} + +errno_t sssm_ipa_access_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ipa_access_ctx *access_ctx; + struct ipa_init_ctx *init_ctx; + struct ipa_id_ctx *id_ctx; + errno_t ret; + + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + id_ctx = init_ctx->id_ctx; + + access_ctx = talloc_zero(mem_ctx, struct ipa_access_ctx); + if (access_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed.\n"); + return ENOMEM; + } + + access_ctx->sdap_ctx = id_ctx->sdap_id_ctx; + access_ctx->host_map = id_ctx->ipa_options->id->host_map; + access_ctx->hostgroup_map = id_ctx->ipa_options->hostgroup_map; + access_ctx->host_search_bases = id_ctx->ipa_options->id->sdom->host_search_bases; + access_ctx->hbac_search_bases = id_ctx->ipa_options->hbac_search_bases; + + ret = dp_copy_options(access_ctx, id_ctx->ipa_options->basic, + IPA_OPTS_BASIC, &access_ctx->ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_copy_options() failed.\n"); + goto done; + } + + /* Set up an sdap_access_ctx for checking as configured */ + access_ctx->sdap_access_ctx = talloc_zero(access_ctx, struct sdap_access_ctx); + if (access_ctx->sdap_access_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n"); + ret = ENOMEM; + goto done; + } + + access_ctx->sdap_access_ctx->type = SDAP_TYPE_IPA; + access_ctx->sdap_access_ctx->id_ctx = access_ctx->sdap_ctx; + ret = sdap_set_access_rules(access_ctx, access_ctx->sdap_access_ctx, + access_ctx->ipa_options, + id_ctx->ipa_options->id->basic); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_set_access_rules failed: [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + dp_set_method(dp_methods, DPM_ACCESS_HANDLER, + ipa_pam_access_handler_send, ipa_pam_access_handler_recv, access_ctx, + struct ipa_access_ctx, struct pam_data, struct pam_data *); + + dp_set_method(dp_methods, DPM_REFRESH_ACCESS_RULES, + ipa_refresh_access_rules_send, ipa_refresh_access_rules_recv, access_ctx, + struct ipa_access_ctx, void, void *); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(access_ctx); + } + + return ret; +} + +errno_t sssm_ipa_selinux_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#if defined HAVE_SELINUX + struct ipa_selinux_ctx *selinux_ctx; + struct ipa_init_ctx *init_ctx; + struct ipa_options *opts; + + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + opts = init_ctx->options; + + selinux_ctx = talloc_zero(mem_ctx, struct ipa_selinux_ctx); + if (selinux_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed.\n"); + return ENOMEM; + } + + selinux_ctx->id_ctx = init_ctx->id_ctx; + selinux_ctx->hbac_search_bases = opts->hbac_search_bases; + selinux_ctx->host_search_bases = opts->id->sdom->host_search_bases; + selinux_ctx->selinux_search_bases = opts->selinux_search_bases; + + dp_set_method(dp_methods, DPM_SELINUX_HANDLER, + ipa_selinux_handler_send, ipa_selinux_handler_recv, selinux_ctx, + struct ipa_selinux_ctx, struct pam_data, struct pam_data *); + + return EOK; +#else + DEBUG(SSSDBG_MINOR_FAILURE, "SELinux init handler called but SSSD is " + "built without SELinux support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ipa_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_SSH + struct ipa_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing IPA host handler\n"); + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + + return ipa_hostid_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); + +#else + DEBUG(SSSDBG_MINOR_FAILURE, "HostID init handler called but SSSD is " + "built without SSH support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ipa_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_AUTOFS + struct ipa_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing IPA autofs handler\n"); + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + + return ipa_autofs_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); +#else + DEBUG(SSSDBG_MINOR_FAILURE, "Autofs init handler called but SSSD is " + "built without autofs support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ipa_subdomains_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ipa_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing IPA subdomains handler\n"); + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + + return ipa_subdomains_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); +} + +errno_t sssm_ipa_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_SUDO + struct ipa_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing IPA sudo handler\n"); + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + + return ipa_sudo_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); +#else + DEBUG(SSSDBG_MINOR_FAILURE, "Sudo init handler called but SSSD is " + "built without sudo support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ipa_session_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ipa_session_ctx *session_ctx; + struct ipa_init_ctx *init_ctx; + struct ipa_id_ctx *id_ctx; + errno_t ret; + + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + id_ctx = init_ctx->id_ctx; + + session_ctx = talloc_zero(mem_ctx, struct ipa_session_ctx); + if (session_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed.\n"); + + return ENOMEM; + } + + session_ctx->sdap_ctx = id_ctx->sdap_id_ctx; + session_ctx->host_map = id_ctx->ipa_options->id->host_map; + session_ctx->hostgroup_map = id_ctx->ipa_options->hostgroup_map; + session_ctx->host_search_bases = id_ctx->ipa_options->id->sdom->host_search_bases; + session_ctx->deskprofile_search_bases = id_ctx->ipa_options->deskprofile_search_bases; + + ret = dp_copy_options(session_ctx, id_ctx->ipa_options->basic, + IPA_OPTS_BASIC, &session_ctx->ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_copy_options() failed.\n"); + + goto done; + } + + dp_set_method(dp_methods, DPM_SESSION_HANDLER, + ipa_pam_session_handler_send, ipa_pam_session_handler_recv, session_ctx, + struct ipa_session_ctx, struct pam_data, struct pam_data *); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(session_ctx); + } + + return ret; +} diff --git a/src/providers/ipa/ipa_netgroups.c b/src/providers/ipa/ipa_netgroups.c new file mode 100644 index 0000000..57f11a5 --- /dev/null +++ b/src/providers/ipa/ipa_netgroups.c @@ -0,0 +1,1056 @@ +/* + SSSD + + Async IPA Helper routines for netgroups + + Authors: + Jan Zeleny + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ipa/ipa_id.h" +#include + +#define ENTITY_NG 1 +#define ENTITY_USER 2 +#define ENTITY_HOST 4 + +struct ipa_get_netgroups_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct ipa_options *ipa_opts; + struct sdap_handle *sh; + struct sysdb_ctx *sysdb; + struct sss_domain_info *dom; + const char **attrs; + int timeout; + + char *filter; + const char *base_filter; + + size_t netgr_base_iter; + size_t host_base_iter; + size_t user_base_iter; + + /* Entities which have been already asked for + * and are scheduled for inspection */ + hash_table_t *new_netgroups; + hash_table_t *new_users; + hash_table_t *new_hosts; + + int current_entity; + int entities_found; + + struct sysdb_attrs **netgroups; + int netgroups_count; +}; + +static errno_t ipa_save_netgroup(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs *attrs) +{ + struct ldb_message_element *el; + struct sysdb_attrs *netgroup_attrs; + const char *name = NULL; + char **missing; + int missing_index; + int ret; + int i; + size_t c; + + ret = sysdb_attrs_get_el(attrs, + opts->netgroup_map[IPA_AT_NETGROUP_NAME].sys_name, + &el); + if (ret) goto fail; + if (el->num_values == 0) { + ret = EINVAL; + goto fail; + } + name = (const char *)el->values[0].data; + DEBUG(SSSDBG_TRACE_INTERNAL, "Storing netgroup %s\n", name); + + netgroup_attrs = sysdb_new_attrs(mem_ctx); + if (!netgroup_attrs) { + ret = ENOMEM; + goto fail; + } + + missing = talloc_zero_array(netgroup_attrs, char *, attrs->num + 1); + if (missing == NULL) { + ret = ENOMEM; + goto fail; + } + + for (i = 0, missing_index = 0; i < attrs->num; i++) { + if (attrs->a[i].num_values == 0) { + missing[missing_index] = talloc_strdup(missing, attrs->a[i].name); + if (missing[missing_index] == NULL) { + ret = ENOMEM; + goto fail; + } + missing_index++; + } + } + + ret = sysdb_attrs_get_el(attrs, SYSDB_ORIG_DN, &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Original DN is not available for [%s].\n", name); + } else { + DEBUG(SSSDBG_TRACE_LIBS, + "Adding original DN [%s] to attributes of [%s].\n", + el->values[0].data, name); + ret = sysdb_attrs_add_string(netgroup_attrs, SYSDB_ORIG_DN, + (const char *)el->values[0].data); + if (ret) { + goto fail; + } + } + + ret = sysdb_attrs_get_el(attrs, SYSDB_NETGROUP_TRIPLE, &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, "No netgroup triples for netgroup [%s].\n", name); + ret = sysdb_attrs_get_el(netgroup_attrs, SYSDB_NETGROUP_TRIPLE, &el); + if (ret != EOK) { + goto fail; + } + } else { + for(c = 0; c < el->num_values; c++) { + ret = sysdb_attrs_add_string_safe(netgroup_attrs, + SYSDB_NETGROUP_TRIPLE, + (const char*)el->values[c].data); + if (ret) { + goto fail; + } + } + } + + ret = sysdb_attrs_get_el(attrs, + opts->netgroup_map[IPA_AT_NETGROUP_MEMBER].sys_name, + &el); + if (ret != EOK) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "No original members for netgroup [%s]\n", name); + } else { + DEBUG(SSSDBG_TRACE_LIBS, + "Adding original members to netgroup [%s]\n", name); + for(c = 0; c < el->num_values; c++) { + ret = sysdb_attrs_add_string(netgroup_attrs, + opts->netgroup_map[IPA_AT_NETGROUP_MEMBER].sys_name, + (const char*)el->values[c].data); + if (ret) { + goto fail; + } + } + } + + + ret = sysdb_attrs_get_el(attrs, SYSDB_NETGROUP_MEMBER, &el); + if (ret != EOK) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, "No members for netgroup [%s]\n", name); + + } else { + DEBUG(SSSDBG_TRACE_LIBS, "Adding members to netgroup [%s]\n", name); + for(c = 0; c < el->num_values; c++) { + ret = sysdb_attrs_add_string(netgroup_attrs, SYSDB_NETGROUP_MEMBER, + (const char*)el->values[c].data); + if (ret) { + goto fail; + } + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Storing info for netgroup %s\n", name); + + ret = sysdb_add_netgroup(dom, name, NULL, netgroup_attrs, missing, + dom->netgroup_timeout, 0); + if (ret) goto fail; + + return EOK; + +fail: + DEBUG(SSSDBG_OP_FAILURE, "Failed to save netgroup %s\n", name); + return ret; +} + +static errno_t ipa_netgr_next_base(struct tevent_req *req); +static void ipa_get_netgroups_process(struct tevent_req *subreq); +static int ipa_netgr_process_all(struct ipa_get_netgroups_state *state); + +struct tevent_req *ipa_get_netgroups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct ipa_options *ipa_options, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout) +{ + struct tevent_req *req; + struct ipa_get_netgroups_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct ipa_get_netgroups_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->ipa_opts = ipa_options; + state->sh = sh; + state->sysdb = sysdb; + state->attrs = attrs; + state->timeout = timeout; + state->base_filter = filter; + state->netgr_base_iter = 0; + state->dom = dom; + + if (!ipa_options->id->sdom->netgroup_search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Netgroup lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + ret = sss_hash_create(state, 0, &state->new_netgroups); + if (ret != EOK) goto done; + ret = sss_hash_create(state, 0, &state->new_users); + if (ret != EOK) goto done; + ret = sss_hash_create(state, 0, &state->new_hosts); + if (ret != EOK) goto done; + + + ret = ipa_netgr_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t ipa_netgr_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct ipa_get_netgroups_state *state; + struct sdap_search_base **netgr_bases; + + state = tevent_req_data(req, struct ipa_get_netgroups_state); + netgr_bases = state->ipa_opts->id->sdom->netgroup_search_bases; + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters( + state, + state->base_filter, + netgr_bases[state->netgr_base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for netgroups with base [%s]\n", + netgr_bases[state->netgr_base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + netgr_bases[state->netgr_base_iter]->basedn, + netgr_bases[state->netgr_base_iter]->scope, + state->filter, state->attrs, + state->opts->netgroup_map, IPA_OPTS_NETGROUP, + state->timeout, + true); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_get_netgroups_process, req); + + return EOK; +} + +static int ipa_netgr_fetch_netgroups(struct ipa_get_netgroups_state *state, + struct tevent_req *req); +static int ipa_netgr_fetch_users(struct ipa_get_netgroups_state *state, + struct tevent_req *req); +static int ipa_netgr_fetch_hosts(struct ipa_get_netgroups_state *state, + struct tevent_req *req); +static void ipa_netgr_members_process(struct tevent_req *subreq); + +static void ipa_get_netgroups_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_netgroups_state *state = tevent_req_data(req, + struct ipa_get_netgroups_state); + int i, ret; + struct ldb_message_element *el; + struct sdap_search_base **netgr_bases; + struct sysdb_attrs **netgroups; + size_t netgroups_count; + const char *orig_dn; + char *dn; + char *filter; + bool fetch_members = false; + hash_key_t key; + hash_value_t value; + + netgr_bases = state->ipa_opts->id->sdom->netgroup_search_bases; + + ret = sdap_get_generic_recv(subreq, state, &netgroups_count, &netgroups); + talloc_zfree(subreq); + if (ret) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Search for netgroups, returned %zu results.\n", + netgroups_count); + + if (netgroups_count == 0) { + /* No netgroups found in this search */ + state->netgr_base_iter++; + if (netgr_bases[state->netgr_base_iter]) { + /* There are more search bases to try */ + ret = ipa_netgr_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ENOENT); + } + return; + } + + ret = ENOENT; + goto done; + } + + filter = talloc_strdup(state, "(|"); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < netgroups_count; i++) { + ret = sysdb_attrs_get_el(netgroups[i], SYSDB_ORIG_NETGROUP_MEMBER, + &el); + if (ret != EOK) goto done; + if (el->num_values) state->entities_found |= ENTITY_NG; + + ret = sysdb_attrs_get_el(netgroups[i], SYSDB_ORIG_MEMBER_USER, + &el); + if (ret != EOK) goto done; + if (el->num_values) state->entities_found |= ENTITY_USER; + + ret = sysdb_attrs_get_el(netgroups[i], SYSDB_ORIG_MEMBER_HOST, + &el); + if (ret != EOK) goto done; + if (el->num_values) state->entities_found |= ENTITY_HOST; + + ret = sysdb_attrs_get_string(netgroups[i], SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + goto done; + } + + key.type = HASH_KEY_STRING; + value.type = HASH_VALUE_PTR; + key.str = discard_const(orig_dn); + value.ptr = netgroups[i]; + ret = hash_enter(state->new_netgroups, &key, &value); + if (ret != HASH_SUCCESS) { + ret = ENOMEM; + goto done; + } + + if (state->entities_found == 0) { + continue; + } + + ret = sss_filter_sanitize_dn(state, orig_dn, &dn); + if (ret != EOK) { + goto done; + } + /* Add this to the filter */ + filter = talloc_asprintf_append(filter, "(%s=%s)", + state->opts->netgroup_map[IPA_AT_NETGROUP_MEMBER_OF].name, + dn); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + fetch_members = true; + } + + if (!fetch_members) { + ret = ipa_netgr_process_all(state); + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + return; + } + + state->filter = talloc_asprintf_append(filter, ")"); + if (state->filter == NULL) { + ret = ENOMEM; + goto done; + } + + if (state->entities_found & ENTITY_NG) { + state->netgr_base_iter = 0; + ret = ipa_netgr_fetch_netgroups(state, req); + if (ret != EOK) goto done; + } else if (state->entities_found & ENTITY_USER) { + ret = ipa_netgr_fetch_users(state, req); + if (ret != EOK) goto done; + } else if (state->entities_found & ENTITY_HOST) { + ret = ipa_netgr_fetch_hosts(state, req); + if (ret != EOK) goto done; + } + + return; +done: + tevent_req_error(req, ret); + return; +} + +static int ipa_netgr_fetch_netgroups(struct ipa_get_netgroups_state *state, + struct tevent_req *req) +{ + char *filter; + const char *base_filter; + struct tevent_req *subreq; + struct sdap_search_base **bases; + + bases = state->ipa_opts->id->sdom->netgroup_search_bases; + if (bases[state->netgr_base_iter] == NULL) { + /* No more bases to try */ + return ENOENT; + } + base_filter = bases[state->netgr_base_iter]->filter; + + filter = talloc_asprintf(state, "(&%s%s(objectclass=%s))", + state->filter, + base_filter?base_filter:"", + state->opts->netgroup_map[SDAP_OC_NETGROUP].name); + if (filter == NULL) + return ENOMEM; + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + bases[state->netgr_base_iter]->basedn, + bases[state->netgr_base_iter]->scope, + filter, state->attrs, state->opts->netgroup_map, + IPA_OPTS_NETGROUP, state->timeout, true); + + state->current_entity = ENTITY_NG; + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_netgr_members_process, req); + + return EOK; +} + +static int ipa_netgr_fetch_users(struct ipa_get_netgroups_state *state, + struct tevent_req *req) +{ + const char *attrs[] = { state->opts->user_map[SDAP_AT_USER_NAME].name, + state->opts->user_map[SDAP_AT_USER_MEMBEROF].name, + "objectclass", NULL }; + char *filter; + const char *base_filter; + struct tevent_req *subreq; + struct sdap_search_base **bases; + + bases = state->ipa_opts->id->sdom->user_search_bases; + if (bases[state->user_base_iter] == NULL) { + return ENOENT; + } + base_filter = bases[state->user_base_iter]->filter; + + filter = talloc_asprintf(state, "(&%s%s(objectclass=%s))", + state->filter, + base_filter?base_filter:"", + state->opts->user_map[SDAP_OC_USER].name); + if (filter == NULL) + return ENOMEM; + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + dp_opt_get_string(state->opts->basic, + SDAP_USER_SEARCH_BASE), + LDAP_SCOPE_SUBTREE, + filter, attrs, state->opts->user_map, + state->opts->user_map_cnt, + state->timeout, true); + + state->current_entity = ENTITY_USER; + if (subreq == NULL) { + talloc_free(attrs); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_netgr_members_process, req); + + return EOK; +} + +static int ipa_netgr_fetch_hosts(struct ipa_get_netgroups_state *state, + struct tevent_req *req) +{ + const char **attrs; + char *filter; + const char *base_filter; + struct tevent_req *subreq; + int ret; + struct sdap_search_base **bases; + + bases = state->ipa_opts->id->sdom->host_search_bases; + if (bases[state->host_base_iter] == NULL) { + return ENOENT; + } + base_filter = bases[state->host_base_iter]->filter; + + filter = talloc_asprintf(state, "(&%s%s(objectclass=%s))", + state->filter, + base_filter ? base_filter : "", + state->ipa_opts->id->host_map[SDAP_OC_HOST].name); + if (filter == NULL) + return ENOMEM; + + ret = build_attrs_from_map(state, state->ipa_opts->id->host_map, + SDAP_OPTS_HOST, NULL, &attrs, NULL); + if (ret != EOK) { + talloc_free(filter); + return ret; + } + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + bases[state->host_base_iter]->basedn, + bases[state->host_base_iter]->scope, + filter, attrs, state->ipa_opts->id->host_map, + SDAP_OPTS_HOST, state->timeout, true); + + state->current_entity = ENTITY_HOST; + if (subreq == NULL) { + talloc_free(filter); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_netgr_members_process, req); + + return EOK; +} + +static void ipa_netgr_members_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_netgroups_state *state = tevent_req_data(req, + struct ipa_get_netgroups_state); + struct sysdb_attrs **entities; + size_t count; + int ret, i; + const char *orig_dn; + hash_table_t *table; + hash_key_t key; + hash_value_t value; + int (* next_call)(struct ipa_get_netgroups_state *, + struct tevent_req *); + bool next_batch_scheduled = false; + + ret = sdap_get_generic_recv(subreq, state, &count, &entities); + talloc_zfree(subreq); + if (ret) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Found %zu members in current search base\n", + count); + + next_call = NULL; + /* While processing a batch of entities from one search base, + * schedule query for another search base if there is one + * + * If there is no other search base, another class of entities + * will be scheduled for lookup after processing of current + * batch. The order of lookup is: netgroups -> users -> hosts + */ + if (state->current_entity == ENTITY_NG) { + /* We just received a batch of netgroups */ + state->netgr_base_iter++; + ret = ipa_netgr_fetch_netgroups(state, req); + table = state->new_netgroups; + /* If there is a member netgroup, we always have to + * ask for both member users and hosts + * -> now schedule users + */ + next_call = ipa_netgr_fetch_users; + } else if (state->current_entity == ENTITY_USER) { + /* We just received a batch of users */ + state->user_base_iter++; + ret = ipa_netgr_fetch_users(state, req); + table = state->new_users; + if (state->entities_found & ENTITY_HOST || + state->entities_found & ENTITY_NG) { + next_call = ipa_netgr_fetch_hosts; + } + } else if (state->current_entity == ENTITY_HOST) { + /* We just received a batch of hosts */ + state->host_base_iter++; + ret = ipa_netgr_fetch_hosts(state, req); + table = state->new_hosts; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid entity type given for processing: %d\n", + state->current_entity); + ret = EINVAL; + goto fail; + } + + if (ret == EOK) { + /* Next search base has been scheduled for inspection, + * don't try to look for other type of entities + */ + next_batch_scheduled = true; + } else if (ret != ENOENT) { + goto fail; + } + + /* Process all member entities and store them in the designated hash table */ + key.type = HASH_KEY_STRING; + value.type = HASH_VALUE_PTR; + for (i = 0; i < count; i++) { + ret = sysdb_attrs_get_string(entities[i], SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + goto fail; + } + + key.str = talloc_strdup(table, orig_dn); + if (key.str == NULL) { + ret = ENOMEM; + goto fail; + } + + value.ptr = entities[i]; + ret = hash_enter(table, &key, &value); + if (ret != HASH_SUCCESS) { + goto fail; + } + } + + if (next_batch_scheduled) { + /* The next search base is already scheduled to be searched */ + return; + } + + if (next_call) { + /* There is another class of members that has to be retrieved + * - schedule the lookup + */ + ret = next_call(state, req); + if (ret != EOK) goto fail; + } else { + /* All members, that could have been fetched, were fetched */ + ret = ipa_netgr_process_all(state); + if (ret != EOK) goto fail; + + tevent_req_done(req); + } + + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static bool extract_netgroups(hash_entry_t *entry, void *pvt) +{ + struct ipa_get_netgroups_state *state; + state = talloc_get_type(pvt, struct ipa_get_netgroups_state); + + state->netgroups[state->netgroups_count] = talloc_get_type(entry->value.ptr, + struct sysdb_attrs); + state->netgroups_count++; + + return true; +} + +struct extract_state { + const char *group; + const char *appropriateMemberOf; + + const char **entries; + int entries_count; +}; + +static bool extract_entities(hash_entry_t *entry, void *pvt) +{ + int ret; + struct extract_state *state; + struct sysdb_attrs *member; + struct ldb_message_element *el; + struct ldb_message_element *name_el; + + state = talloc_get_type(pvt, struct extract_state); + member = talloc_get_type(entry->value.ptr, struct sysdb_attrs); + + ret = sysdb_attrs_get_el(member, state->appropriateMemberOf, &el); + if (ret != EOK) { + return false; + } + + ret = sysdb_attrs_get_el(member, SYSDB_NAME, &name_el); + if (ret != EOK || name_el == NULL || name_el->num_values == 0) { + return false; + } + + for (int j = 0; j < el->num_values; j++) { + if (strcmp((char *)el->values[j].data, state->group) == 0) { + state->entries = talloc_realloc(state, state->entries, + const char *, + state->entries_count + 1); + if (state->entries == NULL) { + return false; + } + + state->entries[state->entries_count] = (char *)name_el->values[0].data; + state->entries_count++; + break; + } + } + + return true; +} + +static int extract_members(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *netgroup, + const char *member_type, + const char *appropriateMemberOf, + hash_table_t *lookup_table, + const char ***_ret_array, + int *_ret_count) +{ + struct extract_state *state; + struct ldb_message_element *el; + struct sysdb_attrs *member; + hash_key_t key; + hash_value_t value; + const char **process = NULL; + const char **ret_array = NULL; + int process_count = 0; + int ret_count = 0; + int ret, i, pi; + + key.type = HASH_KEY_STRING; + value.type = HASH_VALUE_PTR; + + state = talloc_zero(mem_ctx, struct extract_state); + if (state == NULL) { + ret = ENOMEM; + goto done; + } + + state->appropriateMemberOf = appropriateMemberOf; + + ret = sysdb_attrs_get_el(netgroup, member_type, &el); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + if (ret == EOK) { + for (i = 0; i < el->num_values; i++) { + key.str = (char *)el->values[i].data; + ret = hash_lookup(lookup_table, &key, &value); + if (ret != HASH_SUCCESS && ret != HASH_ERROR_KEY_NOT_FOUND) { + ret = ENOENT; + goto done; + } + + if (ret == HASH_ERROR_KEY_NOT_FOUND) { + process = talloc_realloc(mem_ctx, process, const char *, process_count + 1); + if (process == NULL) { + ret = ENOMEM; + goto done; + } + + process[process_count] = (char *)el->values[i].data; + process_count++; + } else { + ret_array = talloc_realloc(mem_ctx, ret_array, const char *, ret_count + 1); + if (ret_array == NULL) { + ret = ENOMEM; + goto done; + } + member = talloc_get_type(value.ptr, struct sysdb_attrs); + ret = sysdb_attrs_get_string(member, SYSDB_NAME, &ret_array[ret_count]); + if (ret != EOK) { + goto done; + } + ret_count++; + } + + for (pi = 0; pi < process_count; pi++) { + state->group = process[pi]; + hash_iterate(lookup_table, extract_entities, state); + if (state->entries_count > 0) { + ret_array = talloc_realloc(mem_ctx, ret_array, const char *, + ret_count + state->entries_count); + if (ret_array == NULL) { + ret = ENOMEM; + goto done; + } + memcpy(&ret_array[ret_count], state->entries, + state->entries_count*sizeof(const char *)); + ret_count += state->entries_count; + } + state->entries_count = 0; + talloc_zfree(state->entries); + } + } + } else { + ret_array = NULL; + } + + *_ret_array = ret_array; + *_ret_count = ret_count; + ret = EOK; + +done: + return ret; +} + +static int ipa_netgr_process_all(struct ipa_get_netgroups_state *state) +{ + int i, j, k, ret; + const char **members; + struct sysdb_attrs *member; + const char *member_name; + struct extract_state *extract_state; + struct ldb_message_element *external_hosts; + const char *dash[] = {"-"}; + const char **uids = NULL; + const char **hosts = NULL; + int uids_count = 0; + int hosts_count = 0; + hash_key_t key; + hash_value_t value; + const char *domain; + char *triple; + + state->netgroups = talloc_zero_array(state, struct sysdb_attrs *, + hash_count(state->new_netgroups)); + if (state->netgroups == NULL) { + return ENOMEM; + } + + extract_state = talloc_zero(state, struct extract_state); + if (extract_state == NULL) { + ret = ENOMEM; + goto done; + } + + key.type = HASH_KEY_STRING; + value.type = HASH_VALUE_PTR; + + hash_iterate(state->new_netgroups, extract_netgroups, state); + for (i = 0; i < state->netgroups_count; i++) { + /* Make sure these attributes always exist, so we can remove them if + * there are no members. */ + ret = sysdb_attrs_add_empty(state->netgroups[i], SYSDB_NETGROUP_MEMBER); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_add_empty(state->netgroups[i], SYSDB_NETGROUP_TRIPLE); + if (ret != EOK) { + goto done; + } + + /* load all its member netgroups, translate */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Extracting netgroup members of netgroup %d\n", i); + ret = sysdb_attrs_get_string_array(state->netgroups[i], + SYSDB_ORIG_NETGROUP_MEMBER, + state, &members); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + j = 0; + if (ret == EOK) { + for (j = 0; members[j]; j++) { + key.str = discard_const(members[j]); + ret = hash_lookup(state->new_netgroups, &key, &value); + if (ret != HASH_SUCCESS) { + ret = ENOENT; + goto done; + } + + member = talloc_get_type(value.ptr, struct sysdb_attrs); + ret = sysdb_attrs_get_string(member, SYSDB_NAME, &member_name); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_add_string(state->netgroups[i], + SYSDB_NETGROUP_MEMBER, + member_name); + if (ret != EOK) { + goto done; + } + } + talloc_zfree(members); + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Extracted %d netgroup members\n", j); + + /* Load all UIDs */ + DEBUG(SSSDBG_TRACE_ALL, "Extracting user members of netgroup %d\n", i); + ret = extract_members(state, state->netgroups[i], + SYSDB_ORIG_MEMBER_USER, + state->ipa_opts->id->user_map[SDAP_AT_USER_MEMBEROF].sys_name, + state->new_users, + &uids, &uids_count); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Extracted %d user members\n", uids_count); + + DEBUG(SSSDBG_TRACE_ALL, "Extracting host members of netgroup %d\n", i); + ret = extract_members(state, state->netgroups[i], + SYSDB_ORIG_MEMBER_HOST, + state->ipa_opts->id->host_map[SDAP_AT_HOST_MEMBER_OF].sys_name, + state->new_hosts, + &hosts, &hosts_count); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Extracted %d host members\n", hosts_count); + + ret = sysdb_attrs_get_el(state->netgroups[i], + SYSDB_ORIG_NETGROUP_EXTERNAL_HOST, + &external_hosts); + if (ret != EOK) { + goto done; + } + + if (external_hosts->num_values > 0) { + hosts = talloc_realloc(state, hosts, const char *, + hosts_count + external_hosts->num_values); + if (hosts == NULL) { + ret = ENOMEM; + goto done; + } + + for (j = 0; j < external_hosts->num_values; j++) { + hosts[hosts_count] = talloc_strdup(hosts, (char *)external_hosts->values[j].data); + if (hosts[hosts_count] == NULL) { + ret = ENOMEM; + goto done; + } + hosts_count++; + } + } + + ret = sysdb_attrs_get_string(state->netgroups[i], SYSDB_NETGROUP_DOMAIN, + &domain); + if (ret == ENOENT) { + domain = NULL; + } else if (ret != EOK) { + goto done; + } + + if (uids_count > 0 || hosts_count > 0) { + if (uids_count == 0) { + uids_count = 1; + uids = dash; + } + + if (hosts_count == 0) { + hosts_count = 1; + hosts = dash; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Putting together triples of " + "netgroup %d\n", i); + for (j = 0; j < uids_count; j++) { + for (k = 0; k < hosts_count; k++) { + triple = talloc_asprintf(state, "(%s,%s,%s)", + hosts[k], uids[j], + domain ? domain : ""); + if (triple == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(state->netgroups[i], + SYSDB_NETGROUP_TRIPLE, + triple); + if (ret != EOK) { + goto done; + } + } + } + } + + ret = ipa_save_netgroup(state, state->dom, + state->opts, state->netgroups[i]); + if (ret != EOK) { + goto done; + } + } + + ret = EOK; +done: + return ret; +} + +int ipa_get_netgroups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply) +{ + struct ipa_get_netgroups_state *state = tevent_req_data(req, + struct ipa_get_netgroups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (reply_count) { + *reply_count = state->netgroups_count; + } + + if (reply) { + *reply = talloc_steal(mem_ctx, state->netgroups); + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_opts.c b/src/providers/ipa/ipa_opts.c new file mode 100644 index 0000000..97cddb1 --- /dev/null +++ b/src/providers/ipa/ipa_opts.c @@ -0,0 +1,428 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "src/providers/data_provider.h" +#include "db/sysdb.h" +#include "db/sysdb_sudo.h" +#include "db/sysdb_autofs.h" +#include "db/sysdb_services.h" +#include "db/sysdb_selinux.h" +#include "db/sysdb_subid.h" +#include "providers/ldap/ldap_common.h" + +struct dp_option ipa_basic_opts[] = { + { "ipa_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_hostname", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_hbac_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "ipa_host_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_selinux_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_subdomains_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_master_domain_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "ipa_hbac_refresh", DP_OPT_NUMBER, { .number = 5 }, NULL_NUMBER }, + { "ipa_selinux_refresh", DP_OPT_NUMBER, { .number = 5 }, NULL_NUMBER }, + { "ipa_hbac_support_srchost", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ipa_automount_location", DP_OPT_STRING, { "default" }, NULL_STRING }, + { "ipa_ranges_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_enable_dns_sites", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ipa_server_mode", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ipa_views_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_confd_path", DP_OPT_STRING, { KRB5_MAPPING_DIR }, NULL_STRING }, + { "ipa_deskprofile_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_deskprofile_refresh", DP_OPT_NUMBER, { .number = 5 }, NULL_NUMBER }, + { "ipa_deskprofile_request_interval", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + { "ipa_subid_ranges_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_access_order", DP_OPT_STRING, { "expire" }, NULL_STRING }, + DP_OPTION_TERMINATOR +}; + +struct dp_option ipa_dyndns_opts[] = { + { "dyndns_update", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_update_per_family", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "dyndns_refresh_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER }, + { "dyndns_refresh_interval_offset", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER }, + { "dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "dyndns_ttl", DP_OPT_NUMBER, { .number = 1200 }, NULL_NUMBER }, + { "dyndns_update_ptr", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_force_tcp", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_auth", DP_OPT_STRING, { "gss-tsig" }, NULL_STRING }, + { "dyndns_auth_ptr", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "dyndns_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + DP_OPTION_TERMINATOR +}; + +struct dp_option ipa_def_ldap_opts[] = { + { "ldap_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_backup_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_authtok_type", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB }, + { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 8 }, NULL_NUMBER }, + { "ldap_tls_reqcert", DP_OPT_STRING, { "hard" }, NULL_STRING }, + { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_user_extra_attrs", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_host_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_service_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_full_refresh_interval", DP_OPT_NUMBER, { .number = 21600 }, NULL_NUMBER }, + { "ldap_sudo_smart_refresh_interval", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, /* 15 mins */ + { "ldap_sudo_random_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, /* disabled */ + { "ldap_sudo_use_host_filter", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_sudo_hostnames", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_ip", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_include_netgroups", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_sudo_include_regexp", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_autofs_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_autofs_map_master_name", DP_OPT_STRING, { "auto.master" }, NULL_STRING }, + { "ldap_iphost_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_ipnetwork_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_schema", DP_OPT_STRING, { "ipa_v1" }, NULL_STRING }, + { "ldap_pwmodify_mode", DP_OPT_STRING, { "exop" }, NULL_STRING }, + { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER }, + { "ldap_enumeration_refresh_offset", DP_OPT_NUMBER, { .number = 30 }, NULL_NUMBER }, + { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_purge_cache_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_tls_cacert", DP_OPT_STRING, { "/etc/ipa/ca.crt" }, NULL_STRING }, + { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_key", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cipher_suite", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_id_use_start_tls", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_id_mapping", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_sasl_mech", DP_OPT_STRING, { "GSSAPI" } , NULL_STRING }, + { "ldap_sasl_authid", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_minssf", DP_OPT_NUMBER, { .number = 56 }, NULL_NUMBER }, + { "ldap_sasl_maxssf", DP_OPT_NUMBER, { .number = -1 }, NULL_NUMBER }, + { "ldap_krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_krb5_init_creds", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + /* use the same parm name as the krb5 module so we set it only once */ + { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_canonicalize", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kdcinfo_lookahead", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_pwd_policy", DP_OPT_STRING, { "none" } , NULL_STRING }, + { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "account_cache_expiration", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_dns_service_name", DP_OPT_STRING, { SSS_LDAP_SRV_NAME }, NULL_STRING }, + { "ldap_krb5_ticket_lifetime", DP_OPT_NUMBER, { .number = (24 * 60 * 60) }, NULL_NUMBER }, + { "ldap_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_netgroup_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_nesting_level", DP_OPT_NUMBER, { .number = 2 }, NULL_NUMBER }, + { "ldap_deref", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_account_expire_policy", DP_OPT_STRING, { "ipa" }, NULL_STRING }, + { "ldap_access_order", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_backup_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_dns_service_name", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_update_last_change", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_enumeration_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + /* Do not include ldap_auth_disable_tls_never_use_in_production in the + * manpages or SSSDConfig API + */ + { "ldap_auth_disable_tls_never_use_in_production", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_page_size", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER }, + { "ldap_deref_threshold", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }, + { "ldap_ignore_unreadable_references", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_sasl_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_connection_expire_timeout", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, + { "ldap_connection_expire_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_connection_idle_timeout", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, + { "ldap_disable_paging", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_idmap_range_min", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, + { "ldap_idmap_range_max", DP_OPT_NUMBER, { .number = 2000200000LL }, NULL_NUMBER }, + { "ldap_idmap_range_size", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, + { "ldap_idmap_autorid_compat", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_idmap_default_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_idmap_default_domain_sid", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_idmap_helper_table_size", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }, + { "ldap_use_tokengroups", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE}, + { "ldap_rfc2307_fallback_to_local_users", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_disable_range_retrieval", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_min_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_max_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_pwdlockout_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "wildcard_limit", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER}, + { "ldap_library_debug_level", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + DP_OPTION_TERMINATOR +}; + +struct sdap_attr_map ipa_attr_map[] = { + { "ldap_entry_usn", "entryUSN", SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", "lastUSN", SYSDB_HIGH_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_user_map[] = { + { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL }, + { "ldap_user_name", "uid", SYSDB_NAME, NULL }, + { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL }, + { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL }, + { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL }, + { "ldap_user_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + { "ldap_user_objectsid", "ipaNTSecurityIdentifier", SYSDB_SID_STR, NULL }, + { "ldap_user_primary_group", NULL, SYSDB_PRIMARY_GROUP, NULL }, + { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }, + { "ldap_user_authorized_service", "authorizedService", SYSDB_AUTHORIZED_SERVICE, NULL }, + { "ldap_user_ad_account_expires", "accountExpires", SYSDB_AD_ACCOUNT_EXPIRES, NULL}, + { "ldap_user_ad_user_account_control", "userAccountControl", SYSDB_AD_USER_ACCOUNT_CONTROL, NULL}, + { "ldap_ns_account_lock", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL}, + { "ldap_user_authorized_host", "host", SYSDB_AUTHORIZED_HOST, NULL }, + { "ldap_user_authorized_rhost", NULL, SYSDB_AUTHORIZED_RHOST, NULL }, + { "ldap_user_nds_login_disabled", "loginDisabled", SYSDB_NDS_LOGIN_DISABLED, NULL }, + { "ldap_user_nds_login_expiration_time", "loginExpirationTime", SYSDB_NDS_LOGIN_EXPIRATION_TIME, NULL }, + { "ldap_user_nds_login_allowed_time_map", "loginAllowedTimeMap", SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP, NULL }, + { "ldap_user_ssh_public_key", "ipaSshPubKey", SYSDB_SSH_PUBKEY, NULL }, + { "ldap_user_auth_type", "ipaUserAuthType", SYSDB_AUTH_TYPE, NULL }, + { "ldap_user_certificate", "userCertificate;binary", SYSDB_USER_CERT, NULL }, + { "ldap_user_email", "mail", SYSDB_USER_EMAIL, NULL }, + { "ldap_user_passkey", "ipaPassKey", SYSDB_USER_PASSKEY, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_group_map[] = { + { "ldap_group_object_class", "ipaUserGroup", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_object_class_alt", "posixGroup", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_name", "cn", SYSDB_NAME, NULL }, + { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_group_member", "member", SYSDB_MEMBER, NULL }, + { "ldap_group_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + { "ldap_group_objectsid", "ipaNTSecurityIdentifier", SYSDB_SID_STR, NULL }, + { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_group_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_group_type", NULL, SYSDB_GROUP_TYPE, NULL }, + { "ldap_group_external_member", "ipaExternalMember", SYSDB_EXTERNAL_MEMBER, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_netgroup_map[] = { + { "ipa_netgroup_object_class", "ipaNisNetgroup", SYSDB_NETGROUP_CLASS, NULL }, + { "ipa_netgroup_name", "cn", SYSDB_NAME, NULL }, + { "ipa_netgroup_member", "member", SYSDB_ORIG_NETGROUP_MEMBER, NULL }, + { "ipa_netgroup_member_of", "memberOf", SYSDB_MEMBEROF, NULL }, + { "ipa_netgroup_member_user", "memberUser", SYSDB_ORIG_MEMBER_USER, NULL }, + { "ipa_netgroup_member_host", "memberHost", SYSDB_ORIG_MEMBER_HOST, NULL }, + { "ipa_netgroup_member_ext_host", "externalHost", SYSDB_ORIG_NETGROUP_EXTERNAL_HOST, NULL }, + { "ipa_netgroup_domain", "nisDomainName", SYSDB_NETGROUP_DOMAIN, NULL }, + { "ipa_netgroup_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_subid_map[] = { + { "ipa_subuid_object_class", "ipasubordinateid", SYSDB_SUBID_RANGE_OC, NULL }, + { "ipa_subuid_count", "ipaSubUidCount", SYSDB_SUBID_UID_COUND, NULL }, + { "ipa_subgid_count", "ipaSubGidCount", SYSDB_SUBID_GID_COUNT, NULL }, + { "ipa_subuid_number", "ipaSubUidNumber", SYSDB_SUBID_UID_NUMBER, NULL }, + { "ipa_subgid_number", "ipaSubGidNumber", SYSDB_SUBID_GID_NUMBER, NULL }, + { "ipa_owner", "ipaOwner", SYSDB_SUBID_OWNER, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_host_map[] = { + { "ipa_host_object_class", "ipaHost", SYSDB_HOST_CLASS, NULL }, + { "ipa_host_name", "cn", SYSDB_NAME, NULL }, + { "ipa_host_fqdn", "fqdn", SYSDB_FQDN, NULL }, + { "ipa_host_serverhostname", "serverHostname", SYSDB_SERVERHOSTNAME, NULL }, + { "ipa_host_member_of", "memberOf", SYSDB_ORIG_MEMBEROF, NULL }, + { "ipa_host_ssh_public_key", "ipaSshPubKey", SYSDB_SSH_PUBKEY, NULL }, + { "ipa_host_uuid", "ipaUniqueID", SYSDB_UUID, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_hostgroup_map[] = { + { "ipa_hostgroup_objectclass", "ipaHostgroup", SYSDB_HOSTGROUP_CLASS, NULL}, + { "ipa_hostgroup_name", "cn", SYSDB_NAME, NULL}, + { "ipa_hostgroup_memberof", "memberOf", SYSDB_ORIG_MEMBEROF, NULL}, + { "ipa_hostgroup_uuid", "ipaUniqueID", SYSDB_UUID, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_selinux_user_map[] = { + { "ipa_selinux_usermap_object_class", "ipaselinuxusermap", SYSDB_SELINUX_USERMAP_CLASS, NULL}, + { "ipa_selinux_usermap_name", "cn", SYSDB_NAME, NULL}, + { "ipa_selinux_usermap_member_user", "memberUser", SYSDB_ORIG_MEMBER_USER, NULL}, + { "ipa_selinux_usermap_member_host", "memberHost", SYSDB_ORIG_MEMBER_HOST, NULL}, + { "ipa_selinux_usermap_see_also", "seeAlso", SYSDB_SELINUX_SEEALSO, NULL}, + { "ipa_selinux_usermap_selinux_user", "ipaSELinuxUser", SYSDB_SELINUX_USER, NULL}, + { "ipa_selinux_usermap_enabled", "ipaEnabledFlag", SYSDB_SELINUX_ENABLED, NULL}, + { "ipa_selinux_usermap_user_category", "userCategory", SYSDB_USER_CATEGORY, NULL}, + { "ipa_selinux_usermap_host_category", "hostCategory", SYSDB_HOST_CATEGORY, NULL}, + { "ipa_selinux_usermap_uuid", "ipaUniqueID", SYSDB_UUID, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_view_map[] = { + { "ipa_view_class", "nsContainer", SYSDB_VIEW_CLASS, NULL}, + { "ipa_view_name", "cn", SYSDB_VIEW_NAME, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_override_map[] = { + { "ipa_override_object_class", "ipaOverrideAnchor", SYSDB_OVERRIDE_CLASS, NULL}, + { "ipa_anchor_uuid", "ipaAnchorUUID", SYSDB_OVERRIDE_ANCHOR_UUID, NULL}, + { "ipa_user_override_object_class", "ipaUserOverride", SYSDB_OVERRIDE_USER_CLASS, NULL}, + { "ipa_group_override_object_class", "ipaGroupOverride", SYSDB_OVERRIDE_GROUP_CLASS, NULL}, + { "ldap_user_name", "uid", SYSDB_NAME, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_group_name", "cn", SYSDB_NAME, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_ssh_public_key", "ipaSshPubKey", SYSDB_SSH_PUBKEY, NULL }, + { "ldap_user_certificate", "userCertificate;binary", SYSDB_USER_CERT, NULL }, + { "", "objectClass", SYSDB_ORIG_OBJECTCLASS, NULL }, /* We don't want this to be configurable */ + SDAP_ATTR_MAP_TERMINATOR +}; + +struct dp_option ipa_def_krb5_opts[] = { + { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_ccachedir", DP_OPT_STRING, { DEFAULT_CCACHE_DIR }, NULL_STRING }, + { "krb5_ccname_template", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING }, + { "krb5_validate", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_store_password_if_offline", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_renewable_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_renew_interval", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_use_fast", DP_OPT_STRING, { "try" }, NULL_STRING }, + { "krb5_fast_principal", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_fast_use_anonymous_pkinit", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_canonicalize", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_use_enterprise_principal", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kdcinfo_lookahead", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_map_user", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_use_subdomain_realm", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + DP_OPTION_TERMINATOR +}; + +struct sdap_attr_map ipa_service_map[] = { + { "ldap_service_object_class", "ipService", SYSDB_SVC_CLASS, NULL }, + { "ldap_service_name", "cn", SYSDB_NAME, NULL }, + { "ldap_service_port", "ipServicePort", SYSDB_SVC_PORT, NULL }, + { "ldap_service_proto", "ipServiceProtocol", SYSDB_SVC_PROTO, NULL }, + { "ldap_service_entry_usn", NULL, SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_autofs_mobject_map[] = { + { "ldap_autofs_map_object_class", "automountMap", SYSDB_AUTOFS_MAP_OC, NULL }, + { "ldap_autofs_map_name", "automountMapName", SYSDB_AUTOFS_MAP_NAME, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_autofs_entry_map[] = { + { "ldap_autofs_entry_object_class", "automount", SYSDB_AUTOFS_ENTRY_OC, NULL }, + { "ldap_autofs_entry_key", "automountKey", SYSDB_AUTOFS_ENTRY_KEY, NULL }, + { "ldap_autofs_entry_value", "automountInformation", SYSDB_AUTOFS_ENTRY_VALUE, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_sudorule_map[] = { + { "ipa_sudorule_object_class", "ipasudorule", SYSDB_IPA_SUDORULE_OC, NULL }, + { "ipa_sudorule_name", "cn", SYSDB_NAME, NULL }, + { "ipa_sudorule_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + { "ipa_sudorule_enabled_flag", "ipaEnabledFlag", SYSDB_IPA_SUDORULE_ENABLED, NULL }, + { "ipa_sudorule_option", "ipaSudoOpt", SYSDB_IPA_SUDORULE_OPTION, NULL }, + { "ipa_sudorule_runasuser", "ipaSudoRunAs", SYSDB_IPA_SUDORULE_RUNASUSER, NULL }, + { "ipa_sudorule_runasgroup", "ipaSudoRunAsGroup", SYSDB_IPA_SUDORULE_RUNASGROUP, NULL }, + { "ipa_sudorule_allowcmd", "memberAllowCmd", SYSDB_IPA_SUDORULE_ALLOWCMD, NULL }, + { "ipa_sudorule_denycmd", "memberDenyCmd", SYSDB_IPA_SUDORULE_DENYCMD, NULL }, + { "ipa_sudorule_host", "memberHost", SYSDB_IPA_SUDORULE_HOST, NULL }, + { "ipa_sudorule_user", "memberUser", SYSDB_IPA_SUDORULE_USER, NULL }, + { "ipa_sudorule_notafter", "sudoNotAfter", SYSDB_IPA_SUDORULE_NOTAFTER, NULL }, + { "ipa_sudorule_notbefore", "sudoNotBefore", SYSDB_IPA_SUDORULE_NOTBEFORE, NULL }, + { "ipa_sudorule_sudoorder", "sudoOrder", SYSDB_IPA_SUDORULE_SUDOORDER, NULL }, + { "ipa_sudorule_cmdcategory", "cmdCategory", SYSDB_IPA_SUDORULE_CMDCATEGORY, NULL }, + { "ipa_sudorule_hostcategory", "hostCategory", SYSDB_IPA_SUDORULE_HOSTCATEGORY, NULL }, + { "ipa_sudorule_usercategory", "userCategory", SYSDB_IPA_SUDORULE_USERCATEGORY, NULL }, + { "ipa_sudorule_runasusercategory", "ipaSudoRunAsUserCategory", SYSDB_IPA_SUDORULE_RUNASUSERCATEGORY, NULL }, + { "ipa_sudorule_runasgroupcategory", "ipaSudoRunAsGroupCategory", SYSDB_IPA_SUDORULE_RUNASGROUPCATEGORY, NULL }, + { "ipa_sudorule_runasextuser", "ipaSudoRunAsExtUser", SYSDB_IPA_SUDORULE_RUNASEXTUSER, NULL }, + { "ipa_sudorule_runasextgroup", "ipaSudoRunAsExtGroup", SYSDB_IPA_SUDORULE_RUNASEXTGROUP, NULL }, + { "ipa_sudorule_runasextusergroup", "ipaSudoRunAsExtUserGroup", SYSDB_IPA_SUDORULE_RUNASEXTUSERGROUP, NULL }, + { "ipa_sudorule_externaluser", "externalUser", SYSDB_IPA_SUDORULE_EXTUSER, NULL }, + { "ipa_sudorule_entry_usn", "entryUSN", SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_sudocmdgroup_map[] = { + { "ipa_sudocmdgroup_object_class", "ipasudocmdgrp", SYSDB_IPA_SUDOCMDGROUP_OC, NULL }, + { "ipa_sudocmdgroup_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + { "ipa_sudocmdgroup_name", "cn", SYSDB_NAME, NULL }, + { "ipa_sudocmdgroup_member", "member", SYSDB_MEMBER, NULL }, + { "ipa_sudocmdgroup_entry_usn", "entryUSN", SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_sudocmd_map[] = { + { "ipa_sudocmd_object_class", "ipasudocmd", SYSDB_IPA_SUDOCMD_OC, NULL }, + { "ipa_sudocmd_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + { "ipa_sudocmd_sudoCmd", "sudoCmd", SYSDB_IPA_SUDOCMD_SUDOCMD, NULL }, + { "ipa_sudocmd_memberof", "memberOf", SYSDB_MEMBEROF, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct dp_option ipa_cli_ad_subdom_opts [] = { + { "ad_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_site", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + DP_OPTION_TERMINATOR +}; diff --git a/src/providers/ipa/ipa_opts.h b/src/providers/ipa/ipa_opts.h new file mode 100644 index 0000000..6f54e57 --- /dev/null +++ b/src/providers/ipa/ipa_opts.h @@ -0,0 +1,71 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_OPTS_H_ +#define IPA_OPTS_H_ + +#include "src/providers/data_provider.h" +#include "providers/ldap/ldap_common.h" + +extern struct dp_option ipa_basic_opts[]; + +extern struct dp_option ipa_dyndns_opts[]; + +extern struct dp_option ipa_def_ldap_opts[]; + +extern struct sdap_attr_map ipa_attr_map[]; + +extern struct sdap_attr_map ipa_user_map[]; + +extern struct sdap_attr_map ipa_group_map[]; + +extern struct sdap_attr_map ipa_netgroup_map[]; + +extern struct sdap_attr_map ipa_subid_map[]; + +extern struct sdap_attr_map ipa_host_map[]; + +extern struct sdap_attr_map ipa_hostgroup_map[]; + +extern struct sdap_attr_map ipa_selinux_user_map[]; + +extern struct sdap_attr_map ipa_view_map[]; + +extern struct sdap_attr_map ipa_override_map[]; + +extern struct dp_option ipa_def_krb5_opts[]; + +extern struct sdap_attr_map ipa_service_map[]; + +extern struct sdap_attr_map ipa_autofs_mobject_map[]; + +extern struct sdap_attr_map ipa_autofs_entry_map[]; + +extern struct sdap_attr_map ipa_sudorule_map[]; + +extern struct sdap_attr_map ipa_sudocmdgroup_map[]; + +extern struct sdap_attr_map ipa_sudocmd_map[]; + +extern struct dp_option ipa_cli_ad_subdom_opts[]; + +#endif /* IPA_OPTS_H_ */ diff --git a/src/providers/ipa/ipa_refresh.c b/src/providers/ipa/ipa_refresh.c new file mode 100644 index 0000000..64f8db8 --- /dev/null +++ b/src/providers/ipa/ipa_refresh.c @@ -0,0 +1,220 @@ +/* + Copyright (C) 2019 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_id.h" + +struct ipa_refresh_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct dp_id_data *account_req; + struct ipa_id_ctx *id_ctx; + struct sss_domain_info *domain; + char **names; + size_t index; +}; + +static errno_t ipa_refresh_step(struct tevent_req *req); +static void ipa_refresh_done(struct tevent_req *subreq); + +static struct tevent_req *ipa_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + int entry_type, + char **names, + void *pvt) +{ + struct ipa_refresh_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (names == NULL) { + ret = EOK; + goto immediately; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->domain = domain; + state->id_ctx = talloc_get_type(pvt, struct ipa_id_ctx); + state->names = names; + state->index = 0; + + state->account_req = be_refresh_acct_req(state, entry_type, + BE_FILTER_NAME, domain); + if (state->account_req == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = ipa_refresh_step(req); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Nothing to refresh\n"); + goto immediately; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, "ipa_refresh_step() failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ipa_refresh_step(struct tevent_req *req) +{ + struct ipa_refresh_state *state = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + state = tevent_req_data(req, struct ipa_refresh_state); + + if (state->names == NULL) { + ret = EOK; + goto done; + } + + state->account_req->filter_value = state->names[state->index]; + if (state->account_req->filter_value == NULL) { + ret = EOK; + goto done; + } + + subreq = ipa_account_info_send(state, state->be_ctx, state->id_ctx, + state->account_req); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_refresh_done, req); + + state->index++; + ret = EAGAIN; + +done: + return ret; +} + +static void ipa_refresh_done(struct tevent_req *subreq) +{ + struct ipa_refresh_state *state = NULL; + struct tevent_req *req = NULL; + errno_t dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_refresh_state); + + ret = ipa_account_info_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to refresh %s [dp_error: %d, " + "errno: %d]\n", be_req2str(state->account_req->entry_type), + dp_error, ret); + goto done; + } + + if (state->account_req->entry_type == BE_REQ_INITGROUPS) { + ret = sysdb_set_initgr_expire_timestamp(state->domain, + state->account_req->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to set initgroups expiration for [%s]\n", + state->account_req->filter_value); + } + } + + ret = ipa_refresh_step(req); + if (ret == EAGAIN) { + return; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_refresh_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +REFRESH_SEND_RECV_FNS(ipa_refresh_initgroups, ipa_refresh, BE_REQ_INITGROUPS); +REFRESH_SEND_RECV_FNS(ipa_refresh_users, ipa_refresh, BE_REQ_USER); +REFRESH_SEND_RECV_FNS(ipa_refresh_groups, ipa_refresh, BE_REQ_GROUP); +REFRESH_SEND_RECV_FNS(ipa_refresh_netgroups, ipa_refresh, BE_REQ_NETGROUP); + +errno_t ipa_refresh_init(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx) +{ + errno_t ret; + struct be_refresh_cb ipa_refresh_callbacks[] = { + { .send_fn = ipa_refresh_initgroups_send, + .recv_fn = ipa_refresh_initgroups_recv, + .pvt = id_ctx, + }, + { .send_fn = ipa_refresh_users_send, + .recv_fn = ipa_refresh_users_recv, + .pvt = id_ctx, + }, + { .send_fn = ipa_refresh_groups_send, + .recv_fn = ipa_refresh_groups_recv, + .pvt = id_ctx, + }, + { .send_fn = ipa_refresh_netgroups_send, + .recv_fn = ipa_refresh_netgroups_recv, + .pvt = id_ctx, + }, + }; + + ret = be_refresh_ctx_init_with_callbacks(be_ctx, + SYSDB_NAME, + ipa_refresh_callbacks); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize background refresh\n"); + return ret; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_rules_common.c b/src/providers/ipa/ipa_rules_common.c new file mode 100644 index 0000000..1182347 --- /dev/null +++ b/src/providers/ipa/ipa_rules_common.c @@ -0,0 +1,455 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ipa/ipa_rules_common.h" + +static errno_t +ipa_common_save_list(struct sss_domain_info *domain, + bool delete_subdir, + const char *subdir, + const char *naming_attribute, + size_t count, + struct sysdb_attrs **list) +{ + int ret; + size_t c; + struct ldb_dn *base_dn; + const char *object_name; + struct ldb_message_element *el; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (delete_subdir) { + base_dn = sysdb_custom_subtree_dn(tmp_ctx, domain, subdir); + if (base_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_delete_recursive(domain->sysdb, base_dn, true); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_delete_recursive failed.\n"); + goto done; + } + } + + for (c = 0; c < count; c++) { + ret = sysdb_attrs_get_el(list[c], naming_attribute, &el); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_get_el failed.\n"); + goto done; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "[%s] not found.\n", naming_attribute); + ret = EINVAL; + goto done; + } + object_name = talloc_strndup(tmp_ctx, (const char *)el->values[0].data, + el->values[0].length); + if (object_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Object name: [%s].\n", object_name); + + ret = sysdb_store_custom(domain, object_name, subdir, list[c]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_store_custom failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ipa_common_entries_and_groups_sysdb_save(struct sss_domain_info *domain, + const char *primary_subdir, + const char *attr_name, + size_t primary_count, + struct sysdb_attrs **primary, + const char *group_subdir, + const char *groupattr_name, + size_t group_count, + struct sysdb_attrs **groups) +{ + errno_t ret, sret; + bool in_transaction = false; + + if ((primary_count == 0 || primary == NULL) + || (group_count > 0 && groups == NULL)) { + /* There always has to be at least one + * primary entry. + */ + return EINVAL; + } + + /* Save the entries and groups to the cache */ + ret = sysdb_transaction_start(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + }; + in_transaction = true; + + /* First, save the specific entries */ + ret = ipa_common_save_list(domain, true, primary_subdir, + attr_name, primary_count, primary); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not save %s. [%d][%s]\n", + primary_subdir, ret, strerror(ret)); + goto done; + } + + /* Second, save the groups */ + if (group_count > 0) { + ret = ipa_common_save_list(domain, true, group_subdir, + groupattr_name, group_count, groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not save %s. [%d][%s]\n", + group_subdir, ret, strerror(ret)); + goto done; + } + } + + ret = sysdb_transaction_commit(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(domain->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not cancel sysdb transaction\n"); + } + } + + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Error [%d][%s]\n", ret, strerror(ret)); + } + return ret; +} + +errno_t +ipa_common_get_cached_rules(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule, + const char *subtree_name, + const char **attrs, + size_t *_rule_count, + struct sysdb_attrs ***_rules) +{ + errno_t ret; + struct ldb_message **msgs; + struct sysdb_attrs **rules; + size_t rule_count; + TALLOC_CTX *tmp_ctx; + char *filter; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = talloc_asprintf(tmp_ctx, "(objectClass=%s)", rule); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_custom(tmp_ctx, domain, filter, + subtree_name, attrs, + &rule_count, &msgs); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error looking up HBAC rules\n"); + goto done; + } + + if (ret == ENOENT) { + rule_count = 0; + } + + ret = sysdb_msg2attrs(tmp_ctx, rule_count, msgs, &rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not convert ldb message to sysdb_attrs\n"); + goto done; + } + + if (_rules) { + *_rules = talloc_steal(mem_ctx, rules); + } + + if (_rule_count) { + *_rule_count = rule_count; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ipa_common_purge_rules(struct sss_domain_info *domain, + const char *subtree_name) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *base_dn; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + base_dn = sysdb_custom_subtree_dn(tmp_ctx, domain, subtree_name); + if (base_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_delete_recursive(domain->sysdb, base_dn, true); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_delete_recursive failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t ipa_common_save_rules(struct sss_domain_info *domain, + struct ipa_common_entries *hosts, + struct ipa_common_entries *services, + struct ipa_common_entries *rules, + time_t *last_update) +{ + bool in_transaction = false; + errno_t ret; + errno_t sret; + + ret = sysdb_transaction_start(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not start transaction\n"); + goto done; + } + in_transaction = true; + + /* Save the hosts */ + if (hosts != NULL) { + ret = ipa_common_entries_and_groups_sysdb_save(domain, + hosts->entry_subdir, + SYSDB_FQDN, + hosts->entry_count, + hosts->entries, + hosts->group_subdir, + SYSDB_NAME, + hosts->group_count, + hosts->groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error saving hosts [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + /* Save the services */ + if (services != NULL) { + ret = ipa_common_entries_and_groups_sysdb_save(domain, + services->entry_subdir, + IPA_CN, + services->entry_count, + services->entries, + services->group_subdir, + IPA_CN, + services->group_count, + services->groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error saving services [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + /* Save the rules */ + if (rules != NULL) { + ret = ipa_common_entries_and_groups_sysdb_save(domain, + rules->entry_subdir, + IPA_UNIQUE_ID, + rules->entry_count, + rules->entries, + NULL, NULL, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error saving rules [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + ret = sysdb_transaction_commit(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + *last_update = time(NULL); + + ret = EOK; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(domain->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n"); + } + } + + return ret; +} + +errno_t +ipa_common_get_hostgroupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *host_dn, + char **_hostgroupname) +{ + errno_t ret; + struct ldb_dn *dn; + const char *rdn_name; + const char *hostgroup_comp_name; + const char *account_comp_name; + const struct ldb_val *rdn_val; + const struct ldb_val *hostgroup_comp_val; + const struct ldb_val *account_comp_val; + + /* This is an IPA-specific hack. It may not + * work for non-IPA servers and will need to + * be changed if SSSD ever supports HBAC on + * a non-IPA server. + */ + *_hostgroupname = NULL; + + dn = ldb_dn_new(mem_ctx, sysdb_ctx_get_ldb(sysdb), host_dn); + if (dn == NULL) { + ret = ENOMEM; + goto done; + } + + if (!ldb_dn_validate(dn)) { + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (ldb_dn_get_comp_num(dn) < 4) { + /* RDN, hostgroups, accounts, and at least one DC= */ + /* If it's fewer, it's not a group DN */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* If the RDN name is 'cn' */ + rdn_name = ldb_dn_get_rdn_name(dn); + if (rdn_name == NULL) { + /* Shouldn't happen if ldb_dn_validate() + * passed, but we'll be careful. + */ + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (strcasecmp("cn", rdn_name) != 0) { + /* RDN has the wrong attribute name. + * It's not a host. + */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the second component is "cn=hostgroups" */ + hostgroup_comp_name = ldb_dn_get_component_name(dn, 1); + if (strcasecmp("cn", hostgroup_comp_name) != 0) { + /* The second component name is not "cn" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + hostgroup_comp_val = ldb_dn_get_component_val(dn, 1); + if (strncasecmp("hostgroups", + (const char *) hostgroup_comp_val->data, + hostgroup_comp_val->length) != 0) { + /* The second component value is not "hostgroups" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the third component is "accounts" */ + account_comp_name = ldb_dn_get_component_name(dn, 2); + if (strcasecmp("cn", account_comp_name) != 0) { + /* The third component name is not "cn" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + account_comp_val = ldb_dn_get_component_val(dn, 2); + if (strncasecmp("accounts", + (const char *) account_comp_val->data, + account_comp_val->length) != 0) { + /* The third component value is not "accounts" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* Then the value of the RDN is the group name */ + rdn_val = ldb_dn_get_rdn_val(dn); + *_hostgroupname = talloc_strndup(mem_ctx, + (const char *)rdn_val->data, + rdn_val->length); + if (*_hostgroupname == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + talloc_free(dn); + return ret; +} diff --git a/src/providers/ipa/ipa_rules_common.h b/src/providers/ipa/ipa_rules_common.h new file mode 100644 index 0000000..6cf57eb --- /dev/null +++ b/src/providers/ipa/ipa_rules_common.h @@ -0,0 +1,89 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_RULES_COMMON_H_ +#define IPA_RULES_COMMON_H_ + +#include "providers/backend.h" + +#define IPA_UNIQUE_ID "ipauniqueid" + +#define OBJECTCLASS "objectclass" +#define IPA_MEMBER_USER "memberUser" +#define IPA_USER_CATEGORY "userCategory" +#define IPA_EXTERNAL_HOST "externalHost" +#define IPA_ENABLED_FLAG "ipaenabledflag" +#define IPA_MEMBER_HOST "memberHost" +#define IPA_HOST_CATEGORY "hostCategory" +#define IPA_CN "cn" +#define IPA_TRUE_VALUE "TRUE" + +/* From ipa_rules_common.c */ + +struct ipa_common_entries { + const char *entry_subdir; + size_t entry_count; + struct sysdb_attrs **entries; + + const char *group_subdir; + size_t group_count; + struct sysdb_attrs **groups; +}; + +errno_t +ipa_common_entries_and_groups_sysdb_save(struct sss_domain_info *domain, + const char *primary_subdir, + const char *attr_name, + size_t primary_count, + struct sysdb_attrs **primary, + const char *group_subdir, + const char *groupattr_name, + size_t group_count, + struct sysdb_attrs **groups); + +errno_t +ipa_common_get_cached_rules(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule, + const char *subtree_name, + const char **attrs, + size_t *_rule_count, + struct sysdb_attrs ***_rules); + +errno_t +ipa_common_purge_rules(struct sss_domain_info *domain, + const char *subtree_name); + +errno_t +ipa_common_save_rules(struct sss_domain_info *domain, + struct ipa_common_entries *hosts, + struct ipa_common_entries *services, + struct ipa_common_entries *rules, + time_t *last_update); + +errno_t +ipa_common_get_hostgroupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *host_dn, + char **_hostgroupname); + +#endif /* IPA_RULES_COMMON_H_ */ diff --git a/src/providers/ipa/ipa_s2n_exop.c b/src/providers/ipa/ipa_s2n_exop.c new file mode 100644 index 0000000..ec944d9 --- /dev/null +++ b/src/providers/ipa/ipa_s2n_exop.c @@ -0,0 +1,3228 @@ +/* + SSSD + + IPA Helper routines - external users and groups with s2n plugin + + Copyright (C) Sumit Bose - 2011 + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "util/sss_nss.h" +#include "util/strtonum.h" +#include "util/crypto/sss_crypto.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/sdap_async_ad.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ad/ad_pac.h" +#include "db/sysdb.h" + +enum input_types { + INP_SID = 1, + INP_NAME, + INP_POSIX_UID, + INP_POSIX_GID, + INP_CERT, + INP_USERNAME, + INP_GROUPNAME +}; + +enum request_types { + REQ_SIMPLE = 1, + REQ_FULL, + REQ_FULL_WITH_MEMBERS +}; + +enum response_types { + RESP_SID = 1, + RESP_NAME, + RESP_USER, + RESP_GROUP, + RESP_USER_GROUPLIST, + RESP_GROUP_MEMBERS, + RESP_NAME_LIST +}; + +struct extdom_protocol_map_item { + int protocol; + const char *oid; +}; + +static struct extdom_protocol_map_item extdom_protocol_map[] = { + { EXTDOM_V2, EXOP_SID2NAME_V2_OID }, + { EXTDOM_V1, EXOP_SID2NAME_V1_OID }, + { EXTDOM_V0, EXOP_SID2NAME_OID }, + { EXTDOM_INVALID_VERSION, NULL } +}; + +static const char* extdom_protocol_to_oid(enum extdom_protocol protocol) +{ + int i; + + for (i = 0; extdom_protocol_map[i].protocol != EXTDOM_INVALID_VERSION; ++i) { + if (extdom_protocol_map[i].protocol == protocol) { + return extdom_protocol_map[i].oid; + } + } + + return NULL; +} + +static enum extdom_protocol extdom_oid_to_protocol(const char *oid) +{ + int i; + + if (oid == NULL) { + return EXTDOM_INVALID_VERSION; + } + + for (i = 0; extdom_protocol_map[i].protocol != EXTDOM_INVALID_VERSION; ++i) { + if (strcmp(extdom_protocol_map[i].oid, oid) == 0) { + return extdom_protocol_map[i].protocol; + } + } + + return EXTDOM_INVALID_VERSION; +} + +static enum extdom_protocol extdom_preferred_protocol(struct sdap_handle *sh) { + if (sdap_is_extension_supported(sh, EXOP_SID2NAME_V2_OID)) { + return EXTDOM_V2; + } + + if (sdap_is_extension_supported(sh, EXOP_SID2NAME_V1_OID)) { + return EXTDOM_V1; + } + + if (sdap_is_extension_supported(sh, EXOP_SID2NAME_OID)) { + return EXTDOM_V0; + } + + return EXTDOM_INVALID_VERSION; +} + +static const char *ipa_s2n_reqtype2str(enum request_types request_type) +{ + switch (request_type) { + case REQ_SIMPLE: + return "REQ_SIMPLE"; + case REQ_FULL: + return "REQ_FULL"; + case REQ_FULL_WITH_MEMBERS: + return "REQ_FULL_WITH_MEMBERS"; + default: + break; + } + + return "Unknown request type"; +} + +/* ==Sid2Name Extended Operation============================================= */ +struct ipa_s2n_exop_state { + struct sdap_handle *sh; + + struct sdap_op *op; + + char *retoid; + struct berval *retdata; +}; + +static void ipa_s2n_exop_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +static struct tevent_req *ipa_s2n_exop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + enum extdom_protocol protocol, + int timeout, + struct berval *bv, + const char *stat_info_in) +{ + struct tevent_req *req = NULL; + struct ipa_s2n_exop_state *state; + int ret; + int msgid; + char *stat_info; + + req = tevent_req_create(mem_ctx, &state, struct ipa_s2n_exop_state); + if (!req) return NULL; + + state->sh = sh; + state->retoid = NULL; + state->retdata = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Executing extended operation\n"); + + ret = ldap_extended_operation(state->sh->ldap, + extdom_protocol_to_oid(protocol), + bv, NULL, NULL, &msgid); + if (ret == -1 || msgid == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_extended_operation failed\n"); + ret = ERR_NETWORK_IO; + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "ldap_extended_operation sent, msgid = %d\n", + msgid); + + stat_info = talloc_asprintf(state, "server: [%s] %s", + sdap_get_server_peer_str_safe(state->sh), + stat_info_in != NULL ? stat_info_in + : "IPA EXOP"); + if (stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); + } + + ret = sdap_op_add(state, ev, state->sh, msgid, stat_info, + ipa_s2n_exop_done, req, timeout, &state->op); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); + ret = ERR_INTERNAL; + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ipa_s2n_exop_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct ipa_s2n_exop_state *state = tevent_req_data(req, + struct ipa_s2n_exop_state); + int ret; + char *errmsg = NULL; + char *retoid = NULL; + struct berval *retdata = NULL; + int result; + + if (error) { + tevent_req_error(req, error); + return; + } + + ret = ldap_parse_result(state->sh->ldap, reply->msg, + &result, NULL, &errmsg, NULL, + NULL, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed (%d)\n", + sdap_op_get_msgid(state->op)); + ret = ERR_NETWORK_IO; + goto done; + } + + DEBUG(((result == LDAP_SUCCESS) || (result == LDAP_NO_SUCH_OBJECT)) ? + SSSDBG_TRACE_FUNC : SSSDBG_OP_FAILURE, + "ldap_extended_operation result: %s(%d), %s.\n", + sss_ldap_err2string(result), result, errmsg); + + if (result != LDAP_SUCCESS) { + if (result == LDAP_NO_SUCH_OBJECT) { + ret = ENOENT; + } else { + DEBUG(SSSDBG_OP_FAILURE, "ldap_extended_operation failed, server " \ + "logs might contain more details.\n"); + ret = ERR_NETWORK_IO; + } + goto done; + } + + ret = ldap_parse_extended_result(state->sh->ldap, reply->msg, + &retoid, &retdata, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_extendend_result failed (%d)\n", + ret); + ret = ERR_NETWORK_IO; + goto done; + } + if (retdata == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing exop result data.\n"); + ret = EINVAL; + goto done; + } + + state->retoid = talloc_strdup(state, retoid); + if (state->retoid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + state->retdata = talloc(state, struct berval); + if (state->retdata == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + state->retdata->bv_len = retdata->bv_len; + state->retdata->bv_val = talloc_memdup(state->retdata, retdata->bv_val, + retdata->bv_len); + if (state->retdata->bv_val == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_memdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + ldap_memfree(errmsg); + ldap_memfree(retoid); + ber_bvfree(retdata); + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static int ipa_s2n_exop_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + char **retoid, struct berval **retdata) +{ + struct ipa_s2n_exop_state *state = tevent_req_data(req, + struct ipa_s2n_exop_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *retoid = talloc_steal(mem_ctx, state->retoid); + *retdata = talloc_steal(mem_ctx, state->retdata); + + return EOK; +} + +static errno_t talloc_ber_flatten(TALLOC_CTX *mem_ctx, BerElement *ber, + struct berval **_bv) +{ + int ret; + struct berval *bv = NULL; + struct berval *tbv = NULL; + + ret = ber_flatten(ber, &bv); + if (ret == -1) { + ret = EFAULT; + goto done; + } + + tbv = talloc_zero(mem_ctx, struct berval); + if (tbv == NULL) { + ret = ENOMEM; + goto done; + } + + tbv->bv_len = bv->bv_len; + tbv->bv_val = talloc_memdup(tbv, bv->bv_val, bv->bv_len); + if (tbv->bv_val == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + ber_bvfree(bv); + if (ret == EOK) { + *_bv = tbv; + } else { + talloc_free(tbv); + } + + return ret; +} + +/* The extended operation expect the following ASN.1 encoded request data: + * + * ExtdomRequestValue ::= SEQUENCE { + * inputType ENUMERATED { + * sid (1), + * name (2), + * posix uid (3), + * posix gid (3) + * }, + * requestType ENUMERATED { + * simple (1), + * full (2) + * full_with_members (3) + * }, + * data InputData + * } + * + * InputData ::= CHOICE { + * sid OCTET STRING, + * name NameDomainData + * uid PosixUid, + * gid PosixGid + * } + * + * NameDomainData ::= SEQUENCE { + * domain_name OCTET STRING, + * object_name OCTET STRING + * } + * + * PosixUid ::= SEQUENCE { + * domain_name OCTET STRING, + * uid INTEGER + * } + * + * PosixGid ::= SEQUENCE { + * domain_name OCTET STRING, + * gid INTEGER + * } + * + */ + +static errno_t s2n_encode_request(TALLOC_CTX *mem_ctx, + const char *domain_name, + int entry_type, + enum request_types request_type, + struct req_input *req_input, + enum extdom_protocol protocol, + struct berval **_bv, + char **stat_info) +{ + BerElement *ber = NULL; + int ret; + char *info = NULL; + + if (protocol == EXTDOM_INVALID_VERSION) { + return EINVAL; + } + + ber = ber_alloc_t( LBER_USE_DER ); + if (ber == NULL) { + return ENOMEM; + } + + switch (entry_type) { + case BE_REQ_USER: + case BE_REQ_USER_AND_GROUP: /* the extdom V0/V1 exop does not care if + the ID belongs to a user or a group */ + if (req_input->type == REQ_INP_NAME) { + ret = ber_printf(ber, "{ee{ss}}", + (protocol == EXTDOM_V2 + ? INP_USERNAME : INP_NAME), + request_type, + domain_name, + req_input->inp.name); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] domain: [%s] name: [%s]", + ipa_s2n_reqtype2str(request_type), + domain_name, req_input->inp.name); + } else if (req_input->type == REQ_INP_ID) { + ret = ber_printf(ber, "{ee{si}}", INP_POSIX_UID, request_type, + domain_name, + req_input->inp.id); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] domain: [%s] id: [%" PRIu32 "]", + ipa_s2n_reqtype2str(request_type), + domain_name, req_input->inp.id); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + break; + case BE_REQ_GROUP: + if (req_input->type == REQ_INP_NAME) { + ret = ber_printf(ber, "{ee{ss}}", + (protocol == EXTDOM_V2 + ? INP_GROUPNAME : INP_NAME), + request_type, + domain_name, + req_input->inp.name); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] domain: [%s] name: [%s]", + ipa_s2n_reqtype2str(request_type), + domain_name, req_input->inp.name); + } else if (req_input->type == REQ_INP_ID) { + ret = ber_printf(ber, "{ee{si}}", INP_POSIX_GID, request_type, + domain_name, + req_input->inp.id); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] domain: [%s] id: [%" PRIu32 "]", + ipa_s2n_reqtype2str(request_type), + domain_name, req_input->inp.id); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + break; + case BE_REQ_BY_SECID: + if (req_input->type == REQ_INP_SECID) { + ret = ber_printf(ber, "{ees}", INP_SID, request_type, + req_input->inp.secid); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] sid: [%s]", + ipa_s2n_reqtype2str(request_type), + req_input->inp.secid); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + break; + case BE_REQ_BY_CERT: + if (req_input->type == REQ_INP_CERT) { + ret = ber_printf(ber, "{ees}", INP_CERT, request_type, + req_input->inp.cert); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] cert: [%s]", + ipa_s2n_reqtype2str(request_type), + req_input->inp.cert); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + break; + default: + ret = EINVAL; + goto done; + } + if (ret == -1) { + ret = EFAULT; + goto done; + } + + ret = talloc_ber_flatten(mem_ctx, ber, _bv); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + ber_free(ber, 1); + if (ret != EOK || (*stat_info == NULL)) { + talloc_free(info); + } else { + *stat_info = info; + } + + return ret; +} + +/* If the extendend operation is successful it returns the following ASN.1 + * encoded response: + * + * ExtdomResponseValue ::= SEQUENCE { + * responseType ENUMERATED { + * sid (1), + * name (2), + * posix_user (3), + * posix_group (4), + * posix_user_grouplist (5), + * posix_group_members (6) + * }, + * data OutputData + * } + * + * OutputData ::= CHOICE { + * sid OCTET STRING, + * name NameDomainData, + * user PosixUser, + * group PosixGroup, + * usergrouplist PosixUserGrouplist, + * groupmembers PosixGroupMembers + * + * } + * + * NameDomainData ::= SEQUENCE { + * domain_name OCTET STRING, + * object_name OCTET STRING + * } + * + * PosixUser ::= SEQUENCE { + * domain_name OCTET STRING, + * user_name OCTET STRING, + * uid INTEGER + * gid INTEGER + * } + * + * PosixGroup ::= SEQUENCE { + * domain_name OCTET STRING, + * group_name OCTET STRING, + * gid INTEGER + * } + * + * PosixUserGrouplist ::= SEQUENCE { + * domain_name OCTET STRING, + * user_name OCTET STRING, + * uid INTEGER, + * gid INTEGER, + * gecos OCTET STRING, + * home_directory OCTET STRING, + * shell OCTET STRING, + * grouplist GroupNameList + * } + * + * GroupNameList ::= SEQUENCE OF OCTET STRING + * + * PosixGroupMembers ::= SEQUENCE { + * domain_name OCTET STRING, + * group_name OCTET STRING, + * gid INTEGER, + * members GroupMemberList + * } + * + * GroupMemberList ::= SEQUENCE OF OCTET STRING + */ + +struct name_list { + char *domain_name; + char *name; +}; + +struct resp_attrs { + enum response_types response_type; + char *domain_name; + union { + struct passwd user; + struct group group; + char *sid_str; + char *name; + } a; + size_t ngroups; + char **groups; + struct sysdb_attrs *sysdb_attrs; + char **name_list; +}; + +static errno_t get_extra_attrs(BerElement *ber, struct resp_attrs *resp_attrs) +{ + ber_tag_t tag; + ber_len_t ber_len; + char *ber_cookie; + char *name; + struct berval **values; + struct ldb_val v; + int ret; + size_t c; + + if (resp_attrs->sysdb_attrs == NULL) { + resp_attrs->sysdb_attrs = sysdb_new_attrs(resp_attrs); + if (resp_attrs->sysdb_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + return ENOMEM; + } + } + + DEBUG(SSSDBG_TRACE_ALL, "Found new sequence.\n"); + for (tag = ber_first_element(ber, &ber_len, &ber_cookie); + tag != LBER_DEFAULT; + tag = ber_next_element(ber, &ber_len, ber_cookie)) { + + tag = ber_scanf(ber, "{a{V}}", &name, &values); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + return EINVAL; + } + DEBUG(SSSDBG_TRACE_ALL, "Extra attribute [%s].\n", name); + + for (c = 0; values[c] != NULL; c++) { + + if (strcmp(name, SYSDB_USER_CERT) == 0) { + if (values[c]->bv_val[values[c]->bv_len] != '\0') { + DEBUG(SSSDBG_OP_FAILURE, + "base64 encoded certificate not 0-terminated.\n"); + return EINVAL; + } + + v.data = sss_base64_decode(NULL, values[c]->bv_val, &v.length); + if (v.data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + return EINVAL; + } + } else { + v.data = (uint8_t *)values[c]->bv_val; + v.length = values[c]->bv_len; + } + + ret = sysdb_attrs_add_val_safe(resp_attrs->sysdb_attrs, name, &v); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_val_safe failed.\n"); + ldap_memfree(name); + ber_bvecfree(values); + return ret; + } + } + + ldap_memfree(name); + ber_bvecfree(values); + } + + return EOK; +} + +static errno_t add_v1_user_data(struct sss_domain_info *dom, + BerElement *ber, + struct resp_attrs *attrs) +{ + ber_tag_t tag; + ber_len_t ber_len; + int ret; + char *gecos = NULL; + char *homedir = NULL; + char *name = NULL; + char *domain = NULL; + char *shell = NULL; + char **list = NULL; + size_t c, gc; + struct sss_domain_info *parent_domain; + struct sss_domain_info *obj_domain; + + tag = ber_scanf(ber, "aaa", &gecos, &homedir, &shell); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + if (gecos == NULL || *gecos == '\0') { + attrs->a.user.pw_gecos = NULL; + } else { + attrs->a.user.pw_gecos = talloc_strdup(attrs, gecos); + if (attrs->a.user.pw_gecos == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (homedir == NULL || *homedir == '\0') { + attrs->a.user.pw_dir = NULL; + } else { + attrs->a.user.pw_dir = talloc_strdup(attrs, homedir); + if (attrs->a.user.pw_dir == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (shell == NULL || *shell == '\0') { + attrs->a.user.pw_shell = NULL; + } else { + attrs->a.user.pw_shell = talloc_strdup(attrs, shell); + if (attrs->a.user.pw_shell == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + + tag = ber_scanf(ber, "{v}", &list); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + for (attrs->ngroups = 0; list[attrs->ngroups] != NULL; + attrs->ngroups++); + + if (attrs->ngroups > 0) { + attrs->groups = talloc_zero_array(attrs, char *, attrs->ngroups + 1); + if (attrs->groups == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + attrs->ngroups = 0; + ret = ENOMEM; + goto done; + } + + parent_domain = get_domains_head(dom); + + for (c = 0, gc = 0; c < attrs->ngroups; c++) { + ret = sss_parse_name(attrs, dom->names, list[c], + &domain, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot parse member %s\n", list[c]); + continue; + } + + if (domain != NULL) { + obj_domain = find_domain_by_name_ex(parent_domain, domain, true, SSS_GND_ALL_DOMAINS); + if (obj_domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name_ex failed.\n"); + attrs->ngroups = gc; + ret = ENOMEM; + goto done; + } else if (sss_domain_get_state(obj_domain) == DOM_DISABLED) { + /* skipping objects from disabled domains */ + DEBUG(SSSDBG_TRACE_ALL, + "Skipping object [%s] from disabled domain.\n", + list[c]); + continue; + } + } else { + obj_domain = parent_domain; + } + + attrs->groups[gc] = sss_create_internal_fqname(attrs->groups, + name, obj_domain->name); + if (attrs->groups[gc] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed.\n"); + attrs->ngroups = gc; + ret = ENOMEM; + goto done; + } + gc++; + } + attrs->ngroups = gc; + } + + tag = ber_peek_tag(ber, &ber_len); + DEBUG(SSSDBG_TRACE_ALL, "BER tag is [%d]\n", (int) tag); + if (tag == LBER_SEQUENCE) { + ret = get_extra_attrs(ber, attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_extra_attrs failed.\n"); + goto done; + } + } + + + ret = EOK; + +done: + ber_memfree(gecos); + ber_memfree(homedir); + ber_memfree(shell); + ber_memvfree((void **) list); + + return ret; +} + +static errno_t add_v1_group_data(BerElement *ber, + struct sss_domain_info *dom, + struct resp_attrs *attrs) +{ + ber_tag_t tag; + ber_len_t ber_len; + int ret; + char **list = NULL; + size_t c, mc; + char *name = NULL; + char *domain = NULL; + + tag = ber_scanf(ber, "{v}", &list); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + if (list != NULL) { + for (attrs->ngroups = 0; list[attrs->ngroups] != NULL; + attrs->ngroups++); + + if (attrs->ngroups > 0) { + attrs->a.group.gr_mem = talloc_zero_array(attrs, char *, + attrs->ngroups + 1); + if (attrs->a.group.gr_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + + for (c = 0, mc=0; c < attrs->ngroups; c++) { + ret = sss_parse_name(attrs, dom->names, list[c], + &domain, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot parse member %s\n", list[c]); + continue; + } + + if (domain == NULL) { + domain = dom->name; + } + + attrs->a.group.gr_mem[mc] = + sss_create_internal_fqname(attrs->a.group.gr_mem, + name, domain); + if (attrs->a.group.gr_mem[mc] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + mc++; + } + } + } else { + attrs->a.group.gr_mem = talloc_zero_array(attrs, char *, 1); + if (attrs->a.group.gr_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + } + + tag = ber_peek_tag(ber, &ber_len); + DEBUG(SSSDBG_TRACE_ALL, "BER tag is [%d]\n", (int) tag); + if (tag == LBER_SEQUENCE) { + ret = get_extra_attrs(ber, attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_extra_attrs failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + ber_memvfree((void **) list); + + return ret; +} + +static char *s2n_response_to_attrs_fqname(TALLOC_CTX *mem_ctx, + enum extdom_protocol protocol, + const char *domain_name, + const char *name) +{ + char *lc_name; + char *out_name; + + if (protocol == EXTDOM_V0) { + /* Compatibility with older IPA servers that may use winbind instead + * of SSSD's server mode. + * + * Winbind is not consistent with the case of the returned user + * name. In general all names should be lower case but there are + * bug in some version of winbind which might lead to upper case + * letters in the name. To be on the safe side we explicitly + * lowercase the name. + */ + + lc_name = sss_tc_utf8_str_tolower(NULL, name); + if (lc_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + return NULL; + } + + out_name = sss_create_internal_fqname(mem_ctx, lc_name, domain_name); + talloc_free(lc_name); + } else { + /* Keep the original casing to support case_sensitive=Preserving */ + out_name = sss_create_internal_fqname(mem_ctx, name, domain_name); + } + + if (out_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + return NULL; + } + + return out_name; +} + +static errno_t ipa_s2n_save_objects(struct sss_domain_info *dom, + struct req_input *req_input, + struct resp_attrs *attrs, + struct resp_attrs *simple_attrs, + const char *view_name, + struct sysdb_attrs *override_attrs, + struct sysdb_attrs *mapped_attrs, + bool update_initgr_timeout); + +static errno_t s2n_response_to_attrs(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + char *retoid, + struct berval *retdata, + struct resp_attrs **resp_attrs) +{ + BerElement *ber = NULL; + ber_tag_t tag; + int ret; + enum response_types type; + char *domain_name = NULL; + char *name = NULL; + uid_t uid; + gid_t gid; + struct resp_attrs *attrs = NULL; + char *sid_str; + enum extdom_protocol protocol; + char **name_list = NULL; + ber_len_t ber_len; + char *fq_name = NULL; + struct sss_domain_info *root_domain = NULL; + + if (retoid == NULL || retdata == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing OID or data.\n"); + return EINVAL; + } + + protocol = extdom_oid_to_protocol(retoid); + if (protocol == EXTDOM_INVALID_VERSION) { + DEBUG(SSSDBG_OP_FAILURE, + "Result has wrong OID, expected [%s], [%s] or [%s], got [%s].\n", + EXOP_SID2NAME_OID, EXOP_SID2NAME_V1_OID, + EXOP_SID2NAME_V2_OID, retoid); + return EINVAL; + } + + ber = ber_init(retdata); + if (ber == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ber_init failed.\n"); + return EINVAL; + } + + tag = ber_scanf(ber, "{e", &type); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + attrs = talloc_zero(mem_ctx, struct resp_attrs); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + switch (type) { + case RESP_USER: + case RESP_USER_GROUPLIST: + tag = ber_scanf(ber, "{aaii", &domain_name, &name, &uid, &gid); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + attrs->a.user.pw_name = s2n_response_to_attrs_fqname(attrs, + protocol, + domain_name, + name); + if (attrs->a.user.pw_name == NULL) { + ret = ENOMEM; + goto done; + } + + attrs->a.user.pw_uid = uid; + attrs->a.user.pw_gid = gid; + + if (protocol > EXTDOM_V0 && type == RESP_USER_GROUPLIST) { + ret = add_v1_user_data(dom, ber, attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_v1_user_data failed.\n"); + goto done; + } + } + + tag = ber_scanf(ber, "}}"); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + break; + case RESP_GROUP: + case RESP_GROUP_MEMBERS: + tag = ber_scanf(ber, "{aai", &domain_name, &name, &gid); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + attrs->a.group.gr_name = s2n_response_to_attrs_fqname(attrs, + protocol, + domain_name, + name); + if (attrs->a.group.gr_name == NULL) { + ret = ENOMEM; + goto done; + } + + attrs->a.group.gr_gid = gid; + + if (protocol > EXTDOM_V0 && type == RESP_GROUP_MEMBERS) { + ret = add_v1_group_data(ber, dom, attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_v1_group_data failed.\n"); + goto done; + } + } + + tag = ber_scanf(ber, "}}"); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + break; + case RESP_SID: + tag = ber_scanf(ber, "a}", &sid_str); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + attrs->a.sid_str = talloc_strdup(attrs, sid_str); + if (attrs->a.sid_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + break; + case RESP_NAME: + tag = ber_scanf(ber, "{aa}", &domain_name, &name); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + attrs->a.name = sss_tc_utf8_str_tolower(attrs, name); + if (attrs->a.name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_tc_utf8_str_tolower failed.\n"); + ret = ENOMEM; + goto done; + } + break; + case RESP_NAME_LIST: + tag = ber_scanf(ber, "{"); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + root_domain = get_domains_head(dom); + + while (ber_peek_tag(ber, &ber_len) == LBER_SEQUENCE) { + tag = ber_scanf(ber, "{aa}", &domain_name, &name); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + fq_name = sss_create_internal_fqname(attrs, name, domain_name); + if (fq_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_create_internal_fqname failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "[%s][%s][%s].\n", domain_name, name, + fq_name); + + if (strcasecmp(root_domain->name, domain_name) != 0) { + ret = add_string_to_list(attrs, fq_name, &name_list); + } else { + DEBUG(SSSDBG_TRACE_ALL, + "[%s] from root domain, skipping.\n", fq_name); + ret = EOK; /* Free resources and continue in the loop */ + } + ber_memfree(domain_name); + ber_memfree(name); + talloc_free(fq_name); + domain_name = NULL; + name = NULL; + fq_name = NULL; + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_to_name_list failed.\n"); + goto done; + } + } + + tag = ber_scanf(ber, "}}"); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + attrs->name_list = name_list; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unexpected response type [%d].\n", + type); + ret = EINVAL; + goto done; + } + + attrs->response_type = type; + if (type != RESP_SID && type != RESP_NAME_LIST) { + attrs->domain_name = talloc_strdup(attrs, domain_name); + if (attrs->domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + + ret = EOK; + +done: + ber_memfree(domain_name); + ber_memfree(name); + talloc_free(fq_name); + ber_free(ber, 1); + + if (ret == EOK) { + *resp_attrs = attrs; + } else { + talloc_free(attrs); + } + + return ret; +} + +static const char *ipa_s2n_reqinp2str(TALLOC_CTX *mem_ctx, + struct req_input *req_input) +{ + const char *str = NULL; + + switch (req_input->type) { + case REQ_INP_NAME: + str = talloc_strdup(mem_ctx, req_input->inp.name); + break; + case REQ_INP_SECID: + str = talloc_strdup(mem_ctx, req_input->inp.secid); + break; + case REQ_INP_CERT: + str = talloc_strdup(mem_ctx, req_input->inp.cert); + break; + case REQ_INP_ID: + str = talloc_asprintf(mem_ctx, "%u", req_input->inp.id); + break; + } + + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + } + + return str; +} + +struct ipa_s2n_get_list_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sss_domain_info *dom; + struct sdap_handle *sh; + enum extdom_protocol protocol; + struct req_input req_input; + char **list; + size_t list_idx; + int exop_timeout; + int entry_type; + enum request_types request_type; + struct resp_attrs *attrs; + struct sss_domain_info *obj_domain; + struct sysdb_attrs *override_attrs; + struct sysdb_attrs *mapped_attrs; +}; + +static errno_t ipa_s2n_get_list_step(struct tevent_req *req); +static void ipa_s2n_get_list_get_override_done(struct tevent_req *subreq); +static void ipa_s2n_get_list_next(struct tevent_req *subreq); +static void ipa_s2n_get_list_ipa_next(struct tevent_req *subreq); +static errno_t ipa_s2n_get_list_save_step(struct tevent_req *req); + +static struct tevent_req *ipa_s2n_get_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *dom, + struct sdap_handle *sh, + int exop_timeout, + int entry_type, + enum request_types request_type, + enum req_input_type list_type, + char **list, + struct sysdb_attrs *mapped_attrs) +{ + int ret; + struct ipa_s2n_get_list_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct ipa_s2n_get_list_state); + if (req == NULL) { + return NULL; + } + + if ((entry_type == BE_REQ_BY_SECID && list_type != REQ_INP_SECID) + || (entry_type != BE_REQ_BY_SECID && list_type == REQ_INP_SECID)) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid parameter combination [%d][%d].\n", + request_type, list_type); + ret = EINVAL; + goto done; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->dom = dom; + state->sh = sh; + state->protocol = extdom_preferred_protocol(sh); + state->list = list; + state->list_idx = 0; + state->req_input.type = list_type; + state->req_input.inp.name = NULL; + state->exop_timeout = exop_timeout; + state->entry_type = entry_type; + state->request_type = request_type; + state->attrs = NULL; + state->override_attrs = NULL; + state->mapped_attrs = mapped_attrs; + + ret = ipa_s2n_get_list_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_step failed.\n"); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t ipa_s2n_get_list_step(struct tevent_req *req) +{ + int ret; + struct ipa_s2n_get_list_state *state = tevent_req_data(req, + struct ipa_s2n_get_list_state); + struct berval *bv_req; + struct tevent_req *subreq; + struct sss_domain_info *parent_domain; + char *short_name = NULL; + char *domain_name = NULL; + uint32_t id; + char *endptr; + struct dp_id_data *ar; + char *stat_info = NULL; + + parent_domain = get_domains_head(state->dom); + switch (state->req_input.type) { + case REQ_INP_NAME: + + ret = sss_parse_name(state, state->dom->names, state->list[state->list_idx], + &domain_name, &short_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse name '%s' [%d]: %s\n", + state->list[state->list_idx], + ret, sss_strerror(ret)); + return ret; + } + + if (domain_name) { + state->obj_domain = find_domain_by_name(parent_domain, + domain_name, true); + if (state->obj_domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); + return ENOMEM; + } + } else { + state->obj_domain = parent_domain; + } + + state->req_input.inp.name = short_name; + + if (strcmp(state->obj_domain->name, + state->ipa_ctx->sdap_id_ctx->be->domain->name) == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Looking up IPA object [%s] from LDAP.\n", + state->list[state->list_idx]); + ret = get_dp_id_data_for_user_name(state, + state->list[state->list_idx], + state->obj_domain->name, + &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create lookup date for IPA object [%s].\n", + state->list[state->list_idx]); + return ret; + } + ar->entry_type = state->entry_type; + + subreq = ipa_id_get_account_info_send(state, state->ev, + state->ipa_ctx, ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_id_get_account_info_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_ipa_next, req); + + return EOK; + } + + break; + case REQ_INP_ID: + id = strtouint32(state->list[state->list_idx], &endptr, 10); + if (errno != 0 || *endptr != '\0' + || (state->list[state->list_idx] == endptr)) { + DEBUG(SSSDBG_OP_FAILURE, "strtouint32 failed.\n"); + return EINVAL; + } + state->req_input.inp.id = id; + state->obj_domain = state->dom; + + break; + case REQ_INP_SECID: + state->req_input.inp.secid = state->list[state->list_idx]; + state->obj_domain = find_domain_by_sid(parent_domain, + state->req_input.inp.secid); + if (state->obj_domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "find_domain_by_sid failed for SID [%s].\n", + state->req_input.inp.secid); + return EINVAL; + } + + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + state->req_input.type); + return EINVAL; + } + + ret = s2n_encode_request(state, state->obj_domain->name, state->entry_type, + state->request_type, &state->req_input, + state->protocol, &bv_req, &stat_info); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n_encode_request failed.\n"); + return ret; + } + + if (state->request_type == REQ_FULL_WITH_MEMBERS && state->protocol == EXTDOM_V0) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_exop failed, protocol > V0 needed for this request.\n"); + return EINVAL; + } + + if (state->req_input.type == REQ_INP_NAME + && state->req_input.inp.name != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Sending request_type: [%s] for object [%s].\n", + ipa_s2n_reqtype2str(state->request_type), + state->list[state->list_idx]); + } + + subreq = ipa_s2n_exop_send(state, state->ev, state->sh, state->protocol, + state->exop_timeout, bv_req, stat_info); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_exop_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_next, req); + + return EOK; +} + +static void ipa_s2n_get_list_next(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_list_state *state = tevent_req_data(req, + struct ipa_s2n_get_list_state); + char *retoid = NULL; + struct berval *retdata = NULL; + const char *sid_str; + struct dp_id_data *ar; + + ret = ipa_s2n_exop_recv(subreq, state, &retoid, &retdata); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n exop request failed.\n"); + goto fail; + } + + talloc_zfree(state->attrs); + ret = s2n_response_to_attrs(state, state->dom, retoid, retdata, + &state->attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n_response_to_attrs failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Received [%s] attributes from IPA server.\n", + state->attrs->a.name); + + if (is_default_view(state->ipa_ctx->view_name)) { + ret = ipa_s2n_get_list_save_step(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_save_step failed.\n"); + goto fail; + } + + return; + } + + ret = sysdb_attrs_get_string(state->attrs->sysdb_attrs, SYSDB_SID_STR, + &sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Object [%s] has no SID, please check the " + "ipaNTSecurityIdentifier attribute on the server-side", + state->attrs->a.name); + goto fail; + } + + ret = get_dp_id_data_for_sid(state, sid_str, state->obj_domain->name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto fail; + } + + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM), + state->ipa_ctx->view_name, + ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_get_override_done, req); + + return; + +fail: + tevent_req_error(req,ret); + return; +} + +static void ipa_s2n_get_list_ipa_next(struct tevent_req *subreq) +{ + int ret; + int dp_error; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_list_state *state = tevent_req_data(req, + struct ipa_s2n_get_list_state); + + ret = ipa_id_get_account_info_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_id_get_account_info failed: %d %d\n", ret, + dp_error); + goto done; + } + + state->list_idx++; + if (state->list[state->list_idx] == NULL) { + tevent_req_done(req); + return; + } + + ret = ipa_s2n_get_list_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_step failed.\n"); + goto done; + } + + return; + +done: + tevent_req_error(req,ret); + return; +} + +static void ipa_s2n_get_list_get_override_done(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_list_state *state = tevent_req_data(req, + struct ipa_s2n_get_list_state); + + ret = ipa_get_ad_override_recv(subreq, NULL, state, &state->override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + goto fail; + } + + ret = ipa_s2n_get_list_save_step(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_save_step failed.\n"); + goto fail; + } + + return; + +fail: + tevent_req_error(req,ret); + return; +} + +static errno_t ipa_s2n_get_list_save_step(struct tevent_req *req) +{ + int ret; + struct ipa_s2n_get_list_state *state = tevent_req_data(req, + struct ipa_s2n_get_list_state); + + ret = ipa_s2n_save_objects(state->dom, &state->req_input, state->attrs, + NULL, state->ipa_ctx->view_name, + state->override_attrs, state->mapped_attrs, + false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_save_objects failed.\n"); + return ret; + } + + state->list_idx++; + if (state->list[state->list_idx] == NULL) { + return EOK; + } + + ret = ipa_s2n_get_list_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_step failed.\n"); + return ret; + } + + return EAGAIN; +} + +static int ipa_s2n_get_list_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_s2n_get_user_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sdap_options *opts; + struct sss_domain_info *dom; + struct sdap_handle *sh; + enum extdom_protocol protocol; + struct req_input *req_input; + int entry_type; + enum request_types request_type; + struct resp_attrs *attrs; + struct resp_attrs *simple_attrs; + struct sysdb_attrs *override_attrs; + struct sysdb_attrs *mapped_attrs; + int exop_timeout; +}; + +static void ipa_s2n_get_user_done(struct tevent_req *subreq); + +struct tevent_req *ipa_s2n_get_acct_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *override_attrs, + struct sdap_handle *sh, + int entry_type, + struct req_input *req_input) +{ + struct ipa_s2n_get_user_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct berval *bv_req = NULL; + const char *input; + int ret = EFAULT; + char *stat_info = NULL; + + req = tevent_req_create(mem_ctx, &state, struct ipa_s2n_get_user_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->protocol = extdom_preferred_protocol(sh); + state->req_input = req_input; + state->entry_type = entry_type; + state->attrs = NULL; + state->simple_attrs = NULL; + state->exop_timeout = dp_opt_get_int(opts->basic, SDAP_SEARCH_TIMEOUT); + state->override_attrs = override_attrs; + + if (state->protocol == EXTDOM_V1 || state->protocol == EXTDOM_V2) { + state->request_type = REQ_FULL_WITH_MEMBERS; + } else if (state->protocol == EXTDOM_V0) { + state->request_type = REQ_FULL; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Extdom not supported on the server, " + "cannot resolve objects from trusted domains.\n"); + ret = EIO; + goto fail; + } + + if (entry_type == BE_REQ_BY_CERT) { + /* Only REQ_SIMPLE is supported for BE_REQ_BY_CERT */ + state->request_type = REQ_SIMPLE; + } + + ret = s2n_encode_request(state, dom->name, entry_type, state->request_type, + req_input, state->protocol, &bv_req, &stat_info); + if (ret != EOK) { + goto fail; + } + + input = ipa_s2n_reqinp2str(state, req_input); + DEBUG(SSSDBG_TRACE_FUNC, + "Sending request_type: [%s] for trust user [%s] to IPA server\n", + ipa_s2n_reqtype2str(state->request_type), + input); + talloc_zfree(input); + + subreq = ipa_s2n_exop_send(state, state->ev, state->sh, state->protocol, + state->exop_timeout, bv_req, stat_info); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_exop_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_s2n_get_user_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static errno_t process_members(struct sss_domain_info *domain, + bool is_default_view, + struct sysdb_attrs *group_attrs, + char **members, + TALLOC_CTX *mem_ctx, char ***_missing_members) +{ + int ret; + size_t c; + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + const char *dn_str; + struct sss_domain_info *obj_domain; + struct sss_domain_info *parent_domain; + char **missing_members = NULL; + size_t miss_count = 0; + const char *attrs[] = {SYSDB_NAME, SYSDB_OVERRIDE_DN, NULL}; + + if (members == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, "No members\n"); + if (_missing_members != NULL) { + *_missing_members = NULL; + } + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (_missing_members != NULL && mem_ctx != NULL) { + /* count members */ + for (c = 0; members[c] != NULL; c++); + missing_members = talloc_zero_array(tmp_ctx, char *, c + 1); + if (missing_members == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array_zero failed.\n"); + ret = ENOMEM; + goto done; + } + } + + parent_domain = get_domains_head(domain); + + for (c = 0; members[c] != NULL; c++) { + obj_domain = find_domain_by_object_name_ex(parent_domain, members[c], + false, SSS_GND_ALL_DOMAINS); + if (obj_domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_object_name failed.\n"); + ret = ENOMEM; + goto done; + } else if (sss_domain_get_state(obj_domain) == DOM_DISABLED) { + /* skip members from disabled domains */ + continue; + } + + ret = sysdb_search_user_by_name(tmp_ctx, obj_domain, members[c], attrs, + &msg); + if (ret == EOK || ret == ENOENT) { + if (ret == ENOENT + || (!is_default_view + && ldb_msg_find_attr_as_string(msg, SYSDB_OVERRIDE_DN, + NULL) == NULL)) { + /* only add ghost if the member is really missing */ + if (group_attrs != NULL && ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "Adding ghost member [%s]\n", + members[c]); + + /* There were cases where the server returned the same user + * multiple times */ + ret = sysdb_attrs_add_string_safe(group_attrs, SYSDB_GHOST, + members[c]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto done; + } + } + + if (missing_members != NULL) { + missing_members[miss_count] = talloc_strdup(missing_members, + members[c]); + if (missing_members[miss_count] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + miss_count++; + } + } else { + if (group_attrs != NULL) { + dn_str = ldb_dn_get_linearized(msg->dn); + if (dn_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_get_linearized failed.\n"); + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Adding member [%s][%s]\n", + members[c], dn_str); + + ret = sysdb_attrs_add_string_safe(group_attrs, SYSDB_MEMBER, + dn_str); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string_safe failed.\n"); + goto done; + } + } + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_name failed.\n"); + goto done; + } + } + + if (_missing_members != NULL) { + if (miss_count == 0) { + *_missing_members = NULL; + } else { + if (mem_ctx != NULL) { + *_missing_members = talloc_steal(mem_ctx, missing_members); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing memory context for missing members list.\n"); + ret = EINVAL; + goto done; + } + } + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t get_group_dn_list(TALLOC_CTX *mem_ctx, + bool is_default_view, + struct sss_domain_info *dom, + size_t ngroups, char **groups, + struct ldb_dn ***_dn_list, + char ***_missing_groups) +{ + int ret; + size_t c; + TALLOC_CTX *tmp_ctx; + struct ldb_dn **dn_list = NULL; + char **missing_groups = NULL; + struct ldb_message *msg = NULL; + size_t n_dns = 0; + size_t n_missing = 0; + struct sss_domain_info *obj_domain; + struct sss_domain_info *parent_domain; + const char *attrs[] = {SYSDB_NAME, SYSDB_OVERRIDE_DN, NULL}; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + dn_list = talloc_zero_array(tmp_ctx, struct ldb_dn *, ngroups + 1); + missing_groups = talloc_zero_array(tmp_ctx, char *, ngroups + 1); + if (dn_list == NULL || missing_groups == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + parent_domain = (dom->parent == NULL) ? dom : dom->parent; + + for (c = 0; c < ngroups; c++) { + obj_domain = find_domain_by_object_name(parent_domain, groups[c]); + if (obj_domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_object_name failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_group_by_name(tmp_ctx, obj_domain, groups[c], attrs, + &msg); + if (ret == EOK || ret == ENOENT) { + if (ret == ENOENT + || (!is_default_view + && ldb_msg_find_attr_as_string(msg, SYSDB_OVERRIDE_DN, + NULL) == NULL)) { + missing_groups[n_missing] = talloc_strdup(missing_groups, + groups[c]); + if (missing_groups[n_missing] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + n_missing++; + + } else { + dn_list[n_dns] = ldb_dn_copy(dn_list, msg->dn); + if (dn_list[n_dns] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_copy failed.\n"); + ret = ENOMEM; + goto done; + } + n_dns++; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_group_by_name failed.\n"); + goto done; + } + } + + if (n_missing != 0) { + *_missing_groups = talloc_steal(mem_ctx, missing_groups); + } else { + *_missing_groups = NULL; + } + + if (n_dns != 0) { + *_dn_list = talloc_steal(mem_ctx, dn_list); + } else { + *dn_list = NULL; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t s2n_remove_missing_object(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + int entry_type, + struct req_input *req_input) +{ + int ret; + bool name_is_upn = false; + char *id_str = NULL; + char *fq_name = NULL; + + if (req_input->type == REQ_INP_ID) { + id_str = talloc_asprintf(mem_ctx, "%"SPRIuid, req_input->inp.id); + if (id_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + } + + switch (entry_type) { + case BE_REQ_USER_AND_GROUP: + case BE_REQ_USER: + if (req_input->type == REQ_INP_NAME) { + name_is_upn = strchr(req_input->inp.name, '@') == NULL ? false + : true; + /* Expand to fully-qualified internal name */ + if (!name_is_upn) { + fq_name = sss_create_internal_fqname(mem_ctx, + req_input->inp.name, + domain->name); + if (fq_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_create_internal_fqname failed.\n"); + ret = ENOMEM; + goto done; + } + } + ret = users_get_handle_no_user(mem_ctx, domain, BE_FILTER_NAME, + fq_name != NULL ? fq_name + : req_input->inp.name, + name_is_upn); + } else if (req_input->type == REQ_INP_ID) { + ret = users_get_handle_no_user(mem_ctx, domain, BE_FILTER_IDNUM, + id_str, false); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + if (ret != EOK || entry_type == BE_REQ_USER) { + break; + } + /* Fallthough if BE_REQ_USER_AND_GROUP */ + SSS_ATTRIBUTE_FALLTHROUGH; + case BE_REQ_GROUP: + if (req_input->type == REQ_INP_NAME) { + /* Expand to fully-qualified internal name */ + fq_name = sss_create_internal_fqname(mem_ctx, + req_input->inp.name, + domain->name); + if (fq_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_create_internal_fqname failed.\n"); + ret = ENOMEM; + goto done; + } + ret = groups_get_handle_no_group(mem_ctx, domain, BE_FILTER_NAME, + fq_name); + } else if (req_input->type == REQ_INP_ID) { + ret = groups_get_handle_no_group(mem_ctx, domain,BE_FILTER_IDNUM, + id_str); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + break; + case BE_REQ_BY_SECID: + ret = EOK; + break; + case BE_REQ_BY_CERT: + ret = EOK; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unexpected entry type [%d].\n", entry_type); + ret = EINVAL; + } + +done: + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Error while trying to remove user or group from cache.\n"); + } + + talloc_free(id_str); + talloc_free(fq_name); + return ret; +} + +static void ipa_s2n_get_list_done(struct tevent_req *subreq); +static void ipa_s2n_get_user_get_override_done(struct tevent_req *subreq); +static void ipa_s2n_get_user_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_user_state *state = tevent_req_data(req, + struct ipa_s2n_get_user_state); + int ret; + char *retoid = NULL; + struct berval *retdata = NULL; + struct resp_attrs *attrs = NULL; + struct berval *bv_req = NULL; + char **missing_list = NULL; + struct ldb_dn **group_dn_list = NULL; + const char *sid_str; + struct dp_id_data *ar; + char *stat_info = NULL; + + ret = ipa_s2n_exop_recv(subreq, state, &retoid, &retdata); + talloc_zfree(subreq); + if (ret != EOK) { + if (ret == ENOENT) { + ret = s2n_remove_missing_object(state, state->dom, + state->entry_type, + state->req_input); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "s2n_remove_missing_object failed [%d].\n", ret); + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "s2n exop request failed.\n"); + if (state->req_input->type == REQ_INP_CERT) { + DEBUG(SSSDBG_OP_FAILURE, + "Maybe the server does not support lookups by " + "certificates.\n"); + } + } + goto done; + } + + switch (state->request_type) { + case REQ_FULL_WITH_MEMBERS: + case REQ_FULL: + ret = s2n_response_to_attrs(state, state->dom, retoid, retdata, + &attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n_response_to_attrs failed.\n"); + goto done; + } + + if (!(strcasecmp(state->dom->name, attrs->domain_name) == 0 || + (state->dom->flat_name != NULL && + strcasecmp(state->dom->flat_name, attrs->domain_name) == 0))) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected domain name returned, " + "expected [%s] or [%s], got [%s].\n", + state->dom->name, + state->dom->flat_name == NULL ? "" : + state->dom->flat_name, + attrs->domain_name); + ret = EINVAL; + goto done; + } + + state->attrs = attrs; + + if (attrs->response_type == RESP_USER_GROUPLIST) { + + DEBUG(SSSDBG_TRACE_FUNC, "Received [%zu] groups in group list " + "from IPA Server\n", attrs->ngroups); + + for (size_t c = 0; c < attrs->ngroups; c++) { + DEBUG(SSSDBG_TRACE_FUNC, "[%s].\n", attrs->groups[c]); + } + + + ret = get_group_dn_list(state, + is_default_view(state->ipa_ctx->view_name), + state->dom, + attrs->ngroups, attrs->groups, + &group_dn_list, &missing_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_group_dn_list failed.\n"); + goto done; + } + + if (missing_list != NULL) { + subreq = ipa_s2n_get_list_send(state, state->ev, + state->ipa_ctx, state->dom, + state->sh, state->exop_timeout, + BE_REQ_GROUP, + REQ_FULL_WITH_MEMBERS, + REQ_INP_NAME, + missing_list, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_s2n_get_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_done, + req); + + return; + } + break; + } else if (attrs->response_type == RESP_GROUP_MEMBERS) { + ret = process_members(state->dom, + is_default_view(state->ipa_ctx->view_name), + NULL, attrs->a.group.gr_mem, state, + &missing_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "process_members failed.\n"); + goto done; + } + + if (missing_list != NULL) { + subreq = ipa_s2n_get_list_send(state, state->ev, + state->ipa_ctx, state->dom, + state->sh, state->exop_timeout, + BE_REQ_USER, + REQ_FULL_WITH_MEMBERS, + REQ_INP_NAME, + missing_list, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_s2n_get_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_done, + req); + + return; + } + break; + } + + if (state->req_input->type == REQ_INP_SECID) { + /* We already know the SID, we do not have to read it. */ + break; + } + + state->request_type = REQ_SIMPLE; + + ret = s2n_encode_request(state, state->dom->name, state->entry_type, + state->request_type, state->req_input, + state->protocol, + &bv_req, &stat_info); + if (ret != EOK) { + goto done; + } + + subreq = ipa_s2n_exop_send(state, state->ev, state->sh, false, + state->exop_timeout, bv_req, stat_info); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_exop_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_s2n_get_user_done, req); + + return; + + case REQ_SIMPLE: + ret = s2n_response_to_attrs(state, state->dom, retoid, retdata, + &state->simple_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n_response_to_attrs failed.\n"); + goto done; + } + + if (state->simple_attrs->response_type == RESP_NAME_LIST + && state->req_input->type == REQ_INP_CERT) { + + if (state->simple_attrs->name_list == NULL) { + /* No results from sub-domains, nothing to do */ + ret = EOK; + goto done; + } + + state->mapped_attrs = sysdb_new_attrs(state); + if (state->mapped_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_base64_blob(state->mapped_attrs, + SYSDB_USER_MAPPED_CERT, + state->req_input->inp.cert); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_base64_blob failed.\n"); + goto done; + } + + subreq = ipa_s2n_get_list_send(state, state->ev, + state->ipa_ctx, state->dom, + state->sh, state->exop_timeout, + BE_REQ_USER, + REQ_FULL_WITH_MEMBERS, + REQ_INP_NAME, + state->simple_attrs->name_list, + state->mapped_attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_s2n_get_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_done, + req); + + return; + } + + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected request type %d.\n", state->request_type); + ret = EINVAL; + goto done; + } + + if (state->attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing data of full request.\n"); + ret = EINVAL; + goto done; + } + + if (state->simple_attrs != NULL + && state->simple_attrs->response_type == RESP_SID) { + sid_str = state->simple_attrs->a.sid_str; + ret = EOK; + } else if (state->attrs->sysdb_attrs != NULL) { + ret = sysdb_attrs_get_string(state->attrs->sysdb_attrs, SYSDB_SID_STR, + &sid_str); + } else if (state->req_input->type == REQ_INP_SECID) { + sid_str = state->req_input->inp.secid; + ret = EOK; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "No SID available.\n"); + ret = ENOENT; + } + + if (ret == ENOENT || is_default_view(state->ipa_ctx->view_name)) { + ret = ipa_s2n_save_objects(state->dom, state->req_input, state->attrs, + state->simple_attrs, NULL, NULL, NULL, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_save_objects failed.\n"); + goto done; + } + } else if (ret == EOK) { + ret = get_dp_id_data_for_sid(state, sid_str, state->dom->name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto done; + } + + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM), + state->ipa_ctx->view_name, + ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_s2n_get_user_get_override_done, + req); + + return; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + return; +} + +static errno_t get_groups_dns(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + char **name_list, char ***_dn_list) +{ + int ret; + TALLOC_CTX *tmp_ctx; + int c; + struct sss_domain_info *root_domain; + char **dn_list; + size_t dn_list_c; + struct ldb_message *msg; + struct ldb_dn *user_base_dn = NULL; + + if (name_list == NULL) { + *_dn_list = NULL; + return EOK; + } + + /* To handle cross-domain memberships we have to check the domain for + * each group the member should be added or deleted. Since sub-domains + * use fully-qualified names by default any short name can only belong + * to the root/head domain. find_domain_by_object_name() will return + * the domain given in the first argument if the second argument is a + * a short name hence we always use root_domain as first argument. */ + root_domain = get_domains_head(dom); + if (root_domain->fqnames) { + DEBUG(SSSDBG_TRACE_FUNC, + "Root domain uses fully-qualified names, " \ + "objects might not be correctly added to groups with " \ + "short names.\n"); + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + for (c = 0; name_list[c] != NULL; c++); + + dn_list = talloc_zero_array(tmp_ctx, char *, c + 1); + if (dn_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + dn_list_c = 0; + for (c = 0; name_list[c] != NULL; c++) { + dom = find_domain_by_object_name(root_domain, name_list[c]); + if (dom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot find domain for [%s].\n", name_list[c]); + ret = ENOENT; + goto done; + } + + /* If the group name is overridden in the default view we have to + * search for the name and cannot construct it because the extdom + * plugin will return the overridden name but the DN of the related + * group object in the cache will contain the original name. */ + + ret = sysdb_search_group_by_name(tmp_ctx, dom, name_list[c], NULL, + &msg); + if (ret == EOK) { + talloc_free(user_base_dn); + user_base_dn = sysdb_user_base_dn(tmp_ctx, dom); + if (user_base_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_user_base_dn failed.\n"); + ret = ENOMEM; + goto done; + } + if (ldb_dn_compare_base(user_base_dn, msg->dn) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping user private group [%s].\n", + ldb_dn_get_linearized(msg->dn)); + continue; + } + + dn_list[dn_list_c] = ldb_dn_alloc_linearized(dn_list, msg->dn); + } else { + /* best effort, try to construct the DN */ + DEBUG(SSSDBG_TRACE_FUNC, + "sysdb_search_group_by_name failed with [%d], " + "generating DN for [%s] in domain [%s].\n", + ret, name_list[c], dom->name); + dn_list[dn_list_c] = sysdb_group_strdn(dn_list, dom->name, + name_list[c]); + } + if (dn_list[dn_list_c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_alloc_linearized failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Added [%s][%s].\n", name_list[c], + dn_list[dn_list_c]); + dn_list_c++; + } + + *_dn_list = talloc_steal(mem_ctx, dn_list); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t ipa_s2n_save_objects(struct sss_domain_info *dom, + struct req_input *req_input, + struct resp_attrs *attrs, + struct resp_attrs *simple_attrs, + const char *view_name, + struct sysdb_attrs *override_attrs, + struct sysdb_attrs *mapped_attrs, + bool update_initgr_timeout) +{ + int ret; + time_t now; + struct sss_nss_homedir_ctx homedir_ctx; + char *name = NULL; + char *upn = NULL; + gid_t gid; + gid_t orig_gid = 0; + TALLOC_CTX *tmp_ctx; + const char *sid_str; + const char *tmp_str; + struct ldb_result *res; + enum sysdb_member_type type; + char **sysdb_grouplist; + char **add_groups_dns; + char **del_groups_dns; + char **groups_dns; + bool in_transaction = false; + int tret; + struct sysdb_attrs *gid_override_attrs = NULL; + struct ldb_message *msg; + struct ldb_message_element *el = NULL; + + /* The list of elements that might be missing are: + * - SYSDB_ORIG_MEMBEROF + * - SYSDB_SSH_PUBKEY + * - SYSDB_USER_CERT + * Note that the list includes the trailing NULL at the end. */ + size_t missing_count = 0; + const char *missing[] = {NULL, NULL, NULL, NULL}; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + now = time(NULL); + + if (attrs->sysdb_attrs == NULL) { + attrs->sysdb_attrs = sysdb_new_attrs(attrs); + if (attrs->sysdb_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (attrs->sysdb_attrs != NULL) { + ret = sysdb_attrs_get_string(attrs->sysdb_attrs, + ORIGINALAD_PREFIX SYSDB_NAME, &tmp_str); + if (ret == EOK) { + name = talloc_strdup(tmp_ctx, tmp_str); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found original AD name [%s].\n", name); + } else if (ret == ENOENT) { + name = NULL; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(attrs->sysdb_attrs, + SYSDB_DEFAULT_OVERRIDE_NAME, &tmp_str); + if (ret == EOK) { + ret = sysdb_attrs_add_lc_name_alias_safe(attrs->sysdb_attrs, + tmp_str); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_lc_name_alias_safe failed.\n"); + goto done; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(attrs->sysdb_attrs, SYSDB_UPN, &tmp_str); + if (ret == EOK) { + upn = talloc_strdup(tmp_ctx, tmp_str); + if (upn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found original AD upn [%s].\n", upn); + } else if (ret == ENOENT) { + upn = NULL; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + } + + if (strcmp(dom->name, attrs->domain_name) != 0) { + dom = find_domain_by_name(get_domains_head(dom), + attrs->domain_name, true); + if (dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot find domain: [%s]\n", attrs->domain_name); + ret = EINVAL; + goto done; + } + } + + switch (attrs->response_type) { + case RESP_USER: + case RESP_USER_GROUPLIST: + type = SYSDB_MEMBER_USER; + if (dom->subdomain_homedir + && attrs->a.user.pw_dir == NULL) { + memset(&homedir_ctx, 0, sizeof(homedir_ctx)); + homedir_ctx.username = attrs->a.user.pw_name; + homedir_ctx.uid = attrs->a.user.pw_uid; + homedir_ctx.domain = dom->name; + homedir_ctx.flatname = dom->flat_name; + homedir_ctx.config_homedir_substr = dom->homedir_substr; + + attrs->a.user.pw_dir = expand_homedir_template(attrs, + dom->subdomain_homedir, + dom->case_preserve, + &homedir_ctx); + if (attrs->a.user.pw_dir == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (name == NULL) { + name = attrs->a.user.pw_name; + } + + ret = sysdb_attrs_add_lc_name_alias_safe(attrs->sysdb_attrs, name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_lc_name_alias_safe failed.\n"); + goto done; + } + + if (req_input->type == REQ_INP_SECID) { + ret = sysdb_attrs_add_string_safe(attrs->sysdb_attrs, + SYSDB_SID_STR, + req_input->inp.secid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto done; + } + } + + if (simple_attrs != NULL + && simple_attrs->response_type == RESP_SID) { + ret = sysdb_attrs_add_string_safe(attrs->sysdb_attrs, + SYSDB_SID_STR, + simple_attrs->a.sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto done; + } + } + + if (attrs->response_type == RESP_USER_GROUPLIST + && update_initgr_timeout) { + /* Since RESP_USER_GROUPLIST contains all group memberships it + * is effectively an initgroups request hence + * SYSDB_INITGR_EXPIRE will be set.*/ + ret = sysdb_attrs_add_time_t(attrs->sysdb_attrs, + SYSDB_INITGR_EXPIRE, + time(NULL) + dom->user_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_time_t failed.\n"); + goto done; + } + } + + gid = 0; + if (sss_domain_is_mpg(dom) == false) { + gid = attrs->a.user.pw_gid; + } else { + /* The extdom plugin always returns the objects with the + * default view applied. Since the GID is handled specially + * for MPG domains we have add any overridden GID separately. + */ + ret = sysdb_attrs_get_uint32_t(attrs->sysdb_attrs, + ORIGINALAD_PREFIX SYSDB_GIDNUM, + &orig_gid); + if (ret == EOK || ret == ENOENT) { + if ((orig_gid != 0 && orig_gid != attrs->a.user.pw_gid) + || attrs->a.user.pw_uid != attrs->a.user.pw_gid) { + + gid_override_attrs = sysdb_new_attrs(tmp_ctx); + if (gid_override_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_uint32(gid_override_attrs, + SYSDB_GIDNUM, + attrs->a.user.pw_gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_uint32 failed.\n"); + goto done; + } + } + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_uint32_t failed.\n"); + goto done; + } + } + + ret = sysdb_attrs_get_el_ext(attrs->sysdb_attrs, + SYSDB_ORIG_MEMBEROF, false, &el); + if (ret == ENOENT) { + missing[missing_count++] = SYSDB_ORIG_MEMBEROF; + } + + ret = sysdb_attrs_get_el_ext(attrs->sysdb_attrs, + SYSDB_SSH_PUBKEY, false, &el); + if (ret == ENOENT) { + missing[missing_count++] = SYSDB_SSH_PUBKEY; + } + + ret = sysdb_attrs_get_el_ext(attrs->sysdb_attrs, + SYSDB_USER_CERT, false, &el); + if (ret == ENOENT) { + missing[missing_count++] = SYSDB_USER_CERT; + } + + ret = sysdb_transaction_start(dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = sysdb_store_user(dom, name, NULL, + attrs->a.user.pw_uid, + gid, attrs->a.user.pw_gecos, + attrs->a.user.pw_dir, attrs->a.user.pw_shell, + NULL, attrs->sysdb_attrs, + missing[0] == NULL ? NULL + : discard_const(missing), + dom->user_timeout, now); + if (ret == EEXIST && sss_domain_is_mpg(dom) == true) { + /* This handles the case where getgrgid() was called for + * this user, so a group was created in the cache + */ + ret = sysdb_search_group_by_name(tmp_ctx, dom, name, NULL, &msg); + if (ret != EOK) { + /* Fail even on ENOENT, the group must be around */ + DEBUG(SSSDBG_OP_FAILURE, + "Could not delete MPG group [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_delete_group(dom, NULL, attrs->a.user.pw_uid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_delete_group failed for MPG group [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_store_user(dom, name, NULL, + attrs->a.user.pw_uid, + gid, attrs->a.user.pw_gecos, + attrs->a.user.pw_dir, + attrs->a.user.pw_shell, + NULL, attrs->sysdb_attrs, NULL, + dom->user_timeout, now); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_store_user failed for MPG user [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_store_user failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (mapped_attrs != NULL) { + ret = sysdb_set_user_attr(dom, name, mapped_attrs, + SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_user_attr failed.\n"); + goto done; + } + } + + if (gid_override_attrs != NULL) { + ret = sysdb_set_user_attr(dom, name, gid_override_attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_user_attr failed.\n"); + goto done; + } + } + + if (attrs->response_type == RESP_USER_GROUPLIST) { + ret = get_sysdb_grouplist_dn(tmp_ctx, dom->sysdb, dom, name, + &sysdb_grouplist); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_sysdb_grouplist failed.\n"); + goto done; + } + + ret = get_groups_dns(tmp_ctx, dom, attrs->groups, &groups_dns); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_groups_dns failed.\n"); + goto done; + } + + ret = diff_string_lists(tmp_ctx, groups_dns, + sysdb_grouplist, &add_groups_dns, + &del_groups_dns, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "diff_string_lists failed.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Updating memberships for %s\n", + name); + ret = sysdb_update_members_dn(dom, name, SYSDB_MEMBER_USER, + (const char *const *) add_groups_dns, + (const char *const *) del_groups_dns); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Membership update failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + ret = sysdb_transaction_commit(dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + break; + case RESP_GROUP: + case RESP_GROUP_MEMBERS: + type = SYSDB_MEMBER_GROUP; + + if (name == NULL) { + name = attrs->a.group.gr_name; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Processing group %s\n", name); + + ret = sysdb_attrs_add_lc_name_alias_safe(attrs->sysdb_attrs, name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_lc_name_alias_safe failed.\n"); + goto done; + } + + /* We might already have the SID from other sources hence + * sysdb_attrs_add_string_safe is used to avoid double entries. */ + if (req_input->type == REQ_INP_SECID) { + ret = sysdb_attrs_add_string_safe(attrs->sysdb_attrs, + SYSDB_SID_STR, + req_input->inp.secid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto done; + } + } + + if (simple_attrs != NULL + && simple_attrs->response_type == RESP_SID) { + ret = sysdb_attrs_add_string_safe(attrs->sysdb_attrs, + SYSDB_SID_STR, + simple_attrs->a.sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto done; + } + } + + ret = process_members(dom, is_default_view(view_name), + attrs->sysdb_attrs, attrs->a.group.gr_mem, + NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "process_members failed.\n"); + goto done; + } + + ret = sysdb_store_group(dom, name, attrs->a.group.gr_gid, + attrs->sysdb_attrs, dom->group_timeout, + now); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_store_group failed.\n"); + goto done; + } + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unexpected response type [%d].\n", + attrs->response_type); + ret = EINVAL; + goto done; + } + + ret = sysdb_attrs_get_string(attrs->sysdb_attrs, SYSDB_SID_STR, &sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot find SID of object.\n"); + if (name != NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Object [%s] has no SID, please check the " + "ipaNTSecurityIdentifier attribute on the server-side.\n", + name); + } + goto done; + } + + ret = sysdb_search_object_by_sid(tmp_ctx, dom, sid_str, NULL, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot find object with override with SID [%s].\n", sid_str); + goto done; + } + + if (!is_default_view(view_name)) { + /* For the default view the data return by the extdom plugin already + * contains all needed data and it is not expected to have a separate + * override object. */ + ret = sysdb_store_override(dom, view_name, type, override_attrs, + res->msgs[0]->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_store_override failed.\n"); + goto done; + } + } + +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(dom->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + + talloc_free(tmp_ctx); + + return ret; +} + +static void ipa_s2n_get_list_done(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_user_state *state = tevent_req_data(req, + struct ipa_s2n_get_user_state); + const char *sid_str; + struct dp_id_data *ar; + + ret = ipa_s2n_get_list_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n get_fqlist request failed.\n"); + tevent_req_error(req, ret); + return; + } + + if (state->attrs == NULL) { + /* If this is a request by certificate we are done */ + if (state->req_input->type == REQ_INP_CERT) { + tevent_req_done(req); + } else { + tevent_req_error(req, EINVAL); + } + return; + } + + ret = sysdb_attrs_get_string(state->attrs->sysdb_attrs, SYSDB_SID_STR, + &sid_str); + if (ret == ENOENT) { + ret = ipa_s2n_save_objects(state->dom, state->req_input, state->attrs, + state->simple_attrs, NULL, NULL, NULL, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_save_objects failed.\n"); + goto fail; + } + tevent_req_done(req); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto fail; + } + + ret = get_dp_id_data_for_sid(state, sid_str, state->dom->name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto fail; + } + + if (state->override_attrs == NULL + && !is_default_view(state->ipa_ctx->view_name)) { + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM), + state->ipa_ctx->view_name, + ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_s2n_get_user_get_override_done, + req); + } else { + ret = ipa_s2n_save_objects(state->dom, state->req_input, state->attrs, + state->simple_attrs, + state->ipa_ctx->view_name, + state->override_attrs, NULL, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_save_objects failed.\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + } + + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static void ipa_s2n_get_user_get_override_done(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_user_state *state = tevent_req_data(req, + struct ipa_s2n_get_user_state); + struct sysdb_attrs *override_attrs = NULL; + + ret = ipa_get_ad_override_recv(subreq, NULL, state, &override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + tevent_req_error(req, ret); + return; + } + + ret = ipa_s2n_save_objects(state->dom, state->req_input, state->attrs, + state->simple_attrs, state->ipa_ctx->view_name, + override_attrs, NULL, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_save_objects failed.\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +int ipa_s2n_get_acct_info_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_get_subdom_acct_process_pac_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sss_domain_info *dom; + char *username; + + size_t num_missing_sids; + char **missing_sids; + size_t num_cached_groups; + char **cached_groups; +}; + +static void ipa_get_subdom_acct_process_pac_done(struct tevent_req *subreq); + +struct tevent_req *ipa_get_subdom_acct_process_pac_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *dom, + struct ldb_message *user_msg) +{ + int ret; + struct ipa_get_subdom_acct_process_pac_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + char *user_sid; + char *primary_group_sid; + size_t num_sids; + char **group_sids; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_get_subdom_acct_process_pac_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->sh = sh; + state->dom = dom; + + ret = ad_get_pac_data_from_user_entry(state, user_msg, + ipa_ctx->sdap_id_ctx->opts->idmap_ctx->map, + &state->username, + &user_sid, &primary_group_sid, + &num_sids, &group_sids); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_get_pac_data_from_user_entry failed.\n"); + goto done; + } + + ret = sdap_ad_tokengroups_get_posix_members(state, state->dom, + num_sids, group_sids, + &state->num_missing_sids, + &state->missing_sids, + &state->num_cached_groups, + &state->cached_groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_tokengroups_get_posix_members failed.\n"); + goto done; + } + + + if (state->num_missing_sids == 0) { + ret = sdap_ad_tokengroups_update_members(state->username, + state->dom->sysdb, + state->dom, + state->cached_groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + } + + goto done; + } + + + subreq = ipa_s2n_get_list_send(state, state->ev, ipa_ctx, state->dom, + state->sh, + dp_opt_get_int(ipa_ctx->sdap_id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + BE_REQ_BY_SECID, REQ_FULL, REQ_INP_SECID, + state->missing_sids, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_get_subdom_acct_process_pac_done, req); + + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ipa_get_subdom_acct_process_pac_done(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_subdom_acct_process_pac_state *state = tevent_req_data(req, + struct ipa_get_subdom_acct_process_pac_state); + char **cached_groups; + size_t num_cached_groups; + + ret = ipa_s2n_get_list_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n get_fqlist request failed.\n"); + tevent_req_error(req, ret); + return; + } + + /* from ad_pac.c */ + ret = sdap_ad_tokengroups_get_posix_members(state, state->dom, + state->num_missing_sids, + state->missing_sids, + NULL, NULL, + &num_cached_groups, + &cached_groups); + if (ret != EOK){ + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_ad_tokengroups_get_posix_members failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + state->cached_groups = concatenate_string_array(state, + state->cached_groups, + state->num_cached_groups, + cached_groups, + num_cached_groups); + if (state->cached_groups == NULL) { + ret = ENOMEM; + goto done; + } + + /* update membership of existing groups */ + ret = sdap_ad_tokengroups_update_members(state->username, + state->dom->sysdb, + state->dom, + state->cached_groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +errno_t ipa_get_subdom_acct_process_pac_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ipa/ipa_selinux.c b/src/providers/ipa/ipa_selinux.c new file mode 100644 index 0000000..16a8d7b --- /dev/null +++ b/src/providers/ipa/ipa_selinux.c @@ -0,0 +1,1698 @@ +/* + SSSD + + IPA Backend Module -- selinux loading + + Authors: + Jan Zeleny + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ +#include + +#include "db/sysdb_selinux.h" +#include "util/child_common.h" +#include "util/sss_selinux.h" +#include "util/sss_chain_id.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_config.h" +#include "providers/ipa/ipa_selinux.h" +#include "providers/ipa/ipa_hosts.h" +#include "providers/ipa/ipa_hbac_rules.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_access.h" +#include "providers/ipa/ipa_selinux_maps.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_rules_common.h" + +#ifndef SELINUX_CHILD_DIR +#ifndef SSSD_LIBEXEC_PATH +#error "SSSD_LIBEXEC_PATH not defined" +#endif /* SSSD_LIBEXEC_PATH */ + +#define SELINUX_CHILD_DIR SSSD_LIBEXEC_PATH +#endif /* SELINUX_CHILD_DIR */ + +#define SELINUX_CHILD SELINUX_CHILD_DIR"/selinux_child" +#define SELINUX_CHILD_LOG_FILE "selinux_child" + +#include + +static struct tevent_req * +ipa_get_selinux_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct ipa_selinux_ctx *selinux_ctx); +static errno_t ipa_get_selinux_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *count, + struct sysdb_attrs ***maps, + size_t *hbac_count, + struct sysdb_attrs ***hbac_rules, + char **default_user, + char **map_order); + +static void ipa_get_selinux_connect_done(struct tevent_req *subreq); +static void ipa_get_selinux_hosts_done(struct tevent_req *subreq); +static void ipa_get_config_step(struct tevent_req *req); +static void ipa_get_selinux_config_done(struct tevent_req *subreq); +static void ipa_get_selinux_maps_done(struct tevent_req *subreq); +static void ipa_get_selinux_hbac_done(struct tevent_req *subreq); +static errno_t ipa_selinux_process_maps(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct sysdb_attrs **selinux_maps, + size_t selinux_map_count, + struct sysdb_attrs **hbac_rules, + size_t hbac_rule_count, + struct sysdb_attrs ***usermaps); + +static errno_t +ipa_save_user_maps(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + size_t map_count, + struct sysdb_attrs **maps) +{ + errno_t ret; + errno_t sret; + bool in_transaction = false; + int i; + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + for (i = 0; i < map_count; i++) { + ret = sysdb_store_selinux_usermap(domain, maps[i]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store user map %d. " + "Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_FUNC, "User map %d processed.\n", i); + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + ret = EOK; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + return ret; +} + +struct map_order_ctx { + char *order; + char **order_array; + size_t order_count; +}; + +struct selinux_child_input { + const char *seuser; + const char *mls_range; + const char *username; +}; + +static errno_t +ipa_selinux_process_seealso_maps(struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct sysdb_attrs **seealso_rules, + size_t seealso_rules_count, + struct sysdb_attrs **hbac_rules, + size_t hbac_rule_count, + uint32_t top_priority, + struct sysdb_attrs **usermaps, + size_t best_match_maps_cnt); +static errno_t +ipa_selinux_process_maps(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct sysdb_attrs **selinux_maps, + size_t selinux_map_count, + struct sysdb_attrs **hbac_rules, + size_t hbac_rule_count, + struct sysdb_attrs ***_usermaps) +{ + TALLOC_CTX *tmp_ctx; + int i; + errno_t ret; + uint32_t priority = 0; + uint32_t top_priority = 0; + struct sysdb_attrs **seealso_rules; + size_t num_seealso_rules = 0; + const char *seealso_str; + struct sysdb_attrs **usermaps; + size_t best_match_maps_cnt = 0; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + seealso_rules = talloc_zero_array(tmp_ctx, struct sysdb_attrs *, + selinux_map_count + 1); + if (seealso_rules == NULL) { + ret = ENOMEM; + goto done; + } + + usermaps = talloc_zero_array(tmp_ctx, struct sysdb_attrs *, selinux_map_count + 1); + if (usermaps == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < selinux_map_count; i++) { + if (sss_selinux_match(selinux_maps[i], user, host, &priority)) { + if (priority < top_priority) { + /* This rule has lower priority than what we already have, + * skip it. */ + continue; + } else if (priority > top_priority) { + /* This rule has higher priority, drop what we already have */ + while (best_match_maps_cnt > 0) { + best_match_maps_cnt--; + usermaps[best_match_maps_cnt] = NULL; + } + top_priority = priority; + } + + usermaps[best_match_maps_cnt] = selinux_maps[i]; + best_match_maps_cnt++; + + continue; + } + + /* SELinux map did not matched -> check sealso attribute for + * possible HBAC match */ + ret = sysdb_attrs_get_string(selinux_maps[i], + SYSDB_SELINUX_SEEALSO, &seealso_str); + if (ret == ENOENT) { + continue; + } else if (ret != EOK) { + goto done; + } + + seealso_rules[num_seealso_rules] = selinux_maps[i]; + num_seealso_rules++; + } + + ret = ipa_selinux_process_seealso_maps(user, host, + seealso_rules, num_seealso_rules, + hbac_rules, hbac_rule_count, + top_priority, usermaps, best_match_maps_cnt); + if (ret != EOK) { + goto done; + } + + *_usermaps = talloc_steal(mem_ctx, usermaps); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_selinux_process_seealso_maps(struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct sysdb_attrs **seealso_rules, + size_t seealso_rules_count, + struct sysdb_attrs **hbac_rules, + size_t hbac_rule_count, + uint32_t top_priority, + struct sysdb_attrs **usermaps, + size_t best_match_maps_cnt) +{ + int i, j; + errno_t ret; + struct ldb_message_element *el; + struct sysdb_attrs *usermap; + const char *seealso_dn; + const char *hbac_dn; + uint32_t priority; + + for (i = 0; i < hbac_rule_count; i++) { + ret = sysdb_attrs_get_string(hbac_rules[i], SYSDB_ORIG_DN, &hbac_dn); + if (ret != EOK) { + return ret; + } + + /* We need to do this translation for further processing. We have to + * do it manually because no map was used to retrieve HBAC rules. + */ + ret = sysdb_attrs_get_el(hbac_rules[i], IPA_MEMBER_HOST, &el); + if (ret != EOK) return ret; + el->name = SYSDB_ORIG_MEMBER_HOST; + + ret = sysdb_attrs_get_el(hbac_rules[i], IPA_MEMBER_USER, &el); + if (ret != EOK) return ret; + el->name = SYSDB_ORIG_MEMBER_USER; + + DEBUG(SSSDBG_TRACE_ALL, + "Matching HBAC rule %s with SELinux mappings\n", hbac_dn); + + if (!sss_selinux_match(hbac_rules[i], user, host, &priority)) { + DEBUG(SSSDBG_TRACE_ALL, "Rule did not match\n"); + continue; + } + + /* HBAC rule matched, find if it is in the "possible" list */ + for (j = 0; j < seealso_rules_count; j++) { + usermap = seealso_rules[j]; + if (usermap == NULL) { + continue; + } + + ret = sysdb_attrs_get_string(usermap, SYSDB_SELINUX_SEEALSO, &seealso_dn); + if (ret != EOK) { + return ret; + } + + if (strcasecmp(hbac_dn, seealso_dn) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "HBAC rule [%s] matched, copying its" + "attributes to SELinux user map [%s]\n", + hbac_dn, seealso_dn); + + /* Selinux maps priority evaluation removed --DELETE this comment before pushing*/ + if (priority < top_priority) { + /* This rule has lower priority than what we already have, + * skip it. */ + continue; + } else if (priority > top_priority) { + /* This rule has higher priority, drop what we already have */ + while (best_match_maps_cnt > 0) { + best_match_maps_cnt--; + usermaps[best_match_maps_cnt] = NULL; + } + top_priority = priority; + } + + usermaps[best_match_maps_cnt] = usermap; + best_match_maps_cnt++; + + ret = sysdb_attrs_copy_values(hbac_rules[i], usermap, SYSDB_ORIG_MEMBER_USER); + if (ret != EOK) { + return ret; + } + + ret = sysdb_attrs_copy_values(hbac_rules[i], usermap, SYSDB_USER_CATEGORY); + if (ret != EOK) { + return ret; + } + + /* Speed up the next iteration */ + seealso_rules[j] = NULL; + } + } + } + + return EOK; +} + +static errno_t init_map_order_ctx(TALLOC_CTX *mem_ctx, const char *map_order, + struct map_order_ctx **_mo_ctx) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + int i; + int len; + struct map_order_ctx *mo_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + mo_ctx = talloc(tmp_ctx, struct map_order_ctx); + if (mo_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + /* The "order" string contains one or more SELinux user records + * separated by $. Now we need to create an array of string from + * this one string. First find out how many elements in the array + * will be. This way only one alloc will be necessary for the array + */ + mo_ctx->order_count = 1; + len = strlen(map_order); + for (i = 0; i < len; i++) { + if (map_order[i] == '$') mo_ctx->order_count++; + } + + mo_ctx->order_array = talloc_array(mo_ctx, char *, mo_ctx->order_count); + if (mo_ctx->order_array == NULL) { + ret = ENOMEM; + goto done; + } + + mo_ctx->order = talloc_strdup(mo_ctx, map_order); + if (mo_ctx->order == NULL) { + ret = ENOMEM; + goto done; + } + + /* Now fill the array with pointers to the original string. Also + * use binary zeros to make multiple string out of the one. + */ + mo_ctx->order_array[0] = mo_ctx->order; + mo_ctx->order_count = 1; + for (i = 0; i < len; i++) { + if (mo_ctx->order[i] == '$') { + mo_ctx->order[i] = '\0'; + mo_ctx->order_array[mo_ctx->order_count] = &mo_ctx->order[i+1]; + mo_ctx->order_count++; + } + } + + *_mo_ctx = talloc_steal(mem_ctx, mo_ctx); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t selinux_child_setup(TALLOC_CTX *mem_ctx, + const char *orig_name, + struct sss_domain_info *dom, + const char *seuser_mls_string, + struct selinux_child_input **_sci); + +/* Choose best selinux user based on given order and write + * the user to selinux login file. */ +static errno_t choose_best_seuser(TALLOC_CTX *mem_ctx, + struct sysdb_attrs **usermaps, + struct pam_data *pd, + struct sss_domain_info *user_domain, + struct map_order_ctx *mo_ctx, + const char *default_user, + struct selinux_child_input **_sci) +{ + TALLOC_CTX *tmp_ctx; + char *seuser_mls_str = NULL; + const char *tmp_str; + errno_t ret; + int i, j; + struct selinux_child_input *sci; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* If no maps match, we'll use the default SELinux user from the + * config */ + seuser_mls_str = talloc_strdup(tmp_ctx, default_user ? default_user : ""); + if (seuser_mls_str == NULL) { + ret = ENOMEM; + goto done; + } + + /* Iterate through the order array and try to find SELinux users + * in fetched maps. The order array contains all SELinux users + * allowed in the domain in the same order they should appear + * in the SELinux config file. If any user from the order array + * is not in fetched user maps, it means it should not be allowed + * for the user who is just logging in. + * + * Right now we have empty content of the SELinux config file, + * we shall add only those SELinux users that are present both in + * the order array and user maps applicable to the user who is + * logging in. + */ + for (i = 0; i < mo_ctx->order_count; i++) { + for (j = 0; usermaps[j] != NULL; j++) { + tmp_str = sss_selinux_map_get_seuser(usermaps[j]); + + if (tmp_str && !strcasecmp(tmp_str, mo_ctx->order_array[i])) { + /* If seuser_mls_str contained something, overwrite it. + * This record has higher priority. + */ + talloc_zfree(seuser_mls_str); + seuser_mls_str = talloc_strdup(tmp_ctx, tmp_str); + if (seuser_mls_str == NULL) { + ret = ENOMEM; + goto done; + } + break; + } + } + } + + ret = selinux_child_setup(tmp_ctx, pd->user, user_domain, seuser_mls_str, &sci); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set up child input buffer\n"); + goto done; + } + + *_sci = talloc_steal(mem_ctx, sci); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +selinux_child_setup(TALLOC_CTX *mem_ctx, + const char *orig_name, + struct sss_domain_info *dom, + const char *seuser_mls_string, + struct selinux_child_input **_sci) +{ + errno_t ret; + char *seuser; + const char *mls_range; + char *ptr; + char *username_final; + TALLOC_CTX *tmp_ctx; + struct selinux_child_input *sci; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Split seuser and mls_range */ + seuser = talloc_strdup(tmp_ctx, seuser_mls_string); + if (seuser == NULL) { + ret = ENOMEM; + goto done; + } + + ptr = seuser; + while (*ptr != ':' && *ptr != '\0') { + ptr++; + } + if (*ptr == '\0') { + /* No mls_range specified */ + mls_range = ""; + } else { + *ptr = '\0'; /* split */ + mls_range = ptr + 1; + } + + /* pam_selinux needs the username in the same format getpwnam() would + * return it + */ + username_final = sss_output_name(tmp_ctx, orig_name, + dom->case_preserve, 0); + if (dom->fqnames) { + username_final = sss_tc_fqname(tmp_ctx, dom->names, dom, username_final); + if (username_final == NULL) { + ret = ENOMEM; + goto done; + } + } + + sci = talloc(tmp_ctx, struct selinux_child_input); + if (sci == NULL) { + ret = ENOMEM; + goto done; + } + + sci->seuser = talloc_strdup(sci, seuser); + sci->mls_range = talloc_strdup(sci, mls_range); + sci->username = talloc_strdup(sci, username_final); + if (sci->seuser == NULL || sci->mls_range == NULL + || sci->username == NULL) { + ret = ENOMEM; + goto done; + } + + *_sci = talloc_steal(mem_ctx, sci); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +struct selinux_child_state { + struct selinux_child_input *sci; + struct tevent_context *ev; + struct io_buffer *buf; + struct child_io_fds *io; +}; + +static errno_t selinux_child_create_buffer(struct selinux_child_state *state); +static errno_t selinux_fork_child(struct selinux_child_state *state); +static void selinux_child_step(struct tevent_req *subreq); +static void selinux_child_done(struct tevent_req *subreq); +static errno_t selinux_child_parse_response(uint8_t *buf, ssize_t len, + uint32_t *_child_result); + +static struct tevent_req *selinux_child_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct selinux_child_input *sci) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct selinux_child_state *state; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct selinux_child_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->sci = sci; + state->ev = ev; + state->io = talloc(state, struct child_io_fds); + state->buf = talloc(state, struct io_buffer); + if (state->io == NULL || state->buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto immediately; + } + + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + ret = selinux_child_create_buffer(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create the send buffer\n"); + ret = ENOMEM; + goto immediately; + } + + ret = selinux_fork_child(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to fork the child\n"); + goto immediately; + } + + subreq = write_pipe_send(state, ev, state->buf->data, state->buf->size, + state->io->write_to_child_fd); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, selinux_child_step, req); + + ret = EOK; +immediately: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static errno_t selinux_child_create_buffer(struct selinux_child_state *state) +{ + size_t rp; + size_t seuser_len; + size_t mls_range_len; + size_t username_len; + + seuser_len = strlen(state->sci->seuser); + mls_range_len = strlen(state->sci->mls_range); + username_len = strlen(state->sci->username); + + state->buf->size = 3 * sizeof(uint32_t); + state->buf->size += seuser_len + mls_range_len + username_len; + + DEBUG(SSSDBG_TRACE_ALL, "buffer size: %zu\n", state->buf->size); + + state->buf->data = talloc_size(state->buf, state->buf->size); + if (state->buf->data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + rp = 0; + + /* seuser */ + SAFEALIGN_SET_UINT32(&state->buf->data[rp], seuser_len, &rp); + safealign_memcpy(&state->buf->data[rp], state->sci->seuser, + seuser_len, &rp); + + /* mls_range */ + SAFEALIGN_SET_UINT32(&state->buf->data[rp], mls_range_len, &rp); + safealign_memcpy(&state->buf->data[rp], state->sci->mls_range, + mls_range_len, &rp); + + /* username */ + SAFEALIGN_SET_UINT32(&state->buf->data[rp], username_len, &rp); + safealign_memcpy(&state->buf->data[rp], state->sci->username, + username_len, &rp); + + return EOK; +} + +static errno_t selinux_fork_child(struct selinux_child_state *state) +{ + int pipefd_to_child[2]; + int pipefd_from_child[2]; + pid_t pid; + errno_t ret; + const char **extra_args; + int c = 0; + + extra_args = talloc_array(state, const char *, 2); + + extra_args[c] = talloc_asprintf(extra_args, "--chain-id=%lu", + sss_chain_id_get()); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + return ret; + } + c++; + + extra_args[c] = NULL; + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe (from) failed [%d][%s].\n", errno, sss_strerror(errno)); + return ret; + } + + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe (to) failed [%d][%s].\n", errno, sss_strerror(errno)); + return ret; + } + + pid = fork(); + + if (pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + SELINUX_CHILD, SELINUX_CHILD_LOG_FILE, extra_args, + false, STDIN_FILENO, STDOUT_FILENO); + DEBUG(SSSDBG_CRIT_FAILURE, "Could not exec selinux_child: [%d][%s].\n", + ret, sss_strerror(ret)); + return ret; + } else if (pid > 0) { /* parent */ + state->io->read_from_child_fd = pipefd_from_child[0]; + close(pipefd_from_child[1]); + state->io->write_to_child_fd = pipefd_to_child[1]; + close(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->read_from_child_fd); + sss_fd_nonblocking(state->io->write_to_child_fd); + + ret = child_handler_setup(state->ev, pid, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set up child signal handler\n"); + return ret; + } + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fork failed [%d][%s].\n", errno, sss_strerror(errno)); + return ret; + } + + return EOK; +} + +static void selinux_child_step(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + struct selinux_child_state *state; + + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct selinux_child_state); + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + close(state->io->write_to_child_fd); + state->io->write_to_child_fd = -1; + + subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, selinux_child_done, req); +} + +static void selinux_child_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct selinux_child_state *state; + uint32_t child_result; + errno_t ret; + ssize_t len; + uint8_t *buf; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct selinux_child_state); + + ret = read_pipe_recv(subreq, state, &buf, &len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + close(state->io->read_from_child_fd); + state->io->read_from_child_fd = -1; + + ret = selinux_child_parse_response(buf, len, &child_result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "selinux_child_parse_response failed: [%d][%s]\n", + ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } else if (child_result != 0){ + DEBUG(SSSDBG_CRIT_FAILURE, + "Error in selinux_child: [%d][%s]\n", + child_result, strerror(child_result)); + tevent_req_error(req, ERR_SELINUX_CONTEXT); + return; + } + + tevent_req_done(req); + return; +} + +static errno_t selinux_child_parse_response(uint8_t *buf, + ssize_t len, + uint32_t *_child_result) +{ + size_t p = 0; + uint32_t child_result; + + /* semanage retval */ + SAFEALIGN_COPY_UINT32_CHECK(&child_result, buf + p, len, &p); + + *_child_result = child_result; + return EOK; +} + +static errno_t selinux_child_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +/* A more generic request to gather all SELinux and HBAC rules. Updates + * cache if necessary + */ +struct ipa_get_selinux_state { + struct be_ctx *be_ctx; + struct ipa_selinux_ctx *selinux_ctx; + struct sdap_id_op *op; + + struct sysdb_attrs *host; + struct sysdb_attrs *user; + + struct sysdb_attrs *defaults; + struct sysdb_attrs **selinuxmaps; + size_t nmaps; + + struct sysdb_attrs **hbac_rules; + size_t hbac_rule_count; +}; + +static errno_t +ipa_get_selinux_maps_offline(struct tevent_req *req); + +static struct tevent_req * +ipa_get_selinux_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct ipa_selinux_ctx *selinux_ctx) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_get_selinux_state *state; + bool offline; + int ret = EOK; + time_t now; + time_t refresh_interval; + struct ipa_options *ipa_options = selinux_ctx->id_ctx->ipa_options; + + DEBUG(SSSDBG_TRACE_FUNC, "Retrieving SELinux user mapping\n"); + req = tevent_req_create(mem_ctx, &state, struct ipa_get_selinux_state); + if (req == NULL) { + return NULL; + } + + state->be_ctx = be_ctx; + state->selinux_ctx = selinux_ctx; + state->user = user; + state->host = host; + + offline = be_is_offline(be_ctx); + DEBUG(SSSDBG_TRACE_INTERNAL, "Connection status is [%s].\n", + offline ? "offline" : "online"); + + if (!offline) { + refresh_interval = dp_opt_get_int(ipa_options->basic, + IPA_SELINUX_REFRESH); + now = time(NULL); + if (now < selinux_ctx->last_update + refresh_interval) { + /* SELinux maps were recently updated -> force offline */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Performing cached SELinux processing\n"); + offline = true; + } + } + + if (!offline) { + state->op = sdap_id_op_create(state, + selinux_ctx->id_ctx->sdap_id_ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto immediate; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send failed: " + "%d(%s).\n", ret, strerror(ret)); + talloc_zfree(state->op); + goto immediate; + } + + tevent_req_set_callback(subreq, ipa_get_selinux_connect_done, req); + } else { + ret = ipa_get_selinux_maps_offline(req); + goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, be_ctx->ev); + return req; +} + +static void ipa_get_selinux_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + int dp_error = DP_ERR_FATAL; + int ret; + struct ipa_id_ctx *id_ctx = state->selinux_ctx->id_ctx; + struct dp_module *access_mod; + struct dp_module *selinux_mod; + const char *hostname; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (dp_error == DP_ERR_OFFLINE) { + talloc_zfree(state->op); + ret = ipa_get_selinux_maps_offline(req); + if (ret == EOK) { + tevent_req_done(req); + return; + } + goto fail; + } + + if (ret != EOK) { + goto fail; + } + + access_mod = dp_target_module(state->be_ctx->provider, DPT_ACCESS); + selinux_mod = dp_target_module(state->be_ctx->provider, DPT_SELINUX); + if (access_mod == selinux_mod && state->host != NULL) { + /* If the access control module is the same as the selinux module + * and the access control had already discovered the host + */ + return ipa_get_config_step(req); + } + + hostname = dp_opt_get_string(state->selinux_ctx->id_ctx->ipa_options->basic, + IPA_HOSTNAME); + if (hostname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot determine the host name\n"); + goto fail; + } + + subreq = ipa_host_info_send(state, state->be_ctx->ev, + sdap_id_op_handle(state->op), + id_ctx->sdap_id_ctx->opts, + hostname, + id_ctx->ipa_options->id->host_map, + NULL, + state->selinux_ctx->host_search_bases); + if (subreq == NULL) { + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_get_selinux_hosts_done, req); + return; + +fail: + tevent_req_error(req, ret); +} + +static errno_t +ipa_get_selinux_maps_offline(struct tevent_req *req) +{ + errno_t ret; + size_t nmaps; + struct ldb_message **maps; + struct ldb_message *defaults; + const char *attrs[] = { SYSDB_NAME, + SYSDB_USER_CATEGORY, + SYSDB_HOST_CATEGORY, + SYSDB_ORIG_MEMBER_USER, + SYSDB_ORIG_MEMBER_HOST, + SYSDB_SELINUX_SEEALSO, + SYSDB_SELINUX_USER, + NULL }; + const char **attrs_get_cached_rules; + const char *default_user; + const char *order; + + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + + /* read the config entry */ + ret = sysdb_search_selinux_config(state, state->be_ctx->domain, + NULL, &defaults); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_selinux_config failed [%d]: %s\n", + ret, strerror(ret)); + return ret; + } + + default_user = ldb_msg_find_attr_as_string(defaults, + SYSDB_SELINUX_DEFAULT_USER, + NULL); + order = ldb_msg_find_attr_as_string(defaults, SYSDB_SELINUX_DEFAULT_ORDER, + NULL); + + state->defaults = sysdb_new_attrs(state); + if (state->defaults == NULL) { + return ENOMEM; + } + + if (default_user) { + ret = sysdb_attrs_add_string(state->defaults, + IPA_CONFIG_SELINUX_DEFAULT_USER_CTX, + default_user); + if (ret != EOK) { + return ret; + } + } + + ret = sysdb_attrs_add_string(state->defaults, + IPA_CONFIG_SELINUX_MAP_ORDER, order); + if (ret != EOK) { + return ret; + } + + /* read all the SELinux rules */ + ret = sysdb_get_selinux_usermaps(state, state->be_ctx->domain, + attrs, &nmaps, &maps); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_selinux_usermaps failed [%d]: %s\n", + ret, strerror(ret)); + return ret; + } + + ret = sysdb_msg2attrs(state, nmaps, maps, &state->selinuxmaps); + if (ret != EOK) { + return ret; + } + state->nmaps = nmaps; + + /* read all the HBAC rules */ + attrs_get_cached_rules = hbac_get_attrs_to_get_cached_rules(state); + if (attrs_get_cached_rules == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "hbac_get_attrs_to_get_cached_rules() failed\n"); + return ENOMEM; + } + + ret = ipa_common_get_cached_rules(state, state->be_ctx->domain, + IPA_HBAC_RULE, HBAC_RULES_SUBDIR, + attrs_get_cached_rules, + &state->hbac_rule_count, + &state->hbac_rules); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_common_get_cached_rules failed [%d]: %s\n", + ret, strerror(ret)); + return ret; + } + + return EOK; +} + +static void ipa_get_selinux_hosts_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + size_t host_count, hostgroup_count; + struct sysdb_attrs **hostgroups; + struct sysdb_attrs **host; + + ret = ipa_host_info_recv(subreq, state, &host_count, &host, + &hostgroup_count, &hostgroups); + talloc_free(subreq); + if (ret != EOK) { + goto done; + } + state->host = host[0]; + + return ipa_get_config_step(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } +} + +static void ipa_get_config_step(struct tevent_req *req) +{ + const char *domain; + struct tevent_req *subreq; + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + struct ipa_id_ctx *id_ctx = state->selinux_ctx->id_ctx; + + domain = dp_opt_get_string(state->selinux_ctx->id_ctx->ipa_options->basic, + IPA_KRB5_REALM); + subreq = ipa_get_config_send(state, state->be_ctx->ev, + sdap_id_op_handle(state->op), + id_ctx->sdap_id_ctx->opts, + domain, NULL, NULL, NULL); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + } + tevent_req_set_callback(subreq, ipa_get_selinux_config_done, req); +} + +static void ipa_get_selinux_config_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + struct sdap_id_ctx *id_ctx = state->selinux_ctx->id_ctx->sdap_id_ctx; + errno_t ret; + + ret = ipa_get_config_recv(subreq, state, &state->defaults); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, "Could not get IPA config\n"); + goto done; + } + + subreq = ipa_selinux_get_maps_send(state, state->be_ctx->ev, + state->be_ctx->domain->sysdb, + sdap_id_op_handle(state->op), + id_ctx->opts, + state->selinux_ctx->id_ctx->ipa_options, + state->selinux_ctx->selinux_search_bases); + if (!subreq) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_get_selinux_maps_done, req); + return; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } +} + +static void ipa_get_selinux_maps_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_get_selinux_state *state; + struct ipa_id_ctx *id_ctx; + struct dp_module *access_mod; + struct dp_module *selinux_mod; + const char **attrs_get_cached_rules; + const char *tmp_str; + bool check_hbac; + errno_t ret; + int i; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_get_selinux_state); + id_ctx = state->selinux_ctx->id_ctx; + + ret = ipa_selinux_get_maps_recv(subreq, state, + &state->nmaps, &state->selinuxmaps); + talloc_free(subreq); + if (ret != EOK) { + if (ret == ENOENT) { + /* This is returned if no SELinux mapping + * rules were found. In that case no error + * occurred, but we don't want any more processing.*/ + ret = EOK; + } + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Found %zu SELinux user maps\n", state->nmaps); + + check_hbac = false; + for (i = 0; i < state->nmaps; i++) { + ret = sysdb_attrs_get_string(state->selinuxmaps[i], + SYSDB_SELINUX_SEEALSO, &tmp_str); + if (ret == EOK) { + check_hbac = true; + break; + } + } + + if (check_hbac) { + access_mod = dp_target_module(state->be_ctx->provider, DPT_ACCESS); + selinux_mod = dp_target_module(state->be_ctx->provider, DPT_SELINUX); + if (access_mod == selinux_mod) { + attrs_get_cached_rules = hbac_get_attrs_to_get_cached_rules(state); + if (attrs_get_cached_rules == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ipa_common_get_cached_rules(state, state->be_ctx->domain, + IPA_HBAC_RULE, HBAC_RULES_SUBDIR, + attrs_get_cached_rules, + &state->hbac_rule_count, + &state->hbac_rules); + /* Terminates the request */ + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "SELinux maps referenced an HBAC rule. " + "Need to refresh HBAC rules\n"); + subreq = ipa_hbac_rule_info_send(state, state->be_ctx->ev, + sdap_id_op_handle(state->op), + id_ctx->sdap_id_ctx->opts, + state->selinux_ctx->hbac_search_bases, + state->host); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_get_selinux_hbac_done, req); + return; + } + + ret = EOK; +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static void ipa_get_selinux_hbac_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + errno_t ret; + + ret = ipa_hbac_rule_info_recv(subreq, state, &state->hbac_rule_count, + &state->hbac_rules); + DEBUG(SSSDBG_TRACE_INTERNAL, + "Received %zu HBAC rules\n", state->hbac_rule_count); + talloc_free(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } +} + +static errno_t +ipa_get_selinux_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *count, + struct sysdb_attrs ***maps, + size_t *hbac_count, + struct sysdb_attrs ***hbac_rules, + char **default_user, + char **map_order) +{ + struct ipa_get_selinux_state *state = + tevent_req_data(req, struct ipa_get_selinux_state); + const char *tmp_str; + errno_t ret; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + ret = sysdb_attrs_get_string(state->defaults, + IPA_CONFIG_SELINUX_DEFAULT_USER_CTX, + &tmp_str); + if (ret != EOK && ret != ENOENT) { + return ret; + } + + if (ret == EOK) { + *default_user = talloc_strdup(mem_ctx, tmp_str); + if (*default_user == NULL) { + return ENOMEM; + } + } + + ret = sysdb_attrs_get_string(state->defaults, IPA_CONFIG_SELINUX_MAP_ORDER, + &tmp_str); + if (ret != EOK) { + return ret; + } + + *map_order = talloc_strdup(mem_ctx, tmp_str); + if (*map_order == NULL) { + talloc_zfree(*default_user); + return ENOMEM; + } + + *count = state->nmaps; + *maps = talloc_steal(mem_ctx, state->selinuxmaps); + + *hbac_count = state->hbac_rule_count; + *hbac_rules = talloc_steal(mem_ctx, state->hbac_rules); + + return EOK; +} + +static errno_t +ipa_selinux_init_attrs(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *ipa_domain, + struct sss_domain_info *user_domain, + const char *username, + const char *hostname, + struct sysdb_attrs **_user, + struct sysdb_attrs **_host) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *host_dn; + const char *attrs[] = { SYSDB_ORIG_DN, + SYSDB_ORIG_MEMBEROF, + NULL }; + size_t count; + struct ldb_message **msgs; + struct sysdb_attrs **hosts; + struct sysdb_attrs *user = NULL; + struct sysdb_attrs *host = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_selinux_extract_user(tmp_ctx, user_domain, username, &user); + if (ret != EOK) { + goto done; + } + + host_dn = sysdb_custom_dn(tmp_ctx, ipa_domain, hostname, HBAC_HOSTS_SUBDIR); + if (host_dn == NULL) { + goto done; + } + + /* Look up the host to get its originalMemberOf entries */ + ret = sysdb_search_entry(tmp_ctx, sysdb, host_dn, LDB_SCOPE_BASE, NULL, + attrs, &count, &msgs); + if (ret == ENOENT || count == 0) { + host = NULL; + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } else if (count > 1) { + DEBUG(SSSDBG_OP_FAILURE, "More than one result for a BASE search!\n"); + goto done; + } + + ret = sysdb_msg2attrs(tmp_ctx, count, msgs, &hosts); + talloc_free(msgs); + if (ret != EOK) { + goto done; + } + + host = hosts[0]; + + ret = EOK; + +done: + if (ret == EOK) { + *_user = talloc_steal(mem_ctx, user); + *_host = talloc_steal(mem_ctx, host); + } + + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +ipa_selinux_store_config(struct sysdb_ctx *sysdb, + struct sss_domain_info *ipa_domain, + const char *default_user, + const char *map_order, + size_t map_count, + struct sysdb_attrs **maps) +{ + bool in_transaction = false; + errno_t sret; + errno_t ret; + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = sysdb_delete_usermaps(ipa_domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot delete existing maps from sysdb\n"); + goto done; + } + + ret = sysdb_store_selinux_config(ipa_domain, default_user, map_order); + if (ret != EOK) { + goto done; + } + + if (map_count > 0) { + ret = ipa_save_user_maps(sysdb, ipa_domain, map_count, maps); + if (ret != EOK) { + goto done; + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n"); + } + } + + return ret; +} + +static errno_t +ipa_selinux_create_child_input(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct sysdb_attrs **maps, + size_t map_count, + struct sysdb_attrs **hbac_rules, + size_t hbac_count, + const char *map_order, + struct pam_data *pd, + struct sss_domain_info *user_domain, + const char *default_user, + struct selinux_child_input **_sci) +{ + struct sysdb_attrs **best_match_maps = NULL; + struct map_order_ctx *map_order_ctx = NULL; + struct selinux_child_input *sci = NULL; + errno_t ret; + + /* Process the maps and return list of best matches + * (maps with highest priority). */ + ret = ipa_selinux_process_maps(mem_ctx, user, host, maps, map_count, + hbac_rules, hbac_count, &best_match_maps); + if (ret != EOK) { + goto done; + } + + ret = init_map_order_ctx(mem_ctx, map_order, &map_order_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to create ordered SELinux users array.\n"); + goto done; + } + + ret = choose_best_seuser(mem_ctx, best_match_maps, pd, user_domain, + map_order_ctx, default_user, &sci); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to evaluate ordered SELinux users array.\n"); + goto done; + } + + *_sci = sci; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(best_match_maps); + talloc_free(map_order_ctx); + talloc_free(sci); + } + + return ret; +} + +struct ipa_selinux_handler_state { + struct be_ctx *be_ctx; + struct tevent_context *ev; + struct pam_data *pd; + + struct sss_domain_info *user_domain; + struct sss_domain_info *ipa_domain; + struct ipa_selinux_ctx *selinux_ctx; + + struct sysdb_attrs *user; + struct sysdb_attrs *host; +}; + +static void ipa_selinux_handler_get_done(struct tevent_req *subreq); +static void ipa_selinux_handler_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_selinux_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_selinux_ctx *selinux_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct ipa_selinux_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + const char *hostname; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_selinux_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->be_ctx = params->be_ctx; + state->ev = params->ev; + state->pd = pd; + state->user_domain = params->domain; + state->ipa_domain = params->be_ctx->domain; + state->selinux_ctx = selinux_ctx; + + pd->pam_status = PAM_SYSTEM_ERR; + + hostname = dp_opt_get_string(selinux_ctx->id_ctx->ipa_options->basic, + IPA_HOSTNAME); + if (hostname == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot determine this machine's host name\n"); + goto immediately; + } + + ret = ipa_selinux_init_attrs(state, state->user_domain->sysdb, + state->ipa_domain, state->user_domain, + pd->user, hostname, + &state->user, &state->host); + if (ret != EOK) { + goto immediately; + } + + subreq = ipa_get_selinux_send(state, params->be_ctx, state->user, + state->host, selinux_ctx); + if (subreq == NULL) { + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_selinux_handler_get_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_selinux_handler_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_selinux_handler_state *state; + struct selinux_child_input *sci; + struct sysdb_attrs **hbac_rules = NULL; + struct sysdb_attrs **maps = NULL; + size_t map_count = 0; + size_t hbac_count = 0; + char *default_user = NULL; + char *map_order = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_selinux_handler_state); + + ret = ipa_get_selinux_recv(subreq, state, &map_count, &maps, + &hbac_count, &hbac_rules, + &default_user, &map_order); + talloc_free(subreq); + if (ret != EOK) { + goto done; + } + + ret = ipa_selinux_store_config(state->ipa_domain->sysdb, state->ipa_domain, + default_user, map_order, map_count, maps); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to store SELinux config [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_selinux_create_child_input(state, state->user, state->host, + maps, map_count, hbac_rules, + hbac_count, map_order, state->pd, + state->user_domain, default_user, + &sci); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create child input [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Update the SELinux context in a privileged child as the back end is + * running unprivileged + */ + subreq = selinux_child_send(state, state->ev, sci); + if (subreq == NULL) { + goto done; + } + tevent_req_set_callback(subreq, ipa_selinux_handler_done, req); + return; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_selinux_handler_done(struct tevent_req *subreq) +{ + struct ipa_selinux_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_selinux_handler_state); + + ret = selinux_child_recv(subreq); + talloc_free(subreq); + if (ret != EOK) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + if (!be_is_offline(state->be_ctx)) { + state->selinux_ctx->last_update = time(NULL); + } + + state->pd->pam_status = PAM_SUCCESS; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +ipa_selinux_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct ipa_selinux_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_selinux_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/ipa/ipa_selinux.h b/src/providers/ipa/ipa_selinux.h new file mode 100644 index 0000000..dea8775 --- /dev/null +++ b/src/providers/ipa/ipa_selinux.h @@ -0,0 +1,50 @@ +/* + SSSD + + IPA Backend Module -- selinux loading + + Authors: + Jan Zeleny + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _IPA_SELINUX_H_ +#define _IPA_SELINUX_H_ + +#include "providers/ldap/ldap_common.h" + +struct ipa_selinux_ctx { + struct ipa_id_ctx *id_ctx; + time_t last_update; + + struct sdap_search_base **selinux_search_bases; + struct sdap_search_base **host_search_bases; + struct sdap_search_base **hbac_search_bases; +}; + +struct tevent_req * +ipa_selinux_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_selinux_ctx *selinux_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +ipa_selinux_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +#endif diff --git a/src/providers/ipa/ipa_selinux_maps.c b/src/providers/ipa/ipa_selinux_maps.c new file mode 100644 index 0000000..9abac4d --- /dev/null +++ b/src/providers/ipa/ipa_selinux_maps.c @@ -0,0 +1,222 @@ +/* + SSSD + + IPA Backend Module -- SELinux user maps (maps retrieval) + + Authors: + Jan Zeleny + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_selinux_maps.h" + +struct ipa_selinux_get_maps_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sdap_handle *sh; + struct sdap_options *opts; + struct ipa_options *ipa_opts; + const char **attrs; + + struct sdap_search_base **search_bases; + int search_base_iter; + + char *cur_filter; + char *maps_filter; + + size_t map_count; + struct sysdb_attrs **maps; +}; + +static errno_t +ipa_selinux_get_maps_next(struct tevent_req *req, + struct ipa_selinux_get_maps_state *state); +static void +ipa_selinux_get_maps_done(struct tevent_req *subreq); + +struct tevent_req *ipa_selinux_get_maps_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sdap_handle *sh, + struct sdap_options *opts, + struct ipa_options *ipa_opts, + struct sdap_search_base **search_bases) +{ + struct tevent_req *req; + struct ipa_selinux_get_maps_state *state; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ipa_selinux_get_maps_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->sysdb = sysdb; + state->sh = sh; + state->opts = opts; + state->ipa_opts = ipa_opts; + state->search_bases = search_bases; + state->search_base_iter = 0; + state->map_count = 0; + state->maps = NULL; + + ret = build_attrs_from_map(state, ipa_opts->selinuxuser_map, + IPA_OPTS_SELINUX_USERMAP, NULL, + &state->attrs, NULL); + if (ret != EOK) goto fail; + + state->cur_filter = NULL; + state->maps_filter = talloc_asprintf(state, + "(&(objectclass=%s)(%s=TRUE))", + ipa_opts->selinuxuser_map[IPA_OC_SELINUX_USERMAP].name, + ipa_opts->selinuxuser_map[IPA_AT_SELINUX_USERMAP_ENABLED].name); + if (state->maps_filter == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = ipa_selinux_get_maps_next(req, state); + if (ret == EOK) { + ret = EINVAL; + } + + if (ret != EAGAIN) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +ipa_selinux_get_maps_next(struct tevent_req *req, + struct ipa_selinux_get_maps_state *state) +{ + struct sdap_search_base *base; + struct tevent_req *subreq; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->maps_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Trying to fetch SELinux maps with following " + "parameters: [%d][%s][%s]\n", base->scope, + state->cur_filter, base->basedn); + subreq = sdap_get_generic_send(state, state->ev, state->opts, + state->sh, base->basedn, + base->scope, state->cur_filter, + state->attrs, + state->ipa_opts->selinuxuser_map, + IPA_OPTS_SELINUX_USERMAP, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_selinux_get_maps_done, req); + return EAGAIN; +} + +static void ipa_selinux_get_maps_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_selinux_get_maps_state *state = tevent_req_data(req, + struct ipa_selinux_get_maps_state); + struct sysdb_attrs **results; + size_t total_count; + size_t count; + int i; + + ret = sdap_get_generic_recv(subreq, state, &count, &results); + if (ret != EOK) { + goto done; + } + + if (count > 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Found %zu user maps in current search base\n", count); + + total_count = count + state->map_count; + state->maps = talloc_realloc(state, state->maps, struct sysdb_attrs *, total_count); + if (state->maps == NULL) { + ret = ENOMEM; + goto done; + } + + i = 0; + while (state->map_count < total_count) { + state->maps[state->map_count] = talloc_steal(state->maps, results[i]); + state->map_count++; + i++; + } + } + + state->search_base_iter++; + ret = ipa_selinux_get_maps_next(req, state); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + goto done; + } + + if (state->map_count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No SELinux user maps found!\n"); + ret = ENOENT; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } +} + +errno_t +ipa_selinux_get_maps_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *count, + struct sysdb_attrs ***maps) +{ + struct ipa_selinux_get_maps_state *state = + tevent_req_data(req, struct ipa_selinux_get_maps_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *count = state->map_count; + *maps = talloc_steal(mem_ctx, state->maps); + + return EOK; +} diff --git a/src/providers/ipa/ipa_selinux_maps.h b/src/providers/ipa/ipa_selinux_maps.h new file mode 100644 index 0000000..d3abec1 --- /dev/null +++ b/src/providers/ipa/ipa_selinux_maps.h @@ -0,0 +1,45 @@ +/* + SSSD + + IPA Backend Module -- SELinux user maps (maps retrieval) + + Authors: + Jan Zeleny + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_SELINUX_MAPS_H_ +#define IPA_SELINUX_MAPS_H_ + +#include "providers/ldap/sdap_async.h" + +struct tevent_req * +ipa_selinux_get_maps_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sdap_handle *sh, + struct sdap_options *opts, + struct ipa_options *ipa_opts, + struct sdap_search_base **search_bases); + +errno_t +ipa_selinux_get_maps_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *count, + struct sysdb_attrs ***maps); + +#endif /* IPA_SELINUX_MAPS_H_ */ diff --git a/src/providers/ipa/ipa_session.c b/src/providers/ipa/ipa_session.c new file mode 100644 index 0000000..bcd8055 --- /dev/null +++ b/src/providers/ipa/ipa_session.c @@ -0,0 +1,861 @@ +/* + SSSD + + IPA Backend Module -- Session Management + + Authors: + Fabiano Fidêncio + + Copyright (C) 2017 Red Hat + + 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 3 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; 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, see . +*/ +#include + +#include "util/child_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_config.h" +#include "providers/ipa/ipa_hosts.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_session.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ipa/ipa_deskprofile_private.h" +#include "providers/ipa/ipa_deskprofile_config.h" +#include "providers/ipa/ipa_deskprofile_rules.h" +#include "providers/ipa/ipa_deskprofile_rules_util.h" +#include "sss_iface/sss_iface_async.h" + + +/* Those here are used for sending a message to the deskprofile client + * informing that our side is done. */ +#define SSS_FLEETCOMMANDERCLIENT_BUS "org.freedesktop.FleetCommanderClient" +#define SSS_FLEETCOMMANDERCLIENT_PATH "/org/freedesktop/FleetCommanderClient" +#define SSS_FLEETCOMMANDERCLIENT_IFACE "org.freedesktop.FleetCommanderClient" + +#define MINUTE_IN_SECONDS 60 + +struct ipa_fetch_deskprofile_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct sdap_id_ctx *sdap_ctx; + struct ipa_session_ctx *session_ctx; + struct sdap_id_op *sdap_op; + struct dp_option *ipa_options; + struct sdap_search_base **search_bases; + const char *username; + + /* Hosts */ + struct ipa_common_entries *hosts; + struct sysdb_attrs *ipa_host; + + /* Rules */ + struct ipa_common_entries *rules; + struct sysdb_attrs *config; + uint16_t priority; +}; + +static errno_t ipa_fetch_deskprofile_retry(struct tevent_req *req); +static void ipa_fetch_deskprofile_connect_done(struct tevent_req *subreq); +static errno_t ipa_fetch_deskprofile_hostinfo(struct tevent_req *req); +static void ipa_fetch_deskprofile_hostinfo_done(struct tevent_req *subreq); +static void ipa_fetch_deskprofile_config_done(struct tevent_req *subreq); +static void ipa_fetch_deskprofile_rules_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_fetch_deskprofile_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_session_ctx *session_ctx, + const char *username) +{ + struct ipa_fetch_deskprofile_state *state; + struct tevent_req *req; + time_t now; + time_t refresh_interval; + time_t request_interval; + time_t next_request; + bool offline; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_fetch_deskprofile_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->session_ctx = session_ctx; + state->sdap_ctx = session_ctx->sdap_ctx; + state->ipa_options = session_ctx->ipa_options; + state->search_bases = session_ctx->deskprofile_search_bases; + state->username = username; + state->hosts = talloc_zero(state, struct ipa_common_entries); + if (state->hosts == NULL) { + ret = ENOMEM; + goto immediately; + } + state->rules = talloc_zero(state, struct ipa_common_entries); + if (state->rules == NULL) { + ret = ENOMEM; + goto immediately; + } + + if (state->search_bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Desktop Profile search base found.\n"); + ret = EINVAL; + goto immediately; + } + + state->sdap_op = sdap_id_op_create(state, + state->sdap_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + now = time(NULL); + + request_interval = dp_opt_get_int(state->ipa_options, + IPA_DESKPROFILE_REQUEST_INTERVAL); + /* This value is in minutes ... */ + request_interval *= MINUTE_IN_SECONDS; + + if (state->session_ctx->no_rules_found && + now < session_ctx->last_request + request_interval) { + next_request = (session_ctx->last_request + request_interval - now); + /* This value is in seconds ... */ + next_request /= 60; + DEBUG(SSSDBG_TRACE_FUNC, + "No rules were found in the last request.\n" + "Next request will happen in any login after %"SPRItime" minutes\n", + next_request); + ret = ENOENT; + goto immediately; + } + + state->session_ctx->no_rules_found = false; + + offline = be_is_offline(be_ctx); + DEBUG(SSSDBG_TRACE_ALL, "Connection status is [%s].\n", + offline ? "offline" : "online"); + + refresh_interval = dp_opt_get_int(state->ipa_options, + IPA_DESKPROFILE_REFRESH); + + if (offline || now < session_ctx->last_update + refresh_interval) { + DEBUG(SSSDBG_TRACE_FUNC, + "Performing cached Desktop Profile evaluation\n"); + ret = EOK; + goto immediately; + } + + ret = ipa_fetch_deskprofile_retry(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t +ipa_fetch_deskprofile_retry(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct ipa_fetch_deskprofile_state *state; + int ret; + + state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_id_op_connect_send() failed: %d (%s)\n", + ret, strerror(ret)); + + return ret; + } + + tevent_req_set_callback(subreq, ipa_fetch_deskprofile_connect_done, req); + + return EAGAIN; +} + +static void +ipa_fetch_deskprofile_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = ipa_fetch_deskprofile_hostinfo(req); + if (ret == EAGAIN) { + return; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t +ipa_fetch_deskprofile_hostinfo(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct ipa_fetch_deskprofile_state *state; + const char *hostname; + + state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); + hostname = dp_opt_get_string(state->ipa_options, IPA_HOSTNAME); + + subreq = ipa_host_info_send(state, + state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, + hostname, + state->session_ctx->host_map, + state->session_ctx->hostgroup_map, + state->session_ctx->host_search_bases); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_fetch_deskprofile_hostinfo_done, req); + + return EAGAIN; +} + +static void +ipa_fetch_deskprofile_hostinfo_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_fetch_deskprofile_state *state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); + + ret = ipa_host_info_recv(subreq, state, + &state->hosts->entry_count, + &state->hosts->entries, + &state->hosts->group_count, + &state->hosts->groups); + state->hosts->entry_subdir = DESKPROFILE_HOSTS_SUBDIR; + state->hosts->group_subdir = DESKPROFILE_HOSTGROUPS_SUBDIR; + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = ipa_get_host_attrs(state->ipa_options, + state->hosts->entry_count, + state->hosts->entries, + &state->ipa_host); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not locate IPA host.\n"); + goto done; + } + + subreq = ipa_deskprofile_get_config_send(state, + state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, + state->ipa_options); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_fetch_deskprofile_config_done, req); + return; + +done: + tevent_req_error(req, ret); +} + +static void +ipa_fetch_deskprofile_config_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_fetch_deskprofile_state *state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); + + ret = ipa_deskprofile_get_config_recv(subreq, state, &state->config); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = sysdb_store_custom(state->be_ctx->domain, IPA_DESKPROFILE_PRIORITY, + DESKPROFILE_CONFIG_SUBDIR, state->config); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save Desktop Profile policy\n"); + goto done; + } + + subreq = ipa_deskprofile_rule_info_send(state, + state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, + state->search_bases, + state->ipa_host, + state->be_ctx->domain, + state->username); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_fetch_deskprofile_rules_done, req); + return; + +done: + tevent_req_error(req, ret); +} + +static void +ipa_fetch_deskprofile_rules_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_fetch_deskprofile_state *state; + int dp_error; + errno_t ret; + bool found; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); + + ret = ipa_deskprofile_rule_info_recv(subreq, + state, + &state->rules->entry_count, + &state->rules->entries); + state->rules->entry_subdir = DESKPROFILE_RULES_SUBDIR; + talloc_zfree(subreq); + if (ret == ENOENT) { + /* Set ret to EOK so we can safely call sdap_id_op_done. */ + ret = EOK; + found = false; + } else if (ret == EOK) { + found = true; + } else { + goto done; + } + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ipa_fetch_deskprofile_retry(req); + if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* For now, let's completely purge the previous stored + * rules before saving the new ones */ + ret = ipa_common_purge_rules(state->be_ctx->domain, + DESKPROFILE_RULES_SUBDIR); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to remove Desktop Profile rules\n"); + goto done; + } + + if (!found) { + ret = ENOENT; + goto done; + } + + ret = ipa_common_save_rules(state->be_ctx->domain, + state->hosts, NULL, state->rules, + &state->session_ctx->last_update); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save Desktop Profile rules\n"); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t +ipa_fetch_deskprofile_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_pam_session_handler_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_session_ctx *session_ctx; + struct pam_data *pd; + + /* Those attributes are used for: + * - saving the deskprofile rules to the disk; + * - deleting the deskprofile rules from the disk; + * - contacting the deskprofile client that everything is ready; + */ + char *shortname; + char *domain; + char *user_dir; + uid_t uid; + gid_t gid; +}; + +static errno_t +ipa_pam_session_handler_get_deskprofile_user_info( + TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username, + char **_shortname, + char **_domain, + char **_user_dir, + uid_t *uid, + gid_t *gid); +static void ipa_pam_session_handler_done(struct tevent_req *subreq); +static errno_t +ipa_pam_session_handler_save_deskprofile_rules( + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + const char *username, /* fully-qualified */ + const char *user_dir, + const char *hostname, + uid_t uid, + gid_t gid); +static errno_t +ipa_pam_session_handler_notify_deskprofile_client(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uid_t uid, + const char *user_dir, + uint16_t prio); + + +struct tevent_req * +ipa_pam_session_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_session_ctx *session_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_pam_session_handler_state *state; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Retrieving Desktop Profile rules\n"); + req = tevent_req_create(mem_ctx, &state, + struct ipa_pam_session_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->ev = params->ev; + state->be_ctx = params->be_ctx; + state->session_ctx = session_ctx; + + /* Get all the user info that will be needed in order the delete the + * user's deskprofile directory from the disk, create the user's directory, + * save the fetched rules to the disk and notify the deskprofile client + * that this operation is done. */ + ret = ipa_pam_session_handler_get_deskprofile_user_info( + state, + params->domain, + pd->user, + &state->shortname, + &state->domain, + &state->user_dir, + &state->uid, + &state->gid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_get_user_info() failed [%d]: %s\n", + ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SESSION_ERR; + goto done; + } + + /* As no proper merging mechanism has been implemented yet ... + * let's just remove the user directory stored in the disk as it's + * going to be created again in case there's any rule fetched. */ + ret = ipa_deskprofile_rules_remove_user_dir(state->user_dir, + state->uid, + state->gid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_rules_remove_user_dir() failed.\n"); + state->pd->pam_status = PAM_SESSION_ERR; + goto done; + } + + subreq = ipa_fetch_deskprofile_send(state, state->ev, state->be_ctx, + state->session_ctx, pd->user); + if (subreq == NULL) { + state->pd->pam_status = PAM_SESSION_ERR; + goto done; + } + + tevent_req_set_callback(subreq, ipa_pam_session_handler_done, req); + return req; + +done: + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void +ipa_pam_session_handler_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_pam_session_handler_state *state; + const char *hostname; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_session_handler_state); + + ret = ipa_fetch_deskprofile_recv(subreq); + talloc_free(subreq); + + if (ret == ENOENT) { + DEBUG(SSSDBG_FUNC_DATA, "No Desktop Profile rules found\n"); + if (!state->session_ctx->no_rules_found) { + state->session_ctx->no_rules_found = true; + state->session_ctx->last_request = time(NULL); + } + state->pd->pam_status = PAM_SUCCESS; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to fetch Desktop Profile rules [%d]: %s\n", + ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + state->session_ctx->last_request = time(NULL); + + hostname = dp_opt_get_string(state->session_ctx->ipa_options, IPA_HOSTNAME); + ret = ipa_pam_session_handler_save_deskprofile_rules(state->be_ctx, + state->be_ctx->domain, + state->pd->user, + state->user_dir, + hostname, + state->uid, + state->gid); + + if (ret == EOK || ret == ENOENT) { + state->pd->pam_status = PAM_SUCCESS; + } else { + state->pd->pam_status = PAM_SESSION_ERR; + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +ipa_pam_session_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct ipa_pam_session_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_pam_session_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} + +static errno_t +ipa_pam_session_handler_get_deskprofile_user_info(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username, + char **_shortname, + char **_domain, + char **_user_dir, + uid_t *_uid, + gid_t *_gid) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_result *res = NULL; + char *shortname; + char *domain_name; + char *user_dir; + uid_t uid; + gid_t gid; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmp_ctx, username, + &shortname, &domain_name); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Failed to parse \"%s\" [%d]: %s\n", + username, ret, sss_strerror(ret)); + goto done; + } + + user_dir = talloc_asprintf(tmp_ctx, IPA_DESKPROFILE_RULES_USER_DIR"/%s/%s", + domain_name, shortname); + if (user_dir == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed!\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_getpwnam(tmp_ctx, domain, username, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_getpwnam() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (res->count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_getpwnam() returned unexpected amount of users. " + "Expected [%d], got [%d]\n", 1, res->count); + ret = EINVAL; + goto done; + } + + uid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_UIDNUM, 0); + gid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_GIDNUM, 0); + if (uid == 0 || gid == 0) { + /* As IPA doesn't handle root users ou groups, we know for sure that's + * something wrong in case we get uid = 0 or gid = 0. + */ + ret = EINVAL; + goto done; + } + + ret = EOK; + + *_shortname = talloc_steal(mem_ctx, shortname); + *_domain = talloc_steal(mem_ctx, domain_name); + *_user_dir = talloc_steal(mem_ctx, user_dir); + *_uid = uid; + *_gid = gid; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_pam_session_handler_save_deskprofile_rules( + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + const char *username, /* fully-qualified */ + const char *user_dir, + const char *hostname, + uid_t uid, + gid_t gid) +{ + TALLOC_CTX *tmp_ctx; + const char **attrs_get_cached_rules; + size_t rule_count; + struct sysdb_attrs **rules; + uint16_t priority; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Get Desktop Profile priority from sysdb */ + ret = deskprofile_get_cached_priority(be_ctx->domain, &priority); + if (ret == ENOENT) { + DEBUG(SSSDBG_FUNC_DATA, "No Desktop Profile priority found in sysdb\n"); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "deskprofile_get_cached_priority() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + + /* Get Desktop Profile rules from sysdb */ + attrs_get_cached_rules = deskprofile_get_attrs_to_get_cached_rules(tmp_ctx); + if (attrs_get_cached_rules == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "deskprofile_get_attrs_get_cached_rules() failed\n"); + ret = ENOMEM; + goto done; + } + ret = ipa_common_get_cached_rules(tmp_ctx, be_ctx->domain, + IPA_DESKPROFILE_RULE, + DESKPROFILE_RULES_SUBDIR, + attrs_get_cached_rules, + &rule_count, + &rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not retrieve Desktop Profile rules from the cache\n"); + goto done; + } + + /* nothing to do for FC */ + if (!rule_count) { + DEBUG(SSSDBG_FUNC_DATA, "No Desktop Profile rules found in sysdb\n"); + ret = ENOENT; + goto done; + } + + /* Create the user directory where the rules are going to be stored */ + ret = ipa_deskprofile_rules_create_user_dir(username, uid, gid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot create the user directory [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Save the rules to the disk */ + for (size_t i = 0; i < rule_count; i++) { + ret = ipa_deskprofile_rules_save_rule_to_disk(tmp_ctx, + priority, + rules[i], + domain, + hostname, + username, + uid, + gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to save a Desktop Profile Rule to disk [%d]: %s\n", + ret, sss_strerror(ret)); + continue; + } + } + + /* Notify FleetCommander that our side is done */ + ret = ipa_pam_session_handler_notify_deskprofile_client(be_ctx, + be_ctx->ev, + uid, + user_dir, + priority); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_pam_session_handler_notify_deskprofile_client() " + "failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void +ipa_pam_session_handler_notify_deskprofile_client_done(struct tevent_req *subreq); + +static errno_t +ipa_pam_session_handler_notify_deskprofile_client(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uid_t uid, + const char *user_dir, + uint16_t prio) +{ + struct sbus_connection *conn; + struct tevent_req *subreq; + + conn = sbus_connect_system(mem_ctx, ev, NULL, NULL); + if (conn == NULL) { + return ENOMEM; + } + + subreq = sbus_call_fleet_ProcessSSSDFiles_send(mem_ctx, conn, + SSS_FLEETCOMMANDERCLIENT_BUS, SSS_FLEETCOMMANDERCLIENT_PATH, + uid, user_dir, prio); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + talloc_free(conn); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_pam_session_handler_notify_deskprofile_client_done, + conn); + + return EOK; +} + +static void ipa_pam_session_handler_notify_deskprofile_client_done(struct tevent_req *subreq) +{ + struct sbus_connection *conn; + errno_t ret; + + conn = tevent_req_callback_data(subreq, struct sbus_connection); + + ret = sbus_call_fleet_ProcessSSSDFiles_recv(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Error sending sbus message [%d]: %s\n", + ret, sss_strerror(ret)); + } + + talloc_free(conn); +} diff --git a/src/providers/ipa/ipa_session.h b/src/providers/ipa/ipa_session.h new file mode 100644 index 0000000..0c4d54f --- /dev/null +++ b/src/providers/ipa/ipa_session.h @@ -0,0 +1,54 @@ +/* + SSSD + + IPA Backend Module -- Session Management + + Authors: + Fabiano Fidêncio + + Copyright (C) 2017 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef IPA_SESSION_H_ +#define IPA_SESSION_H_ + +#include "providers/ldap/ldap_common.h" + +struct ipa_session_ctx { + struct sdap_id_ctx *sdap_ctx; + struct dp_option *ipa_options; + time_t last_update; + time_t last_request; + bool no_rules_found; + + struct sdap_attr_map *host_map; + struct sdap_attr_map *hostgroup_map; + struct sdap_search_base **deskprofile_search_bases; + struct sdap_search_base **host_search_bases; +}; + +struct tevent_req * +ipa_pam_session_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_session_ctx *session_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +ipa_pam_session_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +#endif /* IPA_SESSION_H_ */ diff --git a/src/providers/ipa/ipa_srv.c b/src/providers/ipa/ipa_srv.c new file mode 100644 index 0000000..7477711 --- /dev/null +++ b/src/providers/ipa/ipa_srv.c @@ -0,0 +1,224 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "util/util.h" +#include "resolv/async_resolv.h" +#include "providers/fail_over_srv.h" +#include "providers/ipa/ipa_srv.h" + +#define IPA_DNS_LOCATION "_location" + +struct ipa_srv_plugin_ctx { + struct resolv_ctx *resolv_ctx; + const char *hostname; + const char *ipa_domain; +}; + +struct ipa_srv_plugin_ctx * +ipa_srv_plugin_ctx_init(TALLOC_CTX *mem_ctx, + struct resolv_ctx *resolv_ctx, + const char *hostname, + const char *ipa_domain) +{ + struct ipa_srv_plugin_ctx *ctx = NULL; + + ctx = talloc_zero(mem_ctx, struct ipa_srv_plugin_ctx); + if (ctx == NULL) { + return NULL; + } + + ctx->resolv_ctx = resolv_ctx; + + ctx->hostname = talloc_strdup(ctx, hostname); + if (ctx->hostname == NULL) { + goto fail; + } + + ctx->ipa_domain = talloc_strdup(ctx, ipa_domain); + if (ctx->ipa_domain == NULL) { + goto fail; + } + + return ctx; + +fail: + talloc_free(ctx); + return NULL; +} + +struct ipa_srv_plugin_state { + char *dns_domain; + uint32_t ttl; + struct fo_server_info *primary_servers; + size_t num_primary_servers; + struct fo_server_info *backup_servers; + size_t num_backup_servers; +}; + +static void ipa_srv_plugin_done(struct tevent_req *subreq); + +/* If IPA server supports sites, we will use + * _locations.hostname.discovery_domain for primary servers and + * discovery_domain for backup servers. If the server does not support sites or + * client's SRV record is not found, we will use the latter for primary + * servers, setting backup servers to NULL */ +struct tevent_req *ipa_srv_plugin_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *service, + const char *protocol, + const char *discovery_domain, + void *pvt) +{ + struct ipa_srv_plugin_state *state = NULL; + struct ipa_srv_plugin_ctx *ctx = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + const char *primary_domain = NULL; + const char *backup_domain = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_srv_plugin_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ctx = talloc_get_type(pvt, struct ipa_srv_plugin_ctx); + if (ctx == NULL) { + ret = EINVAL; + goto immediately; + } + + if (discovery_domain != NULL) { + backup_domain = talloc_strdup(state, discovery_domain); + } else { + backup_domain = talloc_strdup(state, ctx->ipa_domain); + } + if (backup_domain == NULL) { + ret = ENOMEM; + goto immediately; + } + + if (strchr(ctx->hostname, '.') == NULL) { + /* not FQDN, append domain name */ + primary_domain = talloc_asprintf(state, IPA_DNS_LOCATION ".%s.%s", + ctx->hostname, backup_domain); + } else { + primary_domain = talloc_asprintf(state, IPA_DNS_LOCATION ".%s", + ctx->hostname); + } + if (primary_domain == NULL) { + ret = ENOMEM; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "About to discover primary and " + "backup servers\n"); + + subreq = fo_discover_servers_send(state, ev, ctx->resolv_ctx, service, + protocol, primary_domain, backup_domain); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_srv_plugin_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void ipa_srv_plugin_done(struct tevent_req *subreq) +{ + struct ipa_srv_plugin_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_srv_plugin_state); + + ret = fo_discover_servers_recv(state, subreq, &state->dns_domain, + &state->ttl, + &state->primary_servers, + &state->num_primary_servers, + &state->backup_servers, + &state->num_backup_servers); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Got %zu primary and %zu backup servers\n", + state->num_primary_servers, state->num_backup_servers); + + tevent_req_done(req); +} + +errno_t ipa_srv_plugin_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_primary_servers, + size_t *_num_primary_servers, + struct fo_server_info **_backup_servers, + size_t *_num_backup_servers) +{ + struct ipa_srv_plugin_state *state = NULL; + state = tevent_req_data(req, struct ipa_srv_plugin_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_primary_servers) { + *_primary_servers = talloc_steal(mem_ctx, state->primary_servers); + } + + if (_num_primary_servers) { + *_num_primary_servers = state->num_primary_servers; + } + + if (_backup_servers) { + *_backup_servers = talloc_steal(mem_ctx, state->backup_servers); + } + + if (_num_backup_servers) { + *_num_backup_servers = state->num_backup_servers; + } + + if (_dns_domain) { + *_dns_domain = talloc_steal(mem_ctx, state->dns_domain); + } + + if (_ttl) { + *_ttl = state->ttl; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_srv.h b/src/providers/ipa/ipa_srv.h new file mode 100644 index 0000000..d089c9f --- /dev/null +++ b/src/providers/ipa/ipa_srv.h @@ -0,0 +1,48 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef __IPA_SRV_H__ +#define __IPA_SRV_H__ + +struct ipa_srv_plugin_ctx; + +struct ipa_srv_plugin_ctx * +ipa_srv_plugin_ctx_init(TALLOC_CTX *mem_ctx, + struct resolv_ctx *resolv_ctx, + const char *hostname, + const char *ipa_domain); + +struct tevent_req *ipa_srv_plugin_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *service, + const char *protocol, + const char *discovery_domain, + void *pvt); + +errno_t ipa_srv_plugin_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_primary_servers, + size_t *_num_primary_servers, + struct fo_server_info **_backup_servers, + size_t *_num_backup_servers); + +#endif /* __IPA_SRV_H__ */ diff --git a/src/providers/ipa/ipa_subdomains.c b/src/providers/ipa/ipa_subdomains.c new file mode 100644 index 0000000..075f6f4 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains.c @@ -0,0 +1,3180 @@ +/* + SSSD + + IPA Subdomains Module + + Authors: + Sumit Bose + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_ops.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ipa/ipa_opts.h" +#include "providers/ipa/ipa_config.h" +#ifdef BUILD_PASSKEY +#include "providers/ipa/ipa_subdomains_passkey.h" +#endif /* BUILD_PASSKEY */ + +#include + +#define SUBDOMAINS_FILTER "objectclass=ipaNTTrustedDomain" +#define MASTER_DOMAIN_FILTER "objectclass=ipaNTDomainAttrs" +#define RANGE_FILTER "objectclass=ipaIDRange" + +#define IPA_FLATNAME "ipaNTFlatName" +#define IPA_SID "ipaNTSecurityIdentifier" +#define IPA_ADDITIONAL_SUFFIXES "ipaNTAdditionalSuffixes" +#define IPA_SID_BLACKLIST_INCOMING "ipaNTSIDBlacklistIncoming" + +#define OBJECTCLASS "objectClass" + +#define IPA_ASSIGNED_ID_VIEW "ipaAssignedIDView" + +#define IPA_DOMAIN_RESOLUTION_ORDER "ipaDomainResolutionOrder" + +/* do not refresh more often than every 5 seconds for now */ +#define IPA_SUBDOMAIN_REFRESH_LIMIT 5 + +#define IPA_SUBDOMAIN_DISABLED_PERIOD 3600 + +#define IPA_OC_CERTMAP_CONFIG_OBJECT "ipaCertMapConfigObject" +#define IPA_CERTMAP_PROMPT_USERNAME "ipaCertMapPromptUserName" + +#define IPA_OC_CERTMAP_RULE "ipaCertMapRule" +#define IPA_CERTMAP_MAPRULE "ipaCertMapMapRule" +#define IPA_CERTMAP_MATCHRULE "ipaCertMapMatchRule" +#define IPA_CERTMAP_PRIORITY "ipaCertMapPriority" +#define IPA_ENABLED_FLAG "ipaEnabledFlag" +#define IPA_TRUE_VALUE "TRUE" +#define IPA_ASSOCIATED_DOMAIN "associatedDomain" +#define IPA_PASSKEY_VERIFICATION "ipaRequireUserVerification" +#define IPA_PASSKEY_CONFIG_FILTER "cn=passkeyconfig" + +#define OBJECTCLASS "objectClass" + +#define CERTMAP_FILTER "(|(&("OBJECTCLASS"="IPA_OC_CERTMAP_RULE")" \ + "("IPA_ENABLED_FLAG"="IPA_TRUE_VALUE"))" \ + "("OBJECTCLASS"="IPA_OC_CERTMAP_CONFIG_OBJECT"))" + +/* It doesn't make sense to resolve more servers than this from the SRV + * lookup because kinit would time out before we are able to cycle + * through the whole list + */ +#define MAX_SERVERS_FROM_SRV 5 + +struct ipa_sd_k5_svc_list { + struct krb5_service *k5svc; + + struct ipa_sd_k5_svc_list *next; + struct ipa_sd_k5_svc_list *prev; +}; + +static errno_t +ipa_subdom_reinit(struct ipa_subdomains_ctx *ctx) +{ + errno_t ret; + bool canonicalize = false; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Re-initializing domain %s\n", ctx->be_ctx->domain->name); + + if (ctx->ipa_id_ctx->ipa_options->auth_ctx != NULL + && ctx->ipa_id_ctx->ipa_options->auth_ctx->krb5_auth_ctx != NULL + && ctx->ipa_id_ctx->ipa_options->auth_ctx->krb5_auth_ctx->opts != NULL + ) { + canonicalize = dp_opt_get_bool( + ctx->ipa_id_ctx->ipa_options->auth_ctx->krb5_auth_ctx->opts, + KRB5_CANONICALIZE); + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "Auth provider data is not available, " + "most probably because the auth provider " + "is not 'ipa'. Kerberos configuration " + "snippet to set the 'canonicalize' option " + "will not be created.\n"); + } + + ret = sss_write_krb5_conf_snippet( + dp_opt_get_string(ctx->ipa_id_ctx->ipa_options->basic, + IPA_KRB5_CONFD_PATH), + canonicalize, false); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "sss_write_krb5_conf_snippet failed.\n"); + /* Just continue */ + } + + ret = sysdb_master_domain_update(ctx->be_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_master_domain_update failed.\n"); + return ret; + } + + ret = sysdb_update_subdomains(ctx->be_ctx->domain, ctx->be_ctx->cdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_subdomains failed.\n"); + return ret; + } + + ret = sss_write_domain_mappings(ctx->be_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sss_krb5_write_mappings failed.\n"); + /* Just continue */ + } + + return EOK; +} + +struct priv_sss_debug { + int level; +}; + +static errno_t ipa_certmap_parse_results(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sdap_options *sdap_opts, + size_t count, + struct sysdb_attrs **reply, + struct certmap_info ***_certmap_list) +{ + struct certmap_info **certmap_list = NULL; + struct certmap_info *m; + const char *value; + const char **values; + size_t c; + size_t lc = 0; + int ret; + const char **ocs = NULL; + bool user_name_hint = false; + + certmap_list = talloc_zero_array(mem_ctx, struct certmap_info *, count + 1); + if (certmap_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + + for (c = 0; c < count; c++) { + ret = sysdb_attrs_get_string_array(reply[c], SYSDB_OBJECTCLASS, mem_ctx, + &ocs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing objectclasses for config objects.\n"); + ret = EINVAL; + goto done; + } + + if (string_in_list(IPA_OC_CERTMAP_CONFIG_OBJECT, discard_const(ocs), + false)) { + ret = sysdb_attrs_get_bool(reply[c], IPA_CERTMAP_PROMPT_USERNAME, + &user_name_hint); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read user name hint option, skipping.\n"); + } + continue; + } + + m = talloc_zero(certmap_list, struct certmap_info); + if (m == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_CN, &value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + m->name = talloc_strdup(m, value); + if (m->name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_CERTMAP_MATCHRULE, &value); + if (ret == EOK) { + m->match_rule = talloc_strdup(m, value); + if (m->match_rule == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_CERTMAP_MAPRULE, &value); + if (ret == EOK) { + m->map_rule = talloc_strdup(m, value); + if (m->map_rule == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string_array(reply[c], IPA_ASSOCIATED_DOMAIN, m, + &values); + if (ret == EOK) { + m->domains = values; + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(reply[c], IPA_CERTMAP_PRIORITY, + &m->priority); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } else if (ret == ENOENT) { + m->priority = SSS_CERTMAP_MIN_PRIO; + } + + certmap_list[lc++] = m; + } + + certmap_list[lc] = NULL; + + ret = sdap_setup_certmap(sdap_opts->sdap_certmap_ctx, certmap_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_setup_certmap failed.\n"); + goto done; + } + + ret = sysdb_update_certmap(domain->sysdb, certmap_list, user_name_hint); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_certmap failed.\n"); + goto done; + } + + if (_certmap_list != NULL) { + *_certmap_list = certmap_list; + } else { + talloc_free(certmap_list); + } + + ret = EOK; + +done: + talloc_free(ocs); + if (ret != EOK) { + talloc_free(certmap_list); + } + + return ret; +} + +static errno_t ipa_subdom_enumerates(struct sss_domain_info *parent, + struct sysdb_attrs *attrs, + bool *_enumerates) +{ + errno_t ret; + const char *name; + + ret = sysdb_attrs_get_string(attrs, IPA_CN, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + return ret; + } + + *_enumerates = subdomain_enumerates(parent, name); + return EOK; +} + +static errno_t ipa_subdom_get_forest(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + struct sysdb_attrs *attrs, + char **_forest) +{ + int ret; + struct ldb_dn *dn = NULL; + const char *name; + const struct ldb_val *val; + char *forest = NULL; + + dn = ipa_subdom_ldb_dn(mem_ctx, ldb_ctx, attrs); + if (dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_subdom_ldb_dn failed.\n"); + ret = EIO; + goto done; + } + + if (ipa_subdom_is_member_dom(dn) == false) { + ret = sysdb_attrs_get_string(attrs, IPA_CN, &name); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + forest = talloc_strdup(mem_ctx, name); + if (forest == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "The forest name is %s\n", forest); + ret = EOK; + goto done; + } + + val = ldb_dn_get_component_val(dn, 1); + forest = talloc_strndup(mem_ctx, (const char *) val->data, val->length); + if (forest == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = EOK; +done: + talloc_free(dn); + + if (ret == EOK) { + *_forest = forest; + } + + return ret; +} + +static errno_t ipa_get_sd_trust_direction(struct sysdb_attrs *sd, + struct ipa_id_ctx *id_ctx, + struct ldb_context *ldb_ctx, + uint32_t *_direction) +{ + if (id_ctx->server_mode != NULL) { + return ipa_server_get_trust_direction(sd, ldb_ctx, _direction); + } else { + /* Clients do not have access to the trust objects's trust direction + * and don't generally care + */ + *_direction = 0; + return EOK; + } +} + +static errno_t ipa_subdom_store(struct sss_domain_info *parent, + struct ipa_id_ctx *id_ctx, + struct sdap_idmap_ctx *sdap_idmap_ctx, + struct sysdb_attrs *attrs) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + char *realm; + const char *flat; + const char *dns; + const char *id; + char *forest = NULL; + int ret; + bool use_id_mapping; + enum sss_domain_mpg_mode mpg_mode; + bool enumerate; + uint32_t direction; + struct ldb_message_element *alternative_domain_suffixes = NULL; + struct range_info *range; + const char *forest_id; + + tmp_ctx = talloc_new(parent); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_attrs_get_string(attrs, IPA_CN, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + realm = get_uppercase_realm(tmp_ctx, name); + if (!realm) { + ret = ENOMEM; + goto done; + } + + dns = talloc_strdup(tmp_ctx, name); + if (dns == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(attrs, IPA_FLATNAME, &flat); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(attrs, IPA_TRUSTED_DOMAIN_SID, &id); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_el_ext(attrs, IPA_ADDITIONAL_SUFFIXES, false, + &alternative_domain_suffixes); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + ret = ipa_subdom_get_forest(tmp_ctx, sysdb_ctx_get_ldb(parent->sysdb), + attrs, &forest); + if (ret != EOK) { + goto done; + } + + ret = ipa_subdom_enumerates(parent, attrs, &enumerate); + if (ret != EOK) { + goto done; + } + + ret = ipa_get_sd_trust_direction(attrs, id_ctx, + sysdb_ctx_get_ldb(parent->sysdb), + &direction); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_get_sd_trust_direction failed: %d\n", ret); + goto done; + } + + if (id_ctx->server_mode != NULL) { + DEBUG(SSSDBG_FUNC_DATA, + "Trust type of [%s]: %s\n", name, ipa_trust_dir2str(direction)); + } + + /* First see if there is an ID range for the domain. */ + ret = sysdb_get_range(tmp_ctx, parent->sysdb, id, &range); + if (ret == ENOENT) { + /* Check if there is ID range for the forest root. We need to find the + * domain in sysdb since the sss_domain_info object might not be yet + * created. */ + ret = sysdb_subdomain_get_id_by_name(tmp_ctx, parent->sysdb, forest, + &forest_id); + if (ret == EOK) { + ret = sysdb_get_range(tmp_ctx, parent->sysdb, forest_id, &range); + } + } + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "Unable to find ID range for [%s] [%d]: %s\n", + name, ret, sss_strerror(ret)); + } + mpg_mode = ret == EOK ? range->mpg_mode : MPG_DEFAULT; + + DEBUG(SSSDBG_TRACE_FUNC, "Range mpg mode for %s: %s\n", + name, str_domain_mpg_mode(mpg_mode)); + + if (mpg_mode == MPG_DEFAULT) { + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + sdap_idmap_ctx, name, id); + if (use_id_mapping == true) { + mpg_mode = MPG_ENABLED; + } else { + /* Domains that use the POSIX attributes set by the admin must + * inherit the MPG setting from the parent domain so that the + * auto_private_groups options works for trusted domains as well + */ + mpg_mode = get_domain_mpg_mode(parent); + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Domain mpg mode for %s: %s\n", + name, str_domain_mpg_mode(mpg_mode)); + + ret = sysdb_subdomain_store(parent->sysdb, name, realm, flat, dns, + id, mpg_mode, enumerate, forest, + direction, alternative_domain_suffixes); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_subdomain_store failed.\n"); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static struct krb5_service * +ipa_subdom_get_k5_svc(struct ipa_subdomains_ctx *ctx, + struct sss_domain_info *dom, + bool use_kdcinfo) +{ + struct ipa_sd_k5_svc_list *k5svc_ent; + + /* get the service by realm */ + DLIST_FOR_EACH(k5svc_ent, ctx->k5svc_list) { + if (strcasecmp(dom->realm, k5svc_ent->k5svc->realm) == 0) { + break; + } + } + + if (k5svc_ent != NULL) { + /* Already exists */ + return k5svc_ent->k5svc; + } + + /* Create a new service */ + k5svc_ent = talloc_zero(ctx, struct ipa_sd_k5_svc_list); + if (k5svc_ent == NULL) { + return NULL; + } + + k5svc_ent->k5svc = krb5_service_new(k5svc_ent, + ctx->be_ctx, + "IPA", + dom->realm, + use_kdcinfo, + (size_t) -1, + (size_t) -1); + if (k5svc_ent->k5svc == NULL) { + talloc_free(k5svc_ent); + return NULL; + } + DLIST_ADD(ctx->k5svc_list, k5svc_ent); + + return k5svc_ent->k5svc; +} + +static void ipa_subdom_remove_k5_svc(struct ipa_subdomains_ctx *ctx) +{ + /* Domain going away is such a rare operation that it makes + * more sense to just throw away the whole k5svc_list and let + * the write_kdcinfo request recreate them all again instead + * of coding up complex logic.. + */ + talloc_zfree(ctx->k5svc_list); +} + +static void ipa_subdom_remove_step(struct ipa_subdomains_ctx *ctx, + struct sss_domain_info *dom) +{ + if (dp_opt_get_bool(ctx->ipa_id_ctx->ipa_options->basic, + IPA_SERVER_MODE) == false) { + /* IPA clients keep track of krb5_service wrappers */ + return ipa_subdom_remove_k5_svc(ctx); + } else { + /* IPA servers keeps track of AD contexts */ + return ipa_ad_subdom_remove(ctx->be_ctx, ctx->ipa_id_ctx, dom); + } + +} + +static void ipa_subdom_store_step(struct sss_domain_info *parent, + struct ipa_id_ctx *id_ctx, + struct sdap_idmap_ctx *sdap_idmap_ctx, + struct sysdb_attrs *attrs) +{ + int ret; + + ret = ipa_subdom_store(parent, id_ctx, sdap_idmap_ctx, attrs); + if (ret == ERR_TRUST_NOT_SUPPORTED) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unsupported trust type, skipping\n"); + } else if (ret) { + /* Nothing we can do about the error. */ + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to parse subdom data, " + "will try to use cached subdomain\n"); + } +} + +static errno_t add_dom_sids_to_list(TALLOC_CTX *mem_ctx, const char **sids, + char ***list) +{ + size_t c; + errno_t ret; + + for (c = 0; sids != NULL && sids[c] != NULL; c++) { + if (is_domain_sid(sids[c])) { + ret = add_string_to_list(mem_ctx, sids[c], list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_string_to_list failed.\n"); + return ret; + } + } + } + + return EOK; +} + +static errno_t ipa_get_disabled_domain_sids(TALLOC_CTX *mem_ctx, size_t count, + struct sysdb_attrs **reply, + char ***disabled_domain_sids) +{ + size_t c; + char **dom_sid_list = NULL; + const char **tmp_list; + int ret; + + for (c = 0; c < count; c++) { + ret = sysdb_attrs_get_string_array(reply[c], IPA_SID_BLACKLIST_INCOMING, + mem_ctx, &tmp_list); + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string_array failed, list of disabled " + "domains might be incomplete.\n"); + } + continue; + } + + ret = add_dom_sids_to_list(mem_ctx, tmp_list, &dom_sid_list); + talloc_free(tmp_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_dom_sids_to_list failed.\n"); + talloc_free(dom_sid_list); + return ret; + } + } + + *disabled_domain_sids = dom_sid_list; + + return EOK; +} + +static errno_t ipa_subdomains_check_domain_state(struct sss_domain_info *dom, + char **disabled_domain_sids) +{ + int ret; + + if (dom->domain_id == NULL) { + return EINVAL; + } + + if (disabled_domain_sids != NULL + && string_in_list(dom->domain_id, disabled_domain_sids, true)) { + DEBUG(SSSDBG_TRACE_ALL, "Domain [%s] is disabled on the server.\n", + dom->name); + /* disable domain if not already disabled */ + if (sss_domain_get_state(dom) != DOM_DISABLED) { + sss_domain_set_state(dom, DOM_DISABLED); + ret = sysdb_domain_set_enabled(dom->sysdb, dom->name, false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_domain_set_enabled failed.\n"); + return ret; + } + + ret = sysdb_subdomain_content_delete(dom->sysdb, dom->name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_subdomain_content_delete failed.\n"); + return ret; + } + } + } else { + /* enabled domain if it was disabled */ + DEBUG(SSSDBG_TRACE_ALL, "Domain [%s] is enabled on the server.\n", + dom->name); + if (sss_domain_get_state(dom) == DOM_DISABLED) { + sss_domain_set_state(dom, DOM_ACTIVE); + ret = sysdb_domain_set_enabled(dom->sysdb, dom->name, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_domain_set_enabled failed.\n"); + return ret; + } + } + } + + return EOK; +} + + +static void ipa_subdomains_update_dom_state(struct sss_domain_info *parent, + int count, + struct sysdb_attrs **reply) +{ + int ret; + struct sss_domain_info *dom; + char **disabled_domain_sids = NULL; + + ret = ipa_get_disabled_domain_sids(reply, count, reply, + &disabled_domain_sids); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_disabled_domain_sids failed, " + "assuming no domain is disabled.\n"); + disabled_domain_sids = NULL; + } + + for (dom = get_next_domain(parent, SSS_GND_DESCEND|SSS_GND_INCLUDE_DISABLED); + dom && IS_SUBDOMAIN(dom); /* if we get back to a parent, stop */ + dom = get_next_domain(dom, SSS_GND_INCLUDE_DISABLED)) { + + /* check if domain should be disabled/enabled */ + ret = ipa_subdomains_check_domain_state(dom, disabled_domain_sids); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to check domain state, " + "state of domain [%s] might be wrong.\n", dom->name); + } + } +} + +static errno_t ipa_subdomains_refresh(struct ipa_subdomains_ctx *ctx, + int count, struct sysdb_attrs **reply, + bool *changes) +{ + struct sss_domain_info *parent, *dom; + bool handled[count]; + const char *value; + int c, h; + int ret; + + parent = ctx->be_ctx->domain; + memset(handled, 0, sizeof(bool) * count); + h = 0; + + if (changes == NULL) { + return EINVAL; + } + *changes = false; + + /* check existing subdomains */ + for (dom = get_next_domain(parent, SSS_GND_DESCEND); + dom && IS_SUBDOMAIN(dom); /* if we get back to a parent, stop */ + dom = get_next_domain(dom, 0)) { + for (c = 0; c < count; c++) { + if (handled[c]) { + continue; + } + ret = sysdb_attrs_get_string(reply[c], IPA_CN, &value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + if (strcmp(value, dom->name) == 0) { + break; + } + } + + if (c >= count) { + /* ok this subdomain does not exist anymore, let's clean up */ + sss_domain_set_state(dom, DOM_DISABLED); + ret = sysdb_subdomain_delete(dom->sysdb, dom->name); + if (ret != EOK) { + goto done; + } + + ipa_subdom_remove_step(ctx, dom); + } else { + /* ok let's try to update it */ + ipa_subdom_store_step(parent, ctx->ipa_id_ctx, + ctx->sdap_id_ctx->opts->idmap_ctx, + reply[c]); + handled[c] = true; + h++; + } + } + + if (count == h) { + /* all domains were already accounted for and have been updated */ + ret = EOK; + goto done; + } + + /* if we get here it means we have changes to the subdomains list */ + *changes = true; + + for (c = 0; c < count; c++) { + if (handled[c]) { + continue; + } + + ipa_subdom_store_step(parent, ctx->ipa_id_ctx, + ctx->sdap_id_ctx->opts->idmap_ctx, + reply[c]); + } + + ret = EOK; +done: + if (ret != EOK) { + ctx->last_refreshed = 0; + } else { + ctx->last_refreshed = time(NULL); + } + + return ret; +} + +static void clean_view_name(struct sss_domain_info *domain) +{ + struct sss_domain_info *dom = domain; + + while (dom) { + dom->has_views = false; + talloc_free(discard_const(dom->view_name)); + dom->view_name = NULL; + dom = get_next_domain(dom, SSS_GND_DESCEND); + } +} + +static errno_t ipa_apply_view(struct sss_domain_info *domain, + struct ipa_id_ctx *ipa_id_ctx, + const char *view_name, + bool read_at_init, + struct confdb_ctx *confdb) +{ + const char *current = ipa_id_ctx->view_name; + struct sysdb_ctx *sysdb = domain->sysdb; + bool in_transaction = false; + errno_t sret; + errno_t ret; + + DEBUG(SSSDBG_TRACE_ALL, "read_at_init [%s] current view [%s]\n", + read_at_init ? "true" : "false", ipa_id_ctx->view_name); + + if (current != NULL && strcmp(current, view_name) != 0 && read_at_init) { + DEBUG(SSSDBG_CRIT_FAILURE, "View name changed, this is not supported " + "at runtime. Please restart SSSD to get the new view applied.\n"); + return EOK; + } + + if (current != NULL && strcmp(current, view_name) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "View name did not change.\n"); + return EOK; + } + + DEBUG(SSSDBG_TRACE_FUNC, "View name changed to [%s].\n", view_name); + + /* View name changed. If there was a non-default non-local view + * was used the tree in cache containing the override values is + * removed. In all cases sysdb_invalidate_overrides() is called to + * remove the override attribute from the cached user objects. + * + * Typically ctx->sd_ctx->id_ctx->view_name == NULL means that the + * cache was empty but there was a bug in with caused that the + * view name was not written to the cache at all. In this case the + * cache must be invalidated if the new view is not the + * default-view as well. */ + + if (current != NULL || !is_default_view(view_name)) { + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to start transaction " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + in_transaction = true; + + if (!is_default_view(current) && !is_local_view(current)) { + /* Old view was not the default view, delete view tree */ + ret = sysdb_delete_view_tree(sysdb, current); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to delete old view tree " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } + + ret = sysdb_invalidate_overrides(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, " Unable to invalidate overrides " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to commint transaction " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + in_transaction = false; + } + + ret = sysdb_update_view_name(sysdb, view_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot update view name " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + talloc_free(ipa_id_ctx->view_name); + ipa_id_ctx->view_name = talloc_strdup(ipa_id_ctx, view_name); + if (ipa_id_ctx->view_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot copy view name.\n"); + ret = ENOMEM; + goto done; + } + + if (!read_at_init) { + /* refresh view data of all domains at startup, since + * sysdb_master_domain_update and sysdb_update_subdomains might have + * been called earlier without the proper view name the name is + * cleaned here before the calls. This is acceptable because this is + * the initial setup (!read_at_init). */ + clean_view_name(domain); + ret = sysdb_master_domain_update(domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_master_domain_update failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_update_subdomains(domain, confdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_subdomains failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n"); + } + } + + return ret; +} + +struct ipa_subdomains_ranges_state { + struct sss_domain_info *domain; +}; + +static void ipa_subdomains_ranges_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_ranges_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_ranges_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + const char *attrs[] = { OBJECTCLASS, IPA_CN, + IPA_BASE_ID, IPA_BASE_RID, IPA_SECONDARY_BASE_RID, + IPA_ID_RANGE_SIZE, IPA_TRUSTED_DOMAIN_SID, + IPA_RANGE_TYPE, IPA_ID_RANGE_MPG, NULL }; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_ranges_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sd_ctx->ranges_search_bases == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No search base is set\n"); + ret = EOK; + goto immediately; + } + + state->domain = sd_ctx->be_ctx->domain; + + subreq = sdap_search_bases_send(state, ev, sd_ctx->sdap_id_ctx->opts, sh, + sd_ctx->ranges_search_bases, NULL, false, + 0, RANGE_FILTER, attrs, NULL); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_ranges_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ipa_subdomains_ranges_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_ranges_state *state; + struct tevent_req *req; + struct range_info **range_list; + struct sysdb_attrs **reply; + size_t reply_count; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_ranges_state); + + ret = sdap_search_bases_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get data from LDAP [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_ranges_parse_results(state, state->domain->name, + reply_count, reply, &range_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to parse range resulg [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_update_ranges(state->domain->sysdb, range_list); + talloc_free(range_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to update ranges [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_ranges_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +#define IPA_CERTMAP_SEARCH_BASE_TEMPLATE "cn=certmap,%s" + +struct ipa_subdomains_certmap_state { + struct sss_domain_info *domain; + struct sdap_options *sdap_opts; +}; + +static void ipa_subdomains_certmap_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_certmap_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_certmap_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + char *ldap_basedn; + char *search_base; + const char *attrs[] = { OBJECTCLASS, IPA_CN, + IPA_CERTMAP_MAPRULE, IPA_CERTMAP_MATCHRULE, + IPA_CERTMAP_PRIORITY, IPA_ASSOCIATED_DOMAIN, + IPA_CERTMAP_PROMPT_USERNAME, + NULL }; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_certmap_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->domain = sd_ctx->be_ctx->domain; + state->sdap_opts = sd_ctx->sdap_id_ctx->opts; + + ret = domain_to_basedn(state, state->domain->name, &ldap_basedn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "domain_to_basedn failed.\n"); + goto immediately; + } + + search_base = talloc_asprintf(state, IPA_CERTMAP_SEARCH_BASE_TEMPLATE, + ldap_basedn); + if (search_base == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_get_generic_send(state, ev, sd_ctx->sdap_id_ctx->opts, sh, + search_base, LDAP_SCOPE_SUBTREE, + CERTMAP_FILTER, + attrs, NULL, 0, 0, false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_certmap_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ipa_subdomains_certmap_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_certmap_state *state; + struct tevent_req *req; + struct sysdb_attrs **reply; + size_t reply_count; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_certmap_state); + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get data from LDAP [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_certmap_parse_results(state, state->domain, + state->sdap_opts, + reply_count, reply, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to parse certmap results [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_certmap_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_subdomains_master_state { + struct sss_domain_info *domain; + struct ipa_options *ipa_options; +}; + +static void ipa_subdomains_master_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_master_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_master_state *state; + struct sss_domain_info *domain; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + const char *attrs[] = { IPA_CN, IPA_FLATNAME, IPA_SID, + IPA_ADDITIONAL_SUFFIXES, NULL }; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_master_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sd_ctx->master_search_bases == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No search base is set\n"); + ret = EOK; + goto immediately; + } + + state->domain = domain = sd_ctx->be_ctx->domain; + state->ipa_options = sd_ctx->ipa_id_ctx->ipa_options; + + ret = sysdb_master_domain_update(domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to update master domain [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediately; + } + + if (domain->flat_name != NULL && domain->domain_id != NULL + && domain->dns_name != NULL + && domain->realm != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Master record is up to date.\n"); + ret = EOK; + goto immediately; + } + + subreq = sdap_search_bases_return_first_send(state, ev, + sd_ctx->sdap_id_ctx->opts, sh, + sd_ctx->master_search_bases, NULL, false, + 0, MASTER_DOMAIN_FILTER, attrs, NULL); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_master_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ipa_subdomains_master_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_master_state *state; + struct tevent_req *req; + struct sysdb_attrs **reply; + size_t reply_count; + const char *flat = NULL; + const char *dns = NULL; + const char *id = NULL; + const char *realm = NULL; + struct ldb_message_element *alternative_domain_suffixes = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_master_state); + + ret = sdap_search_bases_return_first_recv(subreq, state, + &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get data from LDAP [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (reply_count > 0) { + ret = sysdb_attrs_get_string(reply[0], IPA_FLATNAME, &flat); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_get_string(reply[0], IPA_SID, &id); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_get_el_ext(reply[0], IPA_ADDITIONAL_SUFFIXES, false, + &alternative_domain_suffixes); + if (ret != EOK && ret != ENOENT) { + goto done; + } + } else { + /* All search paths are searched and no master domain record was + * found. + * + * A default IPA installation will not have a master domain record, + * this is only created by ipa-adtrust-install. Nevertheless we should + * continue to read other data like the idview on IPA clients. */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Master domain record not found!\n"); + } + + realm = dp_opt_get_string(state->ipa_options->basic, IPA_KRB5_REALM); + if (realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm for IPA?\n"); + ret = EINVAL; + goto done; + } + + dns = dp_opt_get_string(state->ipa_options->basic, IPA_DOMAIN); + if (dns == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No domain name for IPA?\n"); + ret = EINVAL; + goto done; + } + + ret = sysdb_master_domain_add_info(state->domain, realm, flat, dns, id, NULL, + alternative_domain_suffixes); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add master domain info " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_master_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_subdomains_slave_state { + struct ipa_subdomains_ctx *sd_ctx; + struct be_ctx *be_ctx; + struct ipa_id_ctx *ipa_id_ctx; +}; + +static void ipa_subdomains_slave_search_done(struct tevent_req *subreq); +static void ipa_subdomains_slave_trusts_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_slave_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_slave_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + const char *attrs[] = { IPA_CN, IPA_FLATNAME, IPA_TRUSTED_DOMAIN_SID, + IPA_TRUST_DIRECTION, IPA_ADDITIONAL_SUFFIXES, + IPA_SID_BLACKLIST_INCOMING, NULL }; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_slave_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sd_ctx->search_bases == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No search base is set\n"); + ret = EOK; + goto immediately; + } + + state->sd_ctx = sd_ctx; + state->be_ctx = sd_ctx->be_ctx; + state->ipa_id_ctx = sd_ctx->ipa_id_ctx; + + subreq = sdap_search_bases_send(state, ev, sd_ctx->sdap_id_ctx->opts, sh, + sd_ctx->search_bases, NULL, false, + 0, SUBDOMAINS_FILTER, attrs, NULL); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_slave_search_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ipa_enable_enterprise_principals(struct be_ctx *be_ctx) +{ + int ret; + struct sss_domain_info *d; + TALLOC_CTX *tmp_ctx; + char **vals = NULL; + struct dp_module *auth; + struct krb5_ctx *krb5_auth_ctx; + + d = get_domains_head(be_ctx->domain); + + while (d != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "checking [%s].\n", d->name); + if (d->upn_suffixes != NULL) { + break; + } + d = get_next_domain(d, SSS_GND_DESCEND); + } + + if (d == NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "No UPN suffixes found, " + "no need to enable enterprise principals.\n"); + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = confdb_get_param(be_ctx->cdb, tmp_ctx, be_ctx->conf_path, + ipa_def_krb5_opts[KRB5_USE_ENTERPRISE_PRINCIPAL].opt_name, + &vals); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_param failed.\n"); + goto done; + } + + if (vals[0]) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Parameter [%s] set in config file and will not be changed.\n", + ipa_def_krb5_opts[KRB5_USE_ENTERPRISE_PRINCIPAL].opt_name); + return EOK; + } + + auth = dp_target_module(be_ctx->provider, DPT_AUTH); + if (auth == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to find auth proivder.\n"); + ret = EINVAL; + goto done; + } + + krb5_auth_ctx = ipa_init_get_krb5_auth_ctx(dp_get_module_data(auth)); + if (krb5_auth_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to find auth proivder data.\n"); + ret = EINVAL; + goto done; + } + + ret = dp_opt_set_bool(krb5_auth_ctx->opts, + KRB5_USE_ENTERPRISE_PRINCIPAL, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "dp_opt_set_bool failed.\n"); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Enterprise principals enabled.\n"); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static void ipa_subdomains_slave_search_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_slave_state *state; + struct tevent_req *req; + struct sysdb_attrs **reply; + size_t reply_count; + bool has_changes = false; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_slave_state); + + ret = sdap_search_bases_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get data from LDAP [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_subdomains_refresh(state->sd_ctx, reply_count, reply, + &has_changes); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to refresh subdomains.\n"); + goto done; + } + + ret = ipa_enable_enterprise_principals(state->sd_ctx->be_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_enable_enterprise_principals failed. " + "Enterprise principals might not work as " + "expected.\n"); + } + + /* If there are no changes this step can be skipped, but + * ipa_subdomains_update_dom_state() must be called after that in all case + * to cover existing an newly added domains. Since the domain state is not + * handled by a domain flag but by the blacklist has_changes does not + * cover the state. */ + if (has_changes) { + ret = ipa_subdom_reinit(state->sd_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not reinitialize subdomains\n"); + goto done; + } + } + + ipa_subdomains_update_dom_state(state->sd_ctx->be_ctx->domain, + reply_count, reply); + + if (!has_changes || state->sd_ctx->ipa_id_ctx->server_mode == NULL) { + ret = EOK; + goto done; + } + + subreq = ipa_server_create_trusts_send(state, state->be_ctx->ev, + state->be_ctx, state->ipa_id_ctx, + state->be_ctx->domain); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_subdomains_slave_trusts_done, req); + return; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void ipa_subdomains_slave_trusts_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = ipa_server_create_trusts_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create trusts [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_slave_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_subdomains_view_name_state { + struct ipa_subdomains_ctx *sd_ctx; +}; + +static void ipa_subdomains_view_name_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_view_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_view_name_state *state; + struct sdap_attr_map_info *maps; + struct tevent_req *subreq; + struct tevent_req *req; + struct ipa_options *ipa_options; + const char *filter; + const char *attrs[] = {IPA_CN, OBJECTCLASS, NULL}; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_view_name_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sd_ctx->ipa_id_ctx->server_mode != NULL) { + /* Only get view on clients, on servers it is always 'default'. */ + ret = EOK; + goto immediately; + } + + state->sd_ctx = sd_ctx; + + ipa_options = sd_ctx->ipa_id_ctx->ipa_options; + + maps = talloc_zero_array(state, struct sdap_attr_map_info, 2); + if (maps == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero() failed\n"); + ret = ENOMEM; + goto immediately; + } + maps[0].map = ipa_options->view_map; + maps->num_attrs = IPA_OPTS_VIEW; + + filter = talloc_asprintf(state, "(&(objectClass=%s)(%s=%s))", + ipa_options->id->host_map[SDAP_OC_HOST].name, + ipa_options->id->host_map[SDAP_AT_HOST_FQDN].name, + dp_opt_get_string(ipa_options->basic, IPA_HOSTNAME)); + if (filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* We add SDAP_DEREF_FLG_SILENT because old IPA servers don't have + * the attribute we dereference, causing the deref call to fail. */ + subreq = sdap_deref_bases_return_first_send(state, ev, + sd_ctx->sdap_id_ctx->opts, sh, sd_ctx->host_search_bases, + maps, filter, attrs, IPA_ASSIGNED_ID_VIEW, + SDAP_DEREF_FLG_SILENT, 0); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_view_name_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ipa_subdomains_view_name_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_view_name_state *state; + struct tevent_req *req; + size_t reply_count; + struct sdap_deref_attrs **reply; + const char *view_name; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_view_name_state); + + ret = sdap_deref_bases_return_first_recv(subreq, state, + &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + /* Depending on the version 389ds return a different error code if the + * search for the view name failed because our dereference attribute + * ipaAssignedIDView is not known. Newer version return + * LDAP_UNAVAILABLE_CRITICAL_EXTENSION(12) which is translated to + * EOPNOTSUPP and older versions return LDAP_PROTOCOL_ERROR(2) which + * is returned as EIO. In both cases we have to assume that the server + * is not view aware and keep the view name unset. */ + if (ret == EOPNOTSUPP || ret == EIO) { + DEBUG(SSSDBG_TRACE_FUNC, "Unable to get view name, looks " \ + "like server does not support views.\n"); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_OP_FAILURE, "Unable to get view name [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (reply_count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No view found, using default.\n"); + view_name = SYSDB_DEFAULT_VIEW_NAME; + } else if (reply_count == 1) { + ret = sysdb_attrs_get_string(reply[0]->attrs, SYSDB_VIEW_NAME, + &view_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one object returned.\n"); + ret = EINVAL; + goto done; + } + + ret = ipa_apply_view(state->sd_ctx->be_ctx->domain, + state->sd_ctx->ipa_id_ctx, view_name, + state->sd_ctx->view_read_at_init, + state->sd_ctx->be_ctx->cdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set view [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + state->sd_ctx->view_read_at_init = true; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_view_name_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_subdomains_view_domain_resolution_order_state { + struct sss_domain_info *domain; + const char *view_name; +}; + +static void +ipa_subdomains_view_domain_resolution_order_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_view_domain_resolution_order_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_view_domain_resolution_order_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + const char *attrs[] = { IPA_DOMAIN_RESOLUTION_ORDER, NULL }; + char *ldap_basedn; + char *base; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_view_domain_resolution_order_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->domain = sd_ctx->be_ctx->domain; + state->view_name = sd_ctx->ipa_id_ctx->view_name; + + ret = domain_to_basedn(state, sd_ctx->be_ctx->domain->name, &ldap_basedn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "domain_to_basedn failed.\n"); + goto immediately; + } + + base = talloc_asprintf(state, "cn=%s,cn=views,cn=accounts,%s", + sd_ctx->ipa_id_ctx->view_name, ldap_basedn); + if (base == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_get_generic_send( + state, ev, sd_ctx->sdap_id_ctx->opts, sh, + base, LDAP_SCOPE_BASE, NULL, attrs, NULL, 0, + dp_opt_get_int(sd_ctx->sdap_id_ctx->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_view_domain_resolution_order_done, + req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void +ipa_subdomains_view_domain_resolution_order_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_view_domain_resolution_order_state *state; + struct tevent_req *req; + size_t reply_count; + struct sysdb_attrs **reply; + const char *domain_resolution_order; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, + struct ipa_subdomains_view_domain_resolution_order_state); + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to get view name [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (reply_count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one object returned.\n"); + ret = EINVAL; + goto done; + } else if (reply_count == 0) { + domain_resolution_order = NULL; + } else { + /* reply_count == 1 */ + ret = sysdb_attrs_get_string(reply[0], IPA_DOMAIN_RESOLUTION_ORDER, + &domain_resolution_order); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get the view domains' resolution order " + "configuration value for view [%s] [%d]: %s\n", + state->view_name, ret, sss_strerror(ret)); + goto done; + } else if (ret == ENOENT) { + domain_resolution_order = NULL; + } + } + + ret = sysdb_update_view_domain_resolution_order(state->domain->sysdb, + domain_resolution_order); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_update_view_domain_resolution_order() [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t +ipa_subdomains_view_domain_resolution_order_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_domain_resolution_order_state { + struct sss_domain_info *domain; +}; + +static void ipa_domain_resolution_order_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_domain_resolution_order_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_domain_resolution_order_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + const char *attrs[] = {IPA_DOMAIN_RESOLUTION_ORDER, NULL}; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_domain_resolution_order_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->domain = sd_ctx->be_ctx->domain; + + subreq = ipa_get_config_send(state, ev, sh, sd_ctx->sdap_id_ctx->opts, + state->domain->name, attrs, NULL, NULL); + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } else { + tevent_req_set_callback(subreq, ipa_domain_resolution_order_done, req); + } + + return req; +} + +static void ipa_domain_resolution_order_done(struct tevent_req *subreq) +{ + struct ipa_domain_resolution_order_state *state; + struct tevent_req *req; + struct sysdb_attrs *config = NULL; + const char *domain_resolution_order = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_domain_resolution_order_state); + + ret = ipa_get_config_recv(subreq, state, &config); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Failed to get the domains' resolution order configuration " + "from the server [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (config != NULL) { + ret = sysdb_attrs_get_string(config, IPA_DOMAIN_RESOLUTION_ORDER, + &domain_resolution_order); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Failed to get the domains' resolution order configuration " + "value [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } else if (ret == ENOENT) { + domain_resolution_order = NULL; + } + } + + ret = sysdb_domain_update_domain_resolution_order( + state->domain->sysdb, state->domain->name, + domain_resolution_order); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_domain_update_resolution_order() [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_domain_resolution_order_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct kdcinfo_from_server_list_state { + struct resolv_hostport *hostport_list; + enum host_database db[2]; + + struct resolv_hostport_addr **rhp_addrs; + size_t rhp_len; +}; + +static void kdcinfo_from_server_list_done(struct tevent_req *subreq); + +static struct tevent_req * +kdcinfo_from_server_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_resolv_ctx *be_res, + const char *servers) +{ + struct kdcinfo_from_server_list_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + int server_list_len; + char **server_list; + + req = tevent_req_create(mem_ctx, &state, + struct kdcinfo_from_server_list_state); + if (req == NULL) { + return NULL; + } + state->db[0] = DB_DNS; + state->db[1] = DB_SENTINEL; + + if (servers == NULL) { + ret = EOK; + goto immediately; + } + + ret = split_on_separator(state, servers, ',', true, true, + &server_list, + &server_list_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to parse server list!\n"); + goto immediately; + } + + state->hostport_list = talloc_array(state, + struct resolv_hostport, + server_list_len); + if (state->hostport_list == NULL) { + ret = ENOMEM; + goto immediately; + } + + for (int i = 0; i < server_list_len; i++) { + state->hostport_list[i].host = server_list[i]; + state->hostport_list[i].port = 0; + } + + subreq = resolv_hostport_list_send(state, + ev, + be_res->resolv, + state->hostport_list, + server_list_len, + 0, + be_res->family_order, + state->db); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, kdcinfo_from_server_list_done, req); + return req; + +immediately: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static void kdcinfo_from_server_list_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct kdcinfo_from_server_list_state *state = tevent_req_data(req, + struct kdcinfo_from_server_list_state); + + ret = resolv_hostport_list_recv(subreq, + state, + &state->rhp_len, + &state->rhp_addrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to resolve address list [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t kdcinfo_from_server_list_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct resolv_hostport_addr ***_rhp_addrs, + size_t *_rhp_len) +{ + struct kdcinfo_from_server_list_state *state = tevent_req_data(req, + struct kdcinfo_from_server_list_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_rhp_addrs != NULL) { + *_rhp_addrs = talloc_steal(mem_ctx, state->rhp_addrs); + } + + if (_rhp_len != NULL) { + *_rhp_len = state->rhp_len; + } + + return EOK; +} + +struct kdcinfo_from_site_state { + struct tevent_context *ev; + struct be_resolv_ctx *be_res; + + const char *discovery_domains[2]; + struct resolv_hostport *hostport_list; + enum host_database db[2]; + + struct resolv_hostport_addr **rhp_addrs; + size_t rhp_len; +}; + +static void kdcinfo_from_site_srv_done(struct tevent_req *subreq); +static void kdcinfo_from_site_server_list_done(struct tevent_req *subreq); + +static struct tevent_req * +kdcinfo_from_site_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_resolv_ctx *be_res, + const char *site, + const char *domain) +{ + struct kdcinfo_from_site_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct kdcinfo_from_site_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->be_res = be_res; + state->db[0] = DB_DNS; + state->db[1] = DB_SENTINEL; + + state->discovery_domains[0] = ad_site_dns_discovery_domain(state, + site, + domain); + if (state->discovery_domains[0] == NULL) { + ret = ENOMEM; + goto immediately; + } + state->discovery_domains[1] = NULL; + + subreq = fo_discover_srv_send(state, + state->ev, + state->be_res->resolv, + "kerberos", "tcp", + state->discovery_domains); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, kdcinfo_from_site_srv_done, req); + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kdcinfo_from_site_srv_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct kdcinfo_from_site_state *state = tevent_req_data(req, + struct kdcinfo_from_site_state); + struct fo_server_info *servers; + size_t num_servers; + + ret = fo_discover_srv_recv(state, subreq, + NULL, NULL, /* not interested in TTL etc */ + &servers, &num_servers); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not resolve the site [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->hostport_list = talloc_array(state, + struct resolv_hostport, + num_servers); + if (state->hostport_list == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + for (size_t i = 0; i < num_servers; i++) { + state->hostport_list[i].host = servers[i].host; + state->hostport_list[i].port = servers[i].port; + } + + subreq = resolv_hostport_list_send(state, + state->ev, + state->be_res->resolv, + state->hostport_list, + num_servers, + MAX_SERVERS_FROM_SRV, + state->be_res->family_order, + state->db); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kdcinfo_from_site_server_list_done, req); +} + +static void kdcinfo_from_site_server_list_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct kdcinfo_from_site_state *state = tevent_req_data(req, + struct kdcinfo_from_site_state); + + ret = resolv_hostport_list_recv(subreq, + state, + &state->rhp_len, + &state->rhp_addrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to resolve address list [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + + +static errno_t kdcinfo_from_site_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct resolv_hostport_addr ***_rhp_addrs, + size_t *_rhp_len) +{ + struct kdcinfo_from_site_state *state = tevent_req_data(req, + struct kdcinfo_from_site_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_rhp_addrs != NULL) { + *_rhp_addrs = talloc_steal(mem_ctx, state->rhp_addrs); + } + + if (_rhp_len != NULL) { + *_rhp_len = state->rhp_len; + } + + return EOK; +} + +/* Anything per-domain in this request goes here so that we + * can just free the whole struct without mixing data from + * different domains or the overhead of another request + */ +struct ipa_sd_per_dom_kdcinfo_ctx { + struct sss_domain_info *dom; + + const char *servers; + const char *site; + + const char *discovery_domains[2]; + struct krb5_service *krb5_service; +}; + +struct ipa_subdomains_write_kdcinfo_state { + struct tevent_context *ev; + struct ipa_subdomains_ctx *ipa_sd_ctx; + struct be_ctx *be_ctx; + + bool use_kdcinfo; + struct ipa_sd_per_dom_kdcinfo_ctx *pdctx; +}; + +static errno_t ipa_subdomains_write_kdcinfo_domain_step(struct sss_domain_info *start_dom, + struct tevent_req *req); +static void ipa_subdomains_write_kdcinfo_domain_done(struct tevent_req *subreq); +static errno_t ipa_subdomains_write_kdcinfo_write_step(struct sss_domain_info *dom, + struct krb5_service *krb5_service, + struct resolv_hostport_addr **rhp_addrs, + size_t rhp_len); + +static struct tevent_req * +ipa_subdomains_write_kdcinfo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *ipa_sd_ctx, + struct be_ctx *be_ctx) +{ + struct ipa_subdomains_write_kdcinfo_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_write_kdcinfo_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->ipa_sd_ctx = ipa_sd_ctx; + state->be_ctx = be_ctx; + + if (ipa_sd_ctx->ipa_id_ctx->server_mode != NULL) { + /* This request is valid for clients only */ + ret = EOK; + goto immediately; + } + + state->use_kdcinfo = dp_opt_get_bool(ipa_sd_ctx->ipa_id_ctx->ipa_options->auth, + KRB5_USE_KDCINFO); + if (state->use_kdcinfo == false) { + DEBUG(SSSDBG_CONF_SETTINGS, "kdcinfo creation disabled\n"); + ret = EOK; + goto immediately; + } + + if (be_ctx->domain->subdomains == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No subdomains, done\n"); + ret = EOK; + goto immediately; + } + + ret = ipa_subdomains_write_kdcinfo_domain_step(be_ctx->domain->subdomains, + req); + if (ret != EAGAIN) { + goto immediately; + } + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_subdomains_write_kdcinfo_domain_step(struct sss_domain_info *start_dom, + struct tevent_req *req) +{ + struct ipa_subdomains_write_kdcinfo_state *state = \ + tevent_req_data(req, + struct ipa_subdomains_write_kdcinfo_state); + struct dp_option *ipa_ad_subdom_opts; + struct tevent_req *subreq = NULL; + char *subdom_conf_path; + errno_t ret; + const char *servers; + const char *site; + + for (struct sss_domain_info *dom = start_dom; + dom != NULL; + dom = get_next_domain(dom, 0)) { + + talloc_zfree(state->pdctx); + + subdom_conf_path = subdomain_create_conf_path(state, dom); + if (subdom_conf_path == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "subdom_conf_path failed for %s\n", dom->name); + /* Not fatal */ + continue; + } + + ret = dp_get_options(state, state->be_ctx->cdb, + subdom_conf_path, + ipa_cli_ad_subdom_opts, + IPA_OPTS_CLI_AD_SUBDOM, + &ipa_ad_subdom_opts); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot get options for %s: [%d]: %s\n", + dom->name, ret, sss_strerror(ret)); + /* Not fatal */ + continue; + } + + servers = dp_opt_get_string(ipa_ad_subdom_opts, IPA_CLI_AD_SERVER); + site = dp_opt_get_string(ipa_ad_subdom_opts, IPA_CLI_AD_SITE); + + if (servers == NULL && site == NULL) { + /* If neither is set, just go to the next domain */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "No site or server defined for %s, skipping\n", + dom->name); + continue; + } + + /* We will resolve this domain, create a per-domain context */ + state->pdctx = talloc_zero(state, struct ipa_sd_per_dom_kdcinfo_ctx); + if (state->pdctx == NULL) { + return ENOMEM; + } + state->pdctx->dom = dom; + state->pdctx->servers = servers; + state->pdctx->site = site; + state->pdctx->krb5_service = ipa_subdom_get_k5_svc(state->ipa_sd_ctx, + dom, + state->use_kdcinfo); + if (state->pdctx->krb5_service == NULL) { + continue; + } + + if (state->pdctx->servers != NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Resolving servers [%s] for domain %s\n", + state->pdctx->servers, dom->name); + + subreq = kdcinfo_from_server_list_send(state, + state->ev, + state->be_ctx->be_res, + state->pdctx->servers); + } else if (state->pdctx->site != NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Resolving site %s for domain %s\n", + state->pdctx->site, dom->name); + + subreq = kdcinfo_from_site_send(state, + state->ev, + state->be_ctx->be_res, + state->pdctx->site, + state->pdctx->dom->name); + } else { + /* We should never get here */ + return EINVAL; + } + + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_subdomains_write_kdcinfo_domain_done, req); + return EAGAIN; + } + + return EOK; +} + +static void ipa_subdomains_write_kdcinfo_domain_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_subdomains_write_kdcinfo_state *state = \ + tevent_req_data(req, + struct ipa_subdomains_write_kdcinfo_state); + struct sss_domain_info *next_domain; + struct resolv_hostport_addr **rhp_addrs = NULL; + size_t rhp_len = 0; + + if (state->pdctx->servers != NULL) { + ret = kdcinfo_from_server_list_recv(state->pdctx, subreq, + &rhp_addrs, &rhp_len); + } else if (state->pdctx->site != NULL) { + ret = kdcinfo_from_site_recv(state->pdctx, subreq, + &rhp_addrs, &rhp_len); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Neither site nor servers set\n"); + ret = EINVAL; + } + + if (ret == EOK) { + ret = ipa_subdomains_write_kdcinfo_write_step(state->pdctx->dom, + state->pdctx->krb5_service, + rhp_addrs, rhp_len); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not write kdcinfo file for %s\n", state->pdctx->dom->name); + /* Not fatal, loop to the next domain below */ + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not get address list for %s\n", state->pdctx->dom->name); + /* Not fatal, loop to the next domain below */ + } + + next_domain = get_next_domain(state->pdctx->dom, 0); + ret = ipa_subdomains_write_kdcinfo_domain_step(next_domain, req); + if (ret == EOK) { + tevent_req_done(req); + return; + } else if (ret != EAGAIN) { + /* the loop in ipa_subdomains_write_kdcinfo_domain_step already + * tries to be quite permissive, so any error is fatal + */ + tevent_req_error(req, ret); + return; + } + + /* Continue to the next domain */ +} + +static errno_t ipa_subdomains_write_kdcinfo_write_step(struct sss_domain_info *dom, + struct krb5_service *krb5_service, + struct resolv_hostport_addr **rhp_addrs, + size_t rhp_len) +{ + errno_t ret; + char *address = NULL; + char *safe_address = NULL; + const char **safe_addr_list; + int addr_index = 0; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + safe_addr_list = talloc_zero_array(tmp_ctx, const char *, rhp_len+1); + if (safe_addr_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (size_t i = 0; i < rhp_len; i++) { + address = resolv_get_string_address(tmp_ctx, rhp_addrs[i]->reply); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "resolv_get_string_address failed.\n"); + continue; + } + + if (rhp_addrs[i]->origin.port != 0) { + address = talloc_asprintf_append(address, + ":%d", + rhp_addrs[i]->origin.port); + } + + safe_address = sss_escape_ip_address(tmp_ctx, + rhp_addrs[i]->reply->family, + address); + talloc_zfree(address); + if (safe_address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_escape_ip_address failed.\n"); + continue; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Will write [%s] for %s\n", + safe_address, dom->name); + + safe_addr_list[addr_index] = talloc_steal(safe_addr_list, + safe_address); + addr_index++; + } + + ret = write_krb5info_file(krb5_service, + safe_addr_list, + SSS_KRB5KDC_FO_SRV); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "write to %s/kdcinfo.%s failed, authentication might fail.\n", + PUBCONF_PATH, krb5_service->realm); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t ipa_subdomains_write_kdcinfo_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ipa_subdomains_refresh_state { + struct tevent_context *ev; + struct ipa_subdomains_ctx *sd_ctx; + struct sdap_id_op *sdap_op; +}; + +static errno_t ipa_subdomains_refresh_retry(struct tevent_req *req); +static void ipa_subdomains_refresh_connect_done(struct tevent_req *subreq); +static void ipa_subdomains_refresh_ranges_done(struct tevent_req *subreq); +static void ipa_subdomains_refresh_certmap_done(struct tevent_req *subreq); +#ifdef BUILD_PASSKEY +static void ipa_subdomains_refresh_passkey_done(struct tevent_req *subreq); +#endif /* BUILD_PASSKEY */ +static void ipa_subdomains_refresh_master_done(struct tevent_req *subreq); +static void ipa_subdomains_refresh_slave_done(struct tevent_req *subreq); +static void ipa_subdomains_refresh_view_name_done(struct tevent_req *subreq); +static void ipa_subdomains_refresh_view_domain_resolution_order_done( + struct tevent_req *subreq); +static void ipa_domain_refresh_resolution_order_done(struct tevent_req *subreq); +static void ipa_domain_refresh_kdcinfo_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->sd_ctx = sd_ctx; + + state->sdap_op = sdap_id_op_create(state, + sd_ctx->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + ret = ipa_subdomains_refresh_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ipa_subdomains_refresh_retry(struct tevent_req *req) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_connect_done, req); + + return EAGAIN; +} + +static void ipa_subdomains_refresh_connect_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to connect to LDAP " + "[%d]: %s\n", ret, sss_strerror(ret)); + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, "No IPA server is available, " + "cannot get the subdomain list while offline\n"); + ret = ERR_OFFLINE; + } + tevent_req_error(req, ret); + return; + } + + subreq = ipa_subdomains_ranges_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_ranges_done, req); + return; +} + +static void ipa_subdomains_refresh_ranges_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_ranges_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get IPA ranges " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + subreq = ipa_subdomains_certmap_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_certmap_done, req); + return; +} + +static void ipa_subdomains_refresh_certmap_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_certmap_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to read certificate mapping rules " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + +#ifdef BUILD_PASSKEY + subreq = ipa_subdomains_passkey_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_passkey_done, req); + return; +} + +static void ipa_subdomains_refresh_passkey_done(struct tevent_req *subreq) +{ + + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_passkey_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to get passkey configuration " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + DEBUG(SSSDBG_IMPORTANT_INFO, "Passkey feature is not configured " + "on IPA server\n"); + } +#endif /* BUILD_PASSKEY */ + + subreq = ipa_subdomains_master_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_master_done, req); + return; +} + +static void ipa_subdomains_refresh_master_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_master_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get master domain " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + subreq = ipa_subdomains_slave_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_slave_done, req); + return; +} + +static void ipa_subdomains_refresh_slave_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_slave_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get subdomains " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + subreq = ipa_subdomains_view_name_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_view_name_done, + req); + return; +} + +static void ipa_subdomains_refresh_view_name_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_view_name_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to get view name [%d]: %s\n", + ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + subreq = ipa_subdomains_view_domain_resolution_order_send( + state, + state->ev, + state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, + ipa_subdomains_refresh_view_domain_resolution_order_done, + req); +} + +static void +ipa_subdomains_refresh_view_domain_resolution_order_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_view_domain_resolution_order_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to get view domain_resolution order [%d]: %s\n", + ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + subreq = ipa_domain_resolution_order_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, + ipa_domain_refresh_resolution_order_done, + req); +} + +static void +ipa_domain_refresh_resolution_order_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_domain_resolution_order_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get the domains order resolution [%d]: %s\n", + ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ipa_subdomains_refresh_retry(req); + } else if (dp_error == DP_ERR_OFFLINE) { + ret = ERR_OFFLINE; + } + + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Unable to refresh subdomains [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + subreq = ipa_subdomains_write_kdcinfo_send(state, + state->ev, + state->sd_ctx, + state->sd_ctx->be_ctx); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ipa_domain_refresh_kdcinfo_done, req); +} + +static void +ipa_domain_refresh_kdcinfo_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = ipa_subdomains_write_kdcinfo_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to write the kdc info files, authentication might " + "fail or time out [%d]: %s\n", + ret, sss_strerror(ret)); + /* Not fatal, let's hope DNS is set correctly */ + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_refresh_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_subdomains_handler_state { + struct dp_reply_std reply; +}; + +static void ipa_subdomains_handler_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_subdomains_ctx *sd_ctx, + struct dp_subdomains_data *data, + struct dp_req_params *params) +{ + struct ipa_subdomains_handler_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + + if (sd_ctx->last_refreshed > time(NULL) - IPA_SUBDOMAIN_REFRESH_LIMIT) { + DEBUG(SSSDBG_TRACE_FUNC, "Subdomains were recently refreshed, " + "nothing to do\n"); + ret = EOK; + goto immediately; + } + + subreq = ipa_subdomains_refresh_send(state, params->ev, sd_ctx); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_subdomains_handler_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_handler_state); + + ret = ipa_subdomains_refresh_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to refresh subdomains [%d]: %s\n", + ret, sss_strerror(ret)); + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + tevent_req_done(req); +} + +static errno_t ipa_subdomains_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct ipa_subdomains_handler_state *state; + + state = tevent_req_data(req, struct ipa_subdomains_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} + +static struct tevent_req * +ipa_subdomains_ptask_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ipa_subdomains_ctx *sd_ctx; + sd_ctx = talloc_get_type(pvt, struct ipa_subdomains_ctx); + + return ipa_subdomains_refresh_send(mem_ctx, ev, sd_ctx); +} + +static errno_t +ipa_subdomains_ptask_recv(struct tevent_req *req) +{ + return ipa_subdomains_refresh_recv(req); +} + +errno_t ipa_subdomains_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *ipa_id_ctx, + struct dp_method *dp_methods) +{ + struct ipa_subdomains_ctx *sd_ctx; + struct ipa_options *ipa_options; + time_t period; + time_t offset; + errno_t ret; + /* Delay the first ptask that refreshes the trusted domains so that a race between + * the first responder-induced request and the ptask doesn't cause issues, see + * also upstream ticket #3601 + */ + const time_t ptask_first_delay = 600; + + ipa_options = ipa_id_ctx->ipa_options; + + sd_ctx = talloc_zero(mem_ctx, struct ipa_subdomains_ctx); + if (sd_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + sd_ctx->be_ctx = be_ctx; + sd_ctx->ipa_id_ctx = ipa_id_ctx; + sd_ctx->sdap_id_ctx = ipa_id_ctx->sdap_id_ctx; + sd_ctx->search_bases = ipa_options->subdomains_search_bases; + sd_ctx->master_search_bases = ipa_options->master_domain_search_bases; + sd_ctx->ranges_search_bases = ipa_options->ranges_search_bases; + sd_ctx->host_search_bases = ipa_options->id->sdom->host_search_bases; + + dp_set_method(dp_methods, DPM_DOMAINS_HANDLER, + ipa_subdomains_handler_send, ipa_subdomains_handler_recv, sd_ctx, + struct ipa_subdomains_ctx, struct dp_subdomains_data, struct dp_reply_std); + + period = be_ctx->domain->subdomain_refresh_interval; + offset = be_ctx->domain->subdomain_refresh_interval_offset; + ret = be_ptask_create(sd_ctx, be_ctx, period, ptask_first_delay, 0, offset, + period, 0, + ipa_subdomains_ptask_send, ipa_subdomains_ptask_recv, sd_ctx, + "Subdomains Refresh", + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_SCHEDULE_FROM_LAST, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup ptask " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Ignore, responders will trigger refresh from time to time. */ + } + + ret = ipa_subdom_reinit(sd_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not reinitialize subdomains. " + "Users from trusted domains might not be resolved correctly\n"); + /* Ignore this error and try to discover the subdomains later */ + } + + ret = ipa_ad_subdom_init(be_ctx, ipa_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ipa_ad_subdom_init() failed.\n"); + return ret; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_subdomains.h b/src/providers/ipa/ipa_subdomains.h new file mode 100644 index 0000000..1411d0c --- /dev/null +++ b/src/providers/ipa/ipa_subdomains.h @@ -0,0 +1,177 @@ +/* + SSSD + + IPA Subdomains Module + + Authors: + Sumit Bose + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _IPA_SUBDOMAINS_H_ +#define _IPA_SUBDOMAINS_H_ + +#include "providers/backend.h" +#include "providers/ipa/ipa_common.h" +#include "config.h" + +#ifndef IPA_TRUST_KEYTAB_DIR +#define IPA_TRUST_KEYTAB_DIR SSS_STATEDIR"/keytabs" +#endif /* IPA_TRUST_KEYTAB_DIR */ + +/* ==Sid2Name Extended Operation============================================= */ +#define EXOP_SID2NAME_OID "2.16.840.1.113730.3.8.10.4" +#define EXOP_SID2NAME_V1_OID "2.16.840.1.113730.3.8.10.4.1" +#define EXOP_SID2NAME_V2_OID "2.16.840.1.113730.3.8.10.4.2" + +enum extdom_protocol { + EXTDOM_INVALID_VERSION = -1, + EXTDOM_V0, + EXTDOM_V1, + EXTDOM_V2 +}; + +struct ipa_subdomains_ctx { + struct be_ctx *be_ctx; + struct ipa_id_ctx *ipa_id_ctx; + struct sdap_id_ctx *sdap_id_ctx; + struct sdap_search_base **search_bases; + struct sdap_search_base **master_search_bases; + struct sdap_search_base **ranges_search_bases; + struct sdap_search_base **host_search_bases; + + time_t last_refreshed; + bool view_read_at_init; + /* List of krb5_service structures for each subdomain + * in order to write the kdcinfo files. For use on + * the client only + */ + struct ipa_sd_k5_svc_list *k5svc_list; +}; + +errno_t ipa_subdomains_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *ipa_id_ctx, + struct dp_method *dp_methods); + +/* The following are used in server mode only */ +struct ipa_ad_server_ctx { + struct sss_domain_info *dom; + struct ad_id_ctx *ad_id_ctx; + + struct ipa_ad_server_ctx *next, *prev; +}; + +/* Can be used to set up trusted subdomain, for example fetch + * keytab in server mode + */ +struct tevent_req * +ipa_server_trusted_dom_setup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom); +errno_t ipa_server_trusted_dom_setup_recv(struct tevent_req *req); + +/* To be used by ipa_subdomains.c only */ +struct tevent_req * +ipa_server_create_trusts_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *parent); + +errno_t ipa_server_create_trusts_recv(struct tevent_req *req); + +void ipa_ad_subdom_remove(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom); + +int ipa_ad_subdom_init(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx); + +errno_t ipa_server_get_trust_direction(struct sysdb_attrs *sd, + struct ldb_context *ldb_ctx, + uint32_t *_direction); + +const char *ipa_trust_dir2str(uint32_t direction); + +/* Utilities */ +#define IPA_TRUST_DIRECTION "ipaNTTrustDirection" + +struct ldb_dn *ipa_subdom_ldb_dn(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + struct sysdb_attrs *attrs); + +bool ipa_subdom_is_member_dom(struct ldb_dn *dn); + +/* struct for external group memberships, defined in + * ipa_subdomains_ext_groups.c */ +struct ipa_ext_groups; + +struct ipa_server_mode_ctx { + const char *realm; + const char *hostname; + + struct ipa_ad_server_ctx *trusts; + struct ipa_ext_groups *ext_groups; + + uid_t kt_owner_uid; + uid_t kt_owner_gid; +}; + +int ipa_ad_subdom_init(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx); + +enum req_input_type { + REQ_INP_NAME, + REQ_INP_ID, + REQ_INP_SECID, + REQ_INP_CERT +}; + +struct req_input { + enum req_input_type type; + union { + const char *name; + uint32_t id; + const char *secid; + const char *cert; + } inp; +}; + +struct tevent_req *ipa_get_ad_memberships_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dp_id_data *ar, + struct ipa_server_mode_ctx *server_mode, + struct sss_domain_info *user_dom, + struct sdap_id_ctx *sdap_id_ctx, + const char *domain); + +errno_t ipa_get_ad_memberships_recv(struct tevent_req *req, int *dp_error_out); + +struct tevent_req *ipa_ext_group_member_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ext_member, + void *pvt); +errno_t ipa_ext_group_member_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + enum sysdb_member_type *_member_type, + struct sss_domain_info **_dom, + struct sysdb_attrs **_member); + +#endif /* _IPA_SUBDOMAINS_H_ */ diff --git a/src/providers/ipa/ipa_subdomains_ext_groups.c b/src/providers/ipa/ipa_subdomains_ext_groups.c new file mode 100644 index 0000000..f4f8474 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_ext_groups.c @@ -0,0 +1,1213 @@ +/* + SSSD + + IPA Identity Backend Module for sub-domains - evaluate external group + memberships + + Authors: + Sumit Bose + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_ops.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ad/ad_id.h" +#include "providers/ipa/ipa_subdomains.h" + +#define IPA_EXT_GROUPS_FILTER "objectClass=ipaexternalgroup" + +struct ipa_ext_groups { + time_t next_update; + hash_table_t *ext_groups; +}; + +static errno_t process_ext_groups(TALLOC_CTX *mem_ctx, size_t reply_count, + struct sysdb_attrs **reply, + hash_table_t **_ext_group_hash) +{ + int ret; + hash_table_t *ext_group_hash = NULL; + hash_key_t key; + hash_value_t value; + hash_table_t *m_hash = NULL; + hash_key_t m_key; + hash_value_t m_value; + size_t g; + size_t s; + size_t m; + TALLOC_CTX *tmp_ctx = NULL; + const char **ext_sids; + const char **mof; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sss_hash_create(mem_ctx, reply_count, &ext_group_hash); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n"); + goto done; + } + + key.type = HASH_KEY_STRING; + m_key.type = HASH_KEY_STRING; + m_value.type = HASH_VALUE_PTR; + m_value.ptr = NULL; + + for (g = 0; g < reply_count; g++) { + ret = sysdb_attrs_get_string_array(reply[g], "ipaExternalMember", + tmp_ctx, &ext_sids); + if (ret == ENOENT) { + /* no external members, try next external group. */ + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string_array failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string_array(reply[g], "memberOf", + tmp_ctx, &mof); + if (ret == ENOENT) { + /* no IPA groups, try next external group. */ + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string_array failed.\n"); + goto done; + } + + for (s = 0; ext_sids[s] != NULL; s++) { + /* hash_lookup does not modify key.str. */ + key.str = discard_const(ext_sids[s]); + ret = hash_lookup(ext_group_hash, &key, &value); + if (ret == HASH_SUCCESS) { + if (value.type != HASH_VALUE_PTR) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected value type.\n"); + ret = EINVAL; + goto done; + } + + for (m = 0; mof[m] != NULL; m++) { + /* hash_enter does not modify m_key.str. */ + m_key.str = discard_const(mof[m]); + DEBUG(SSSDBG_TRACE_ALL, "Adding group [%s] to SID [%s].\n", + m_key.str, key.str); + ret = hash_enter(value.ptr, &m_key, &m_value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed.\n"); + goto done; + } + } + } else if (ret == HASH_ERROR_KEY_NOT_FOUND) { + ret = sss_hash_create(ext_group_hash, 0, &m_hash); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n"); + goto done; + } + + value.type = HASH_VALUE_PTR; + value.ptr = m_hash; + + DEBUG(SSSDBG_TRACE_ALL, + "Adding SID [%s] to external group hash.\n", key.str); + ret = hash_enter(ext_group_hash, &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed.\n"); + goto done; + } + + for (m = 0; mof[m] != NULL; m++) { + /* hash_enter does not modify m_key.str. */ + m_key.str = discard_const(mof[m]); + DEBUG(SSSDBG_TRACE_ALL, "Adding group [%s] to SID [%s].\n", + m_key.str, key.str); + ret = hash_enter(m_hash, &m_key, &m_value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed.\n"); + goto done; + } + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "hash_lookup failed.\n"); + goto done; + } + } + } + + ret = EOK; +done: + if (ret != EOK) { + talloc_free(ext_group_hash); + } else { + *_ext_group_hash = ext_group_hash; + } + + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t find_ipa_ext_memberships(TALLOC_CTX *mem_ctx, + const char *user_name, + struct sss_domain_info *user_dom, + hash_table_t *ext_group_hash, + struct ldb_dn **_user_dn, + char ***_groups) +{ + int ret; + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_result *result; + char **groups = NULL; + size_t c; + const char *sid; + hash_key_t key; + hash_value_t value; + hash_entry_t *entry; + struct hash_iter_context_t *iter; + hash_table_t *group_hash; + size_t g_count; + struct ldb_dn *user_dn = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_initgroups(tmp_ctx, user_dom, user_name, &result); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_initgroups failed.\n"); + goto done; + } + + if (result->count == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "User [%s] not found in cache.\n", + user_name); + ret = EOK; + goto done; + } + + ret = sss_hash_create(tmp_ctx, 0, &group_hash); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n"); + goto done; + } + + key.type = HASH_KEY_STRING; + + /* The IPA external domains can have references to group and user SIDs. + * This means that we not only want to look up the group SIDs but the SID + * of the user (first element of result) as well. */ + for (c = 0; c < result->count; c++) { + sid = ldb_msg_find_attr_as_string(result->msgs[c], SYSDB_SID_STR, + NULL); + if (sid == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Group [%s] does not have a SID.\n", + ldb_dn_get_linearized(result->msgs[c]->dn)); + continue; + } + + key.str = discard_const(sid); + ret = hash_lookup(ext_group_hash, &key, &value); + if (ret == HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_TRACE_ALL, "SID [%s] not found in ext group hash.\n", + sid); + } else if (ret == HASH_SUCCESS) { + iter = new_hash_iter_context(value.ptr); + if (iter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "new_hash_iter_context failed.\n"); + ret = EINVAL; + goto done; + } + + while ((entry = iter->next(iter)) != NULL) { + ret = hash_enter(group_hash, &entry->key, &entry->value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add group [%s].\n", + entry->key.str); + } + } + + talloc_free(iter); + } else { + DEBUG(SSSDBG_OP_FAILURE, "hash_lookup failed for SID [%s].\n", + sid); + } + } + + g_count = hash_count(group_hash); + if (g_count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No external groupmemberships found.\n"); + ret = EOK; + goto done; + } + + groups = talloc_zero_array(mem_ctx, char *, g_count + 1); + if (groups == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + + iter = new_hash_iter_context(group_hash); + if (iter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "new_hash_iter_context failed.\n"); + ret = EINVAL; + goto done; + } + + c = 0; + while ((entry = iter->next(iter)) != NULL) { + groups[c] = talloc_strdup(groups, entry->key.str); + if (groups[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + } + + user_dn = ldb_dn_copy(mem_ctx, result->msgs[0]->dn); + if (user_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_copy failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = EOK; +done: + *_user_dn = user_dn; + *_groups = groups; + + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t add_ad_user_to_cached_groups(struct ldb_dn *user_dn, + struct sss_domain_info *user_dom, + struct sss_domain_info *group_dom, + char **groups, + bool *missing_groups) +{ + size_t c; + struct sysdb_attrs *user_attrs; + size_t msgs_count; + struct ldb_message **msgs; + TALLOC_CTX *tmp_ctx; + int ret; + + *missing_groups = false; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + for (c = 0; groups[c] != NULL; c++) { + if (groups[c][0] == '\0') { + continue; + } + + ret = sysdb_search_groups_by_orig_dn(tmp_ctx, group_dom, groups[c], + NULL, &msgs_count, &msgs); + if (ret != EOK) { + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "Group [%s] not in the cache.\n", + groups[c]); + *missing_groups = true; + continue; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_entry failed.\n"); + goto done; + } + } + +/* TODO? Do we have to remove members as well? I think not because the AD + * query before removes all memberships. */ + + ret = sysdb_mod_group_member(group_dom, user_dn, msgs[0]->dn, + LDB_FLAG_MOD_ADD); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_mod_group_member failed.\n"); + goto done; + } + + user_attrs = sysdb_new_attrs(tmp_ctx); + if (user_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(user_attrs, SYSDB_ORIG_MEMBEROF, + groups[c]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string failed.\n"); + goto done; + } + + ret = sysdb_set_entry_attr(user_dom->sysdb, user_dn, user_attrs, + LDB_FLAG_MOD_ADD); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_entry_attr failed.\n"); + goto done; + } + + /* mark group as already processed */ + groups[c][0] = '\0'; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +static struct tevent_req *ipa_add_ad_memberships_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_id_ctx, + struct ldb_dn *user_dn, + struct sss_domain_info *user_dom, + char **groups, + struct sss_domain_info *group_dom); +static void ipa_add_ad_memberships_done(struct tevent_req *subreq); + +struct get_ad_membership_state { + struct tevent_context *ev; + struct ipa_server_mode_ctx *server_mode; + struct sdap_id_op *sdap_op; + struct sdap_id_ctx *sdap_id_ctx; + struct fo_server *srv; + char *user_name; + struct sss_domain_info *user_dom; + + int dp_error; + const char *domain; + size_t reply_count; + struct sysdb_attrs **reply; +}; + +static void ipa_get_ad_memberships_connect_done(struct tevent_req *subreq); +static void ipa_get_ext_groups_done(struct tevent_req *subreq); +static errno_t ipa_add_ext_groups_step(struct tevent_req *req); +static errno_t ipa_add_ad_memberships_recv(struct tevent_req *req, + int *dp_error_out); + +struct tevent_req *ipa_get_ad_memberships_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dp_id_data *ar, + struct ipa_server_mode_ctx *server_mode, + struct sss_domain_info *user_dom, + struct sdap_id_ctx *sdap_id_ctx, + const char *domain) +{ + int ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct get_ad_membership_state *state; + + req = tevent_req_create(mem_ctx, &state, struct get_ad_membership_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->user_dom = user_dom; + state->sdap_id_ctx = sdap_id_ctx; + state->srv = NULL; + state->domain = domain; + state->dp_error = -1; + + if (((ar->entry_type & BE_REQ_TYPE_MASK) != BE_REQ_INITGROUPS + && (ar->entry_type & BE_REQ_TYPE_MASK) != BE_REQ_USER) + || ar->filter_type != BE_FILTER_NAME) { + DEBUG(SSSDBG_OP_FAILURE, "Unsupported request type.\n"); + ret = EINVAL; + goto done; + } + + state->user_name = talloc_strdup(state, ar->filter_value); + if (state->user_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_Strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + state->sdap_op = sdap_id_op_create(state, + state->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + state->server_mode = server_mode; + if (server_mode->ext_groups == NULL) { + server_mode->ext_groups = talloc_zero(server_mode, + struct ipa_ext_groups); + if (server_mode->ext_groups == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (server_mode->ext_groups->next_update > time(NULL)) { + DEBUG(SSSDBG_TRACE_FUNC, "External group information still valid.\n"); + ret = ipa_add_ext_groups_step(req); + if (ret == EOK) { + goto done; + } else if (ret == EAGAIN) { + return req; + } else { + DEBUG(SSSDBG_OP_FAILURE, "ipa_add_ext_groups_step failed.\n"); + goto done; + } + + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto done; + } + + tevent_req_set_callback(subreq, ipa_get_ad_memberships_connect_done, req); + + return req; + +done: + if (ret != EOK) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + } else { + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + } + tevent_req_post(req, state->ev); + + return req; +} + +static void ipa_get_ad_memberships_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_ad_membership_state *state = tevent_req_data(req, + struct get_ad_membership_state); + int ret; + + ret = sdap_id_op_connect_recv(subreq, &state->dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (state->dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No IPA server is available, going offline\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to IPA server: [%d](%s)\n", + ret, strerror(ret)); + } + + goto fail; + } + + subreq = sdap_search_bases_send(state, state->ev, state->sdap_id_ctx->opts, + sdap_id_op_handle(state->sdap_op), + state->sdap_id_ctx->opts->sdom->group_search_bases, + NULL, true, + dp_opt_get_int(state->sdap_id_ctx->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + IPA_EXT_GROUPS_FILTER, + NULL, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_get_ext_groups_done, req); + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static void ipa_get_ext_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_ad_membership_state *state = tevent_req_data(req, + struct get_ad_membership_state); + int ret; + hash_table_t *ext_group_hash; + + ret = sdap_search_bases_recv(subreq, + state, + &state->reply_count, + &state->reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ext_groups request failed.\n"); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "[%zu] external groups found.\n", + state->reply_count); + + ret = process_ext_groups(state, + state->reply_count, + state->reply, + &ext_group_hash); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "process_ext_groups failed.\n"); + goto fail; + } + + talloc_free(state->server_mode->ext_groups->ext_groups); + state->server_mode->ext_groups->ext_groups = talloc_steal( + state->server_mode->ext_groups, + ext_group_hash); + /* Do we have to make the update timeout configurable? */ + state->server_mode->ext_groups->next_update = time(NULL) + 10; + + ret = ipa_add_ext_groups_step(req); + if (ret == EOK) { + tevent_req_done(req); + return; + } else if (ret == EAGAIN) { + return; + } else { + DEBUG(SSSDBG_OP_FAILURE, "ipa_add_ext_groups_step failed.\n"); + goto fail; + } + +fail: + tevent_req_error(req, ret); + return; +} + +static errno_t ipa_add_ext_groups_step(struct tevent_req *req) +{ + struct get_ad_membership_state *state = tevent_req_data(req, + struct get_ad_membership_state); + struct ldb_dn *user_dn; + int ret; + char **groups = NULL; + struct tevent_req *subreq; + + ret = find_ipa_ext_memberships(state, state->user_name, state->user_dom, + state->server_mode->ext_groups->ext_groups, + &user_dn, &groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "find_ipa_ext_memberships failed.\n"); + goto fail; + } + + if (groups == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No external groups memberships found.\n"); + state->dp_error = DP_ERR_OK; + return EOK; + } + + subreq = ipa_add_ad_memberships_send(state, state->ev, state->sdap_id_ctx, + user_dn, state->user_dom, groups, + state->sdap_id_ctx->be->domain); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_add_ad_memberships_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_add_ad_memberships_done, req); + return EAGAIN; + +fail: + tevent_req_error(req, ret); + return ret; +} + +static void ipa_add_ad_memberships_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_ad_membership_state *state = tevent_req_data(req, + struct get_ad_membership_state); + int ret; + + ret = ipa_add_ad_memberships_recv(subreq, &state->dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_add_ad_memberships request failed.\n"); + tevent_req_error(req, ret); + return; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; +} + +errno_t ipa_get_ad_memberships_recv(struct tevent_req *req, int *dp_error_out) +{ + struct get_ad_membership_state *state = tevent_req_data(req, + struct get_ad_membership_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + return EOK; +} + +struct add_ad_membership_state { + struct tevent_context *ev; + struct sdap_id_ctx *sdap_id_ctx; + struct sdap_id_op *sdap_op; + struct ldb_dn *user_dn; + struct sss_domain_info *user_dom; + struct sss_domain_info *group_dom; + char **groups; + int dp_error; + size_t iter; + struct sdap_domain *group_sdom; +}; + +static void ipa_add_ad_memberships_connect_done(struct tevent_req *subreq); +static void ipa_add_ad_memberships_get_next(struct tevent_req *req); +static void ipa_add_ad_memberships_get_group_done(struct tevent_req *subreq); +static struct tevent_req *ipa_add_ad_memberships_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_id_ctx, + struct ldb_dn *user_dn, + struct sss_domain_info *user_dom, + char **groups, + struct sss_domain_info *group_dom) +{ + int ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct add_ad_membership_state *state; + bool missing_groups = false; + + req = tevent_req_create(mem_ctx, &state, struct add_ad_membership_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->user_dom = user_dom; + state->sdap_id_ctx = sdap_id_ctx; + state->user_dn = user_dn; + state->group_dom = group_dom; + state->groups = groups; + state->dp_error = -1; + state->iter = 0; + state->group_sdom = sdap_domain_get(sdap_id_ctx->opts, group_dom); + if (state->group_sdom == NULL) { + ret = EIO; + goto done; + } + + ret = add_ad_user_to_cached_groups(user_dn, user_dom, group_dom, groups, + &missing_groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_ad_user_to_cached_groups failed.\n"); + goto done; + } + + if (!missing_groups) { + DEBUG(SSSDBG_TRACE_ALL, "All groups found in cache.\n"); + ret = EOK; + goto done; + } + + state->sdap_op = sdap_id_op_create(state, + state->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto done; + } + + tevent_req_set_callback(subreq, ipa_add_ad_memberships_connect_done, req); + + return req; + +done: + if (ret != EOK) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + } else { + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + } + tevent_req_post(req, state->ev); + + return req; +} + +static void ipa_add_ad_memberships_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct add_ad_membership_state *state = tevent_req_data(req, + struct add_ad_membership_state); + int ret; + + ret = sdap_id_op_connect_recv(subreq, &state->dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (state->dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No IPA server is available, going offline\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to IPA server: [%d](%s)\n", + ret, strerror(ret)); + } + + tevent_req_error(req, ret); + return; + } + + state->iter = 0; + ipa_add_ad_memberships_get_next(req); +} + +static void ipa_add_ad_memberships_get_next(struct tevent_req *req) +{ + struct add_ad_membership_state *state = tevent_req_data(req, + struct add_ad_membership_state); + struct tevent_req *subreq; + struct ldb_dn *group_dn; + int ret; + const struct ldb_val *val; + bool missing_groups; + const char *fq_name; + char *tmp_str; + + while (state->groups[state->iter] != NULL + && state->groups[state->iter][0] == '\0') { + state->iter++; + } + + if (state->groups[state->iter] == NULL) { + ret = add_ad_user_to_cached_groups(state->user_dn, state->user_dom, + state->group_dom, state->groups, + &missing_groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_ad_user_to_cached_groups failed.\n"); + goto fail; + } + + if (missing_groups) { + /* this might be HBAC or sudo rule */ + DEBUG(SSSDBG_FUNC_DATA, "There are unresolved external group " + "memberships even after all groups " + "have been looked up on the LDAP " + "server.\n"); + } + tevent_req_done(req); + return; + } + + group_dn = ldb_dn_new(state, sysdb_ctx_get_ldb(state->group_dom->sysdb), + state->groups[state->iter]); + if (group_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_new failed.\n"); + ret = ENOMEM; + goto fail; + } + + val = ldb_dn_get_rdn_val(group_dn); + if (val == NULL || val->data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Invalid group DN [%s].\n", state->groups[state->iter]); + ret = EINVAL; + goto fail; + } + + fq_name = (const char *) val->data; + if (strchr(fq_name, '@') == NULL) { + tmp_str = sss_create_internal_fqname(state, fq_name, + state->group_dom->name); + /* keep using val->data if sss_create_internal_fqname() fails */ + if (tmp_str != NULL) { + fq_name = tmp_str; + } + } + +/* TODO: here is would be useful for have a filter type like BE_FILTER_DN to + * directly fetch the group with the corresponding DN. */ + subreq = groups_get_send(state, state->ev, + state->sdap_id_ctx, state->group_sdom, + state->sdap_id_ctx->conn, + fq_name, + BE_FILTER_NAME, + false, false, false); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "groups_get_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_add_ad_memberships_get_group_done, req); + return; + +fail: + tevent_req_error(req, ret); +} + +static void ipa_add_ad_memberships_get_group_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct add_ad_membership_state *state = tevent_req_data(req, + struct add_ad_membership_state); + int ret; + + ret = groups_get_recv(subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to read group [%s] from LDAP [%d](%s)\n", + state->groups[state->iter], ret, strerror(ret)); + + tevent_req_error(req, ret); + return; + } + + state->iter++; + ipa_add_ad_memberships_get_next(req); +} + +static errno_t ipa_add_ad_memberships_recv(struct tevent_req *req, + int *dp_error_out) +{ + struct add_ad_membership_state *state = tevent_req_data(req, + struct add_ad_membership_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + return EOK; +} + +static errno_t +search_user_or_group_by_sid_str(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *sid_str, + enum sysdb_member_type *_member_type, + struct ldb_message **_msg) +{ + errno_t ret; + struct ldb_message *msg = NULL; + const char *attrs[] = { SYSDB_NAME, + SYSDB_SID_STR, + SYSDB_ORIG_DN, + SYSDB_OBJECTCATEGORY, + SYSDB_CACHE_EXPIRE, + NULL }; + TALLOC_CTX *tmp_ctx = NULL; + char *sanitized_sid = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* In theory SID shouldn't contain any special LDAP characters, but let's + * be paranoid + */ + ret = sss_filter_sanitize(tmp_ctx, sid_str, &sanitized_sid); + if (ret != EOK) { + goto done; + } + + ret = sysdb_search_user_by_sid_str(tmp_ctx, domain, + sid_str, attrs, &msg); + if (ret == EOK) { + *_member_type = SYSDB_MEMBER_USER; + } else if (ret == ENOENT) { + ret = sysdb_search_group_by_sid_str(tmp_ctx, domain, + sid_str, attrs, &msg); + if (ret == EOK) { + *_member_type = SYSDB_MEMBER_GROUP; + } + } + + switch (ret) { + case EOK: + DEBUG(SSSDBG_TRACE_FUNC, "Found %s in sysdb\n", sid_str); + *_msg = talloc_steal(mem_ctx, msg); + break; + case ENOENT: + DEBUG(SSSDBG_TRACE_FUNC, + "Could not find %s in sysdb\n", sid_str); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "Error looking for %s in sysdb [%d]: %s\n", + sid_str, ret, sss_strerror(ret)); + break; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_ext_group_member_check(TALLOC_CTX *mem_ctx, + struct sss_domain_info *member_dom, + const char *ext_member, + enum sysdb_member_type *_member_type, + struct sysdb_attrs **_member) +{ + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + uint64_t expire; + time_t now = time(NULL); + struct ldb_message *msg; + struct sysdb_attrs **members; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = search_user_or_group_by_sid_str(tmp_ctx, member_dom, ext_member, + _member_type, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Error looking up sid %s: [%d]: %s\n", + ext_member, ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_msg2attrs(tmp_ctx, 1, &msg, &members); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not convert result to sysdb_attrs [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Return the member both expired and valid */ + *_member = talloc_steal(mem_ctx, members[0]); + + expire = ldb_msg_find_attr_as_uint64(msg, SYSDB_CACHE_EXPIRE, 0); + if (expire != 0 && expire <= now) { + DEBUG(SSSDBG_TRACE_FUNC, "%s is expired\n", ext_member); + ret = EAGAIN; + goto done; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +/* For the IPA external member resolution, we expect a SID as the input. + * The _recv() function output is the member and a type (user/group) + * since nothing else can be a group member. + */ +struct ipa_ext_member_state { + const char *ext_member; + struct sss_domain_info *dom; + + enum sysdb_member_type member_type; + struct sysdb_attrs *member; +}; + +static void ipa_ext_group_member_done(struct tevent_req *subreq); + +struct tevent_req *ipa_ext_group_member_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ext_member, + void *pvt) +{ + struct ipa_id_ctx *ipa_ctx; + struct ipa_ext_member_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct dp_id_data *ar; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ipa_ext_member_state); + if (req == NULL) { + return NULL; + } + state->ext_member = ext_member; + + ipa_ctx = talloc_get_type(pvt, struct ipa_id_ctx); + if (ipa_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Wrong private context!\n"); + ret = EINVAL; + goto immediate; + } + + state->dom = find_domain_by_sid(ipa_ctx->sdap_id_ctx->be->domain, + ext_member); + if (state->dom == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot find domain of SID [%s]\n", ext_member); + ret = ENOENT; + goto immediate; + } + + ret = ipa_ext_group_member_check(state, state->dom, ext_member, + &state->member_type, &state->member); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "external member %s already cached\n", ext_member); + goto immediate; + } + + ret = get_dp_id_data_for_sid(state, ext_member, state->dom->name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot create the account request for [%s]\n", ext_member); + goto immediate; + } + + subreq = dp_req_send(state, ipa_ctx->sdap_id_ctx->be->provider, + ar->domain, "External Member", 0, NULL, + DPT_ID, DPM_ACCOUNT_HANDLER, 0, ar, NULL); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ipa_ext_group_member_done, req); + + return req; + +immediate: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static void ipa_ext_group_member_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_ext_member_state *state = tevent_req_data(req, + struct ipa_ext_member_state); + errno_t ret; + struct ldb_message *msg; + struct sysdb_attrs **members; + struct dp_reply_std *reply; + + + ret = dp_req_recv_ptr(state, subreq, struct dp_reply_std, &reply); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "dp_req_recv failed\n"); + tevent_req_error(req, ret); + return; + } else if (reply->dp_error != DP_ERR_OK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot refresh data from DP: %u,%u: %s\n", + reply->dp_error, reply->error, reply->message); + tevent_req_error(req, EIO); + return; + } + + ret = search_user_or_group_by_sid_str(state, + state->dom, + state->ext_member, + &state->member_type, + &msg); + if (ret != EOK) { + DEBUG(ret == ENOENT ? SSSDBG_TRACE_FUNC : SSSDBG_OP_FAILURE, + "Could not find %s in sysdb [%d]: %s\n", + state->ext_member, ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = sysdb_msg2attrs(state, 1, &msg, &members); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not convert result to sysdb_attrs [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->member = members[0]; + tevent_req_done(req); +} + +errno_t ipa_ext_group_member_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + enum sysdb_member_type *_member_type, + struct sss_domain_info **_dom, + struct sysdb_attrs **_member) +{ + struct ipa_ext_member_state *state = tevent_req_data(req, + struct ipa_ext_member_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_member_type != NULL) { + *_member_type = state->member_type; + } + + if (_dom) { + *_dom = state->dom; + } + + if (_member != NULL) { + *_member = talloc_steal(mem_ctx, state->member); + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_subdomains_id.c b/src/providers/ipa/ipa_subdomains_id.c new file mode 100644 index 0000000..1b63f9e --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_id.c @@ -0,0 +1,1827 @@ +/* + SSSD + + IPA Identity Backend Module for sub-domains + + Authors: + Sumit Bose + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "util/util.h" +#include "util/sss_nss.h" +#include "util/strtonum.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_async_ad.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ad/ad_id.h" +#include "providers/ad/ad_pac.h" +#include "providers/ipa/ipa_subdomains.h" + +static struct tevent_req * +ipa_srv_ad_acct_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sysdb_attrs *override_attrs, + struct dp_id_data *ar); +static errno_t +ipa_srv_ad_acct_recv(struct tevent_req *req, int *dp_error_out); + +struct ipa_subdomain_account_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sdap_id_ctx *ctx; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct dp_id_data *ar; + + bool ipa_server_mode; + bool server_retry; + int entry_type; + const char *filter; + int filter_type; + struct sysdb_attrs *override_attrs; + struct sysdb_attrs *mapped_attrs; + char *object_sid; + + int dp_error; +}; + +static void ipa_subdomain_account_connected(struct tevent_req *subreq); +static void ipa_subdomain_account_got_override(struct tevent_req *subreq); +static void ipa_subdomain_account_done(struct tevent_req *subreq); +static errno_t ipa_subdomain_account_get_original_step(struct tevent_req *req, + struct dp_id_data *ar); + +struct tevent_req *ipa_subdomain_account_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct dp_id_data *ar) +{ + struct tevent_req *req; + struct ipa_subdomain_account_state *state; + struct tevent_req *subreq; + int ret; + + req = tevent_req_create(memctx, &state, struct ipa_subdomain_account_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->ctx = ipa_ctx->sdap_id_ctx; + state->dp_error = DP_ERR_FATAL; + + state->op = sdap_id_op_create(state, state->ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = find_domain_by_name(state->ctx->be->domain, + ar->domain, true); + if (state->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); + ret = ENOMEM; + goto fail; + } + state->sysdb = state->domain->sysdb; + state->ar = ar; + state->ipa_server_mode = dp_opt_get_bool(state->ipa_ctx->ipa_options->basic, + IPA_SERVER_MODE); + state->override_attrs = NULL; + state->mapped_attrs = NULL; + + /* With views we cannot got directly to the look up the AD objects but + * have to check first if the request matches an override in the given + * view. But there are cases where this can be skipped and the AD object + * can be searched directly: + * - if no view is defined, i.e. the server does not supprt views yet + * - searches by SID: because we do not override the SID + * - if the responder does not send the EXTRA_INPUT_MAYBE_WITH_VIEW flags, + * because in this case the entry was found in the cache and the + * original value is used for the search (e.g. during cache updates) */ + if (state->ipa_ctx->view_name == NULL + || state->ar->filter_type == BE_FILTER_SECID + || (!state->ipa_server_mode + && state->ar->extra_value != NULL + && strcmp(state->ar->extra_value, + EXTRA_INPUT_MAYBE_WITH_VIEW) != 0 )) { + ret = ipa_subdomain_account_get_original_step(req, state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_subdomain_account_get_original_step failed.\n"); + goto fail; + } + + return req; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + goto fail; + } + tevent_req_set_callback(subreq, ipa_subdomain_account_connected, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ipa_subdomain_account_connected(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_subdomain_account_state *state = tevent_req_data(req, + struct ipa_subdomain_account_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect request failed.\n"); + goto fail; + } + + subreq = ipa_get_ad_override_send(state, state->ev, state->ctx, + state->ipa_ctx->ipa_options, + dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM), + state->ipa_ctx->view_name, state->ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_subdomain_account_got_override, req); + + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +#define OVERRIDE_ANCHOR_SID_PREFIX ":SID:" +#define OVERRIDE_ANCHOR_SID_PREFIX_LEN (sizeof(OVERRIDE_ANCHOR_SID_PREFIX) -1 ) + +static void ipa_subdomain_account_got_override(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_subdomain_account_state *state = tevent_req_data(req, + struct ipa_subdomain_account_state); + int dp_error = DP_ERR_FATAL; + int ret; + const char *anchor = NULL; + struct dp_id_data *ar; + + ret = ipa_get_ad_override_recv(subreq, &dp_error, state, + &state->override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed.\n"); + goto fail; + } + tevent_req_set_callback(subreq, ipa_subdomain_account_connected, + req); + return; + } + + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + goto fail; + } + + if (state->ar->filter_type == BE_FILTER_CERT + && is_default_view(state->ipa_ctx->view_name)) { + /* The override data was found with a lookup by certificate. for the + * default view the certificate will be added to + * SYSDB_USER_MAPPED_CERT so that cache lookups will find the same + * user. If no override data was found the mapping (if any) should be + * removed. For other view this is not needed because the override + * certificate is store in the cached override object in this case. */ + state->mapped_attrs = sysdb_new_attrs(state); + if (state->mapped_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_new_attrs failed, ignored.\n"); + } else { + ret = sysdb_attrs_add_base64_blob(state->mapped_attrs, + SYSDB_USER_MAPPED_CERT, + state->ar->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_base64_blob failed, ignored.\n"); + talloc_free(state->mapped_attrs); + state->mapped_attrs = NULL; + } + } + } + + if (state->override_attrs != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Processing override.\n"); + + ret = sysdb_attrs_get_string(state->override_attrs, + SYSDB_OVERRIDE_ANCHOR_UUID, + &anchor); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto fail; + } + if (anchor != NULL && strncmp(OVERRIDE_ANCHOR_SID_PREFIX, anchor, + OVERRIDE_ANCHOR_SID_PREFIX_LEN) == 0) { + + ret = get_dp_id_data_for_sid(state, + anchor + OVERRIDE_ANCHOR_SID_PREFIX_LEN, + state->ar->domain, + &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto fail; + } + + if (state->mapped_attrs != NULL) { + /* save the SID so that SYSDB_USER_MAPPED_CERT can be added + * later to the object */ + state->object_sid = talloc_strdup(state, ar->filter_value); + if (state->object_sid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "talloc_strdup failed, ignored.\n"); + talloc_free(state->mapped_attrs); + state->mapped_attrs = NULL; + } + } + + if (state->ipa_server_mode + && (state->ar->entry_type & BE_REQ_TYPE_MASK) + == BE_REQ_INITGROUPS) { + DEBUG(SSSDBG_TRACE_ALL, + "Switching back to BE_REQ_INITGROUPS.\n"); + ar->entry_type = BE_REQ_INITGROUPS; + ar->filter_type = BE_FILTER_SECID; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported override anchor type [%s].\n", anchor); + ret = EINVAL; + goto fail; + } + } else { + if (state->mapped_attrs != NULL) { + /* remove certificate (if any) if no matching override was found */ + ret = sysdb_remove_mapped_data(state->domain, state->mapped_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_remove_mapped_data failed, " + "some cached entries might contain " + "invalid mapping data.\n"); + } + talloc_free(state->mapped_attrs); + state->mapped_attrs = NULL; + } + ar = state->ar; + } + + ret = ipa_subdomain_account_get_original_step(req, ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_subdomain_account_get_original_step failed.\n"); + goto fail; + } + + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static errno_t ipa_subdomain_account_get_original_step(struct tevent_req *req, + struct dp_id_data *ar) +{ + struct ipa_subdomain_account_state *state = tevent_req_data(req, + struct ipa_subdomain_account_state); + struct tevent_req *subreq; + + if (state->ipa_server_mode) { + subreq = ipa_srv_ad_acct_send(state, state->ev, state->ipa_ctx, + state->override_attrs, ar); + } else { + subreq = ipa_get_subdom_acct_send(state, state->ev, state->ipa_ctx, + state->override_attrs, ar); + } + + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_*_acct_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_subdomain_account_done, req); + + return EOK; +} + + +static void ipa_subdomain_account_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_subdomain_account_state *state = tevent_req_data(req, + struct ipa_subdomain_account_state); + int dp_error = DP_ERR_FATAL; + int ret; + struct ldb_result *res; + struct sss_domain_info *object_dom; + + if (state->ipa_server_mode) { + ret = ipa_srv_ad_acct_recv(subreq, &dp_error); + } else { + ret = ipa_get_subdom_acct_recv(subreq, &dp_error); + } + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_*_acct request failed: [%d]: %s.\n", + ret, sss_strerror(ret)); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (state->mapped_attrs != NULL) { + object_dom = sss_get_domain_by_sid_ldap_fallback(state->domain, + state->object_sid); + ret = sysdb_search_object_by_sid(state, + object_dom != NULL ? object_dom + : state->domain, + state->object_sid, NULL, &res); + if (ret == EOK) { + ret = sysdb_set_entry_attr(state->domain->sysdb, res->msgs[0]->dn, + state->mapped_attrs, SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_set_entry_attr failed, ignoring.\n"); + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "No cached object found, cannot add " + "mapped attribute, ignoring.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_search_object_by_sid failed, cannot add mapped " + "attribute, ignoring.\n"); + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; +} + +errno_t ipa_subdomain_account_recv(struct tevent_req *req, int *dp_error_out) +{ + struct ipa_subdomain_account_state *state = tevent_req_data(req, + struct ipa_subdomain_account_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_get_subdom_acct { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sdap_id_ctx *ctx; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct sysdb_attrs *override_attrs; + + int entry_type; + const char *filter; + int filter_type; + const char *extra_value; + bool use_pac; + struct ldb_message *user_msg; + + int dp_error; +}; + +static void ipa_get_subdom_acct_connected(struct tevent_req *subreq); +static void ipa_get_subdom_acct_done(struct tevent_req *subreq); + +struct tevent_req *ipa_get_subdom_acct_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sysdb_attrs *override_attrs, + struct dp_id_data *ar) +{ + struct tevent_req *req; + struct ipa_get_subdom_acct *state; + struct tevent_req *subreq; + int ret; + + req = tevent_req_create(memctx, &state, struct ipa_get_subdom_acct); + if (!req) return NULL; + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->ctx = ipa_ctx->sdap_id_ctx; + state->dp_error = DP_ERR_FATAL; + state->override_attrs = override_attrs; + state->use_pac = false; + + state->op = sdap_id_op_create(state, state->ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = find_domain_by_name(state->ctx->be->domain, + ar->domain, true); + if (state->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); + ret = ENOMEM; + goto fail; + } + state->sysdb = state->domain->sysdb; + + state->entry_type = (ar->entry_type & BE_REQ_TYPE_MASK); + state->filter = ar->filter_value; + state->filter_type = ar->filter_type; + state->extra_value = ar->extra_value; + + switch (state->entry_type) { + case BE_REQ_USER: + case BE_REQ_GROUP: + case BE_REQ_BY_SECID: + case BE_REQ_BY_CERT: + case BE_REQ_USER_AND_GROUP: + ret = EOK; + break; + case BE_REQ_INITGROUPS: + ret = check_if_pac_is_available(state, state->domain, ar, + &state->user_msg); + if (ret == EOK) { + state->use_pac = true; + } + + ret = EOK; + break; + default: + ret = EINVAL; + if (state->entry_type > BE_REQ__LAST) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid sub-domain request type %d.\n", + state->entry_type); + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Unhandled sub-domain request type %d.\n", + state->entry_type); + } + } + if (ret != EOK) goto fail; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + goto fail; + } + tevent_req_set_callback(subreq, ipa_get_subdom_acct_connected, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ipa_get_subdom_acct_connected(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_subdom_acct *state = tevent_req_data(req, + struct ipa_get_subdom_acct); + int dp_error = DP_ERR_FATAL; + int ret; + char *endptr; + struct req_input *req_input; + char *shortname; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (state->entry_type == BE_REQ_INITGROUPS) { + /* With V1/V2 of the extdom plugin a user lookup will resolve the full + * group membership of the user. */ + if (sdap_is_extension_supported(sdap_id_op_handle(state->op), + EXOP_SID2NAME_V1_OID) || + sdap_is_extension_supported(sdap_id_op_handle(state->op), + EXOP_SID2NAME_V2_OID)) { + state->entry_type = BE_REQ_USER; + } else { + if (state->use_pac && state->user_msg != NULL) { + /* This means the user entry is already in the cache and has + * the pac attached, we only have look up the missing groups + * and add the user to all groups. */ + + subreq = ipa_get_subdom_acct_process_pac_send(state, state->ev, + sdap_id_op_handle(state->op), + state->ipa_ctx, + state->domain, + state->user_msg); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_get_subdom_acct_process_pac failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ipa_get_subdom_acct_done, req); + + return; + } + + /* Fall through if there is no PAC */ + + DEBUG(SSSDBG_TRACE_FUNC, "Initgroups requests are not handled " \ + "by the IPA provider but are resolved " \ + "by the responder directly from the " \ + "cache.\n"); + tevent_req_error(req, ENOTSUP); + return; + } + } + + req_input = talloc(state, struct req_input); + if (req_input == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + + switch (state->filter_type) { + case BE_FILTER_NAME: + req_input->type = REQ_INP_NAME; + /* The extdom plugin expects the shortname and domain separately, + * but for UPN/email lookup we need to send the raw name */ + if (state->extra_value != NULL + && strcmp(state->extra_value, EXTRA_NAME_IS_UPN) == 0) { + req_input->inp.name = talloc_strdup(req_input, state->filter); + } else { + ret = sss_parse_internal_fqname(req_input, state->filter, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse internal name [%s]: %d\n", + state->filter, ret); + tevent_req_error(req, ret); + return; + } + + req_input->inp.name = talloc_steal(req_input, shortname); + } + if (req_input->inp.name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + break; + case BE_FILTER_IDNUM: + req_input->type = REQ_INP_ID; + req_input->inp.id = strtouint32(state->filter, &endptr, 10); + if (errno || *endptr || (state->filter == endptr)) { + tevent_req_error(req, errno ? errno : EINVAL); + return; + } + break; + case BE_FILTER_SECID: + req_input->type = REQ_INP_SECID; + req_input->inp.secid = talloc_strdup(req_input, state->filter); + if (req_input->inp.secid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + break; + case BE_FILTER_CERT: + if (sdap_is_extension_supported(sdap_id_op_handle(state->op), + EXOP_SID2NAME_V1_OID) || + sdap_is_extension_supported(sdap_id_op_handle(state->op), + EXOP_SID2NAME_V2_OID)) { + req_input->type = REQ_INP_CERT; + req_input->inp.cert = talloc_strdup(req_input, state->filter); + if (req_input->inp.cert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Lookup by certificate not supported by the server.\n"); + state->dp_error = DP_ERR_OK; + tevent_req_error(req, EINVAL); + return; + } + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Invalid sub-domain filter type.\n"); + state->dp_error = dp_error; + tevent_req_error(req, EINVAL); + return; + } + + subreq = ipa_s2n_get_acct_info_send(state, + state->ev, + state->ipa_ctx, + state->ctx->opts, + state->domain, + state->override_attrs, + sdap_id_op_handle(state->op), + state->entry_type, + req_input); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ipa_get_subdom_acct_done, req); + + return; +} + +static void ipa_get_subdom_acct_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_subdom_acct *state = tevent_req_data(req, + struct ipa_get_subdom_acct); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = ipa_s2n_get_acct_info_recv(subreq); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, ipa_get_subdom_acct_connected, req); + return; + } + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + /* FIXME: do we need some special handling of ENOENT */ + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +int ipa_get_subdom_acct_recv(struct tevent_req *req, int *dp_error_out) +{ + struct ipa_get_subdom_acct *state = tevent_req_data(req, + struct ipa_get_subdom_acct); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static struct ad_id_ctx *ipa_get_ad_id_ctx(struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *dom); + +static struct sdap_id_conn_ctx ** +ipa_ad_gc_conn_list(TALLOC_CTX *mem_ctx, struct ipa_id_ctx *ipa_ctx, + struct ad_id_ctx *ad_ctx, struct sss_domain_info *dom) +{ + struct ad_id_ctx *forest_root_ad_id_ctx; + struct sdap_id_conn_ctx **clist; + int cindex = 0; + + /* While creating the domains and sub-domains each domain gets a global + * catalog services assigned but only one should be used because the + * global catalog is by definition responsible for the whole forest so it + * does not make sense to use a global catalog service for each domain and + * in the worst case connect to the same GC multiple times. + * + * In the AD provider this is simple because the GC service of the + * configured domain AD_GC_SERVICE_NAME ("AD_GC") can be used. In the IPA + * case all domains from the trusted forest are on the level of + * sub-domains so we have to pick one. Since the forest root is linked + * from all domain of the same forest it will be the most straight forward + * choice. */ + forest_root_ad_id_ctx = ipa_get_ad_id_ctx(ipa_ctx, dom->forest_root); + if (forest_root_ad_id_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing ad_id_ctx for forest root.\n"); + return NULL; + } + + clist = talloc_zero_array(mem_ctx, struct sdap_id_conn_ctx *, 3); + if (clist == NULL) return NULL; + + /* Always try GC first */ + if (dp_opt_get_bool(forest_root_ad_id_ctx->ad_options->basic, + AD_ENABLE_GC)) { + clist[cindex] = forest_root_ad_id_ctx->gc_ctx; + clist[cindex]->ignore_mark_offline = true; + clist[cindex]->no_mpg_user_fallback = true; + cindex++; + } + + clist[cindex] = ad_get_dom_ldap_conn(ad_ctx, dom); + + return clist; +} + +/* IPA lookup for server mode. Directly to AD. */ +struct ipa_get_ad_acct_state { + int dp_error; + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct dp_id_data *ar; + struct sss_domain_info *obj_dom; + char *object_sid; + struct sysdb_attrs *override_attrs; + struct ldb_message *obj_msg; +}; + +static void ipa_get_ad_acct_ad_part_done(struct tevent_req *subreq); +static void ipa_get_ad_override_done(struct tevent_req *subreq); +static errno_t ipa_get_ad_apply_override_step(struct tevent_req *req); +static errno_t ipa_get_ad_ipa_membership_step(struct tevent_req *req); +static void ipa_id_get_groups_overrides_done(struct tevent_req *subreq); +static void ipa_get_ad_acct_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_get_ad_acct_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sysdb_attrs *override_attrs, + struct dp_id_data *ar) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_get_ad_acct_state *state; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx **clist; + struct sdap_id_ctx *sdap_id_ctx; + struct ad_id_ctx *ad_id_ctx; + + req = tevent_req_create(mem_ctx, &state, struct ipa_get_ad_acct_state); + if (req == NULL) return NULL; + + state->dp_error = -1; + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->ar = ar; + state->obj_msg = NULL; + state->override_attrs = override_attrs; + + /* This can only be a subdomain request, verify subdomain */ + state->obj_dom = find_domain_by_name(ipa_ctx->sdap_id_ctx->be->domain, + ar->domain, true); + if (state->obj_dom == NULL) { + ret = EINVAL; + goto fail; + } + + /* Let's see if this subdomain has a ad_id_ctx */ + ad_id_ctx = ipa_get_ad_id_ctx(ipa_ctx, state->obj_dom); + if (ad_id_ctx == NULL) { + ret = EINVAL; + goto fail; + } + sdap_id_ctx = ad_id_ctx->sdap_id_ctx; + + /* We read users and groups from GC. From groups, we may switch to + * using LDAP connection in the group request itself, but in order + * to resolve Universal group memberships, we also need the GC + * connection + */ + switch (state->ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_INITGROUPS: + case BE_REQ_BY_SECID: + case BE_REQ_GROUP: + clist = ipa_ad_gc_conn_list(req, ipa_ctx, ad_id_ctx, state->obj_dom); + break; + default: + clist = ad_ldap_conn_list(req, ad_id_ctx, state->obj_dom); + break; + } + + if (clist == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot generate AD connection list!\n"); + ret = ENOMEM; + goto fail; + } + + /* Now we already need ad_id_ctx in particular sdap_id_conn_ctx */ + sdom = sdap_domain_get(sdap_id_ctx->opts, state->obj_dom); + if (sdom == NULL) { + ret = EIO; + goto fail; + } + + subreq = ad_handle_acct_info_send(req, ar, sdap_id_ctx, + ad_id_ctx->ad_options, sdom, clist); + if (subreq == NULL) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_get_ad_acct_ad_part_done, req); + return req; + +fail: + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static struct ad_id_ctx * +ipa_get_ad_id_ctx(struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *dom) +{ + struct ipa_ad_server_ctx *iter; + + DLIST_FOR_EACH(iter, ipa_ctx->server_mode->trusts) { + if (iter->dom == dom) break; + } + + return (iter) ? iter->ad_id_ctx : NULL; +} + +static errno_t +get_subdomain_homedir_of_user(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + const char *fqname, uint32_t uid, + const char *original, const char **_homedir) +{ + errno_t ret; + const char *homedir; + TALLOC_CTX *tmp_ctx; + struct sss_nss_homedir_ctx homedir_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + if (strstr(dom->subdomain_homedir, "%o") != NULL && original == NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "Original home directory for user: %s is empty.\n", fqname); + ret = ERR_HOMEDIR_IS_NULL; + goto done; + } + + memset(&homedir_ctx, 0, sizeof(homedir_ctx)); + + homedir_ctx.uid = uid; + homedir_ctx.username = fqname; + homedir_ctx.domain = dom->name; + homedir_ctx.flatname = dom->flat_name; + homedir_ctx.config_homedir_substr = dom->homedir_substr; + homedir_ctx.original = original; + + /* To be compatible with the old winbind based user lookups and IPA + * clients the user name in the home directory path will be lower-case. */ + homedir = expand_homedir_template(tmp_ctx, dom->subdomain_homedir, + false, &homedir_ctx); + if (homedir == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "expand_homedir_template failed\n"); + ret = ENOMEM; + goto done; + } + + if (_homedir == NULL) { + ret = EINVAL; + goto done; + } + *_homedir = talloc_steal(mem_ctx, homedir); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +store_homedir_of_user(struct sss_domain_info *domain, + const char *fqname, const char *homedir) +{ + errno_t ret; + errno_t sret; + TALLOC_CTX *tmp_ctx; + bool in_transaction = false; + struct sysdb_attrs *attrs; + struct sysdb_ctx *sysdb = domain->sysdb; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(attrs, SYSDB_HOMEDIR, homedir); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Error setting homedir: [%s]\n", + strerror(ret)); + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + + in_transaction = true; + + ret = sysdb_set_user_attr(domain, fqname, attrs, SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to update homedir information!\n"); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot commit sysdb transaction [%d]: %s.\n", + ret, strerror(ret)); + goto done; + } + + in_transaction = false; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction.\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +apply_subdomain_homedir(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + struct ldb_message *msg) +{ + errno_t ret; + uint32_t uid; + const char *fqname; + const char *original; + const char *homedir = NULL; + struct ldb_message_element *msg_el = NULL; + size_t c; + const char *category = NULL; + size_t length = 0; + bool user_class = true; + + msg_el = ldb_msg_find_element(msg, SYSDB_OBJECTCATEGORY); + if (msg_el == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_msg_find_element failed.\n"); + ret = ENOENT; + goto done; + } + + /* The object is a user if SYSDB_OBJECTCATEGORY is SYSDB_USER_CLASS or in + * case of a MPG group lookup if SYSDB_OBJECTCATEGORY is SYSDB_GROUP_CLASS. + */ + for (c = 0; c < msg_el->num_values; c++) { + category = (const char *)msg_el->values[c].data; + length = msg_el->values[c].length; + if (strncmp(SYSDB_USER_CLASS, category, length) == 0) { + user_class = true; + break; + } + if (sss_domain_is_mpg(dom) + && strncmp(SYSDB_GROUP_CLASS, category, length) == 0) { + user_class = false; + break; + } + } + if (c == msg_el->num_values) { + DEBUG(SSSDBG_TRACE_ALL, + "User objectclass not found, object is not a user.\n"); + ret = ENOENT; + goto done; + } + + fqname = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (fqname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + uid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0); + if (uid == 0) { + if (user_class) { + DEBUG(SSSDBG_OP_FAILURE, "UID for user [%s] is unknown\n", fqname); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "No UID for object [%s], perhaps mpg\n", fqname); + } + ret = ENOENT; + goto done; + } + + original = ldb_msg_find_attr_as_string(msg, SYSDB_HOMEDIR, NULL); + if (original == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Missing homedir of %s.\n", fqname); + } + + ret = get_subdomain_homedir_of_user(mem_ctx, dom, fqname, uid, original, + &homedir); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_subdomain_homedir_of_user failed: [%d]: [%s]\n", + ret, sss_strerror(ret)); + if (ret == ERR_HOMEDIR_IS_NULL) { + /* This is not fatal, fallback_homedir will be used. */ + ret = EOK; + } + goto done; + } + + ret = store_homedir_of_user(dom, fqname, homedir); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "store_homedir_of_user failed: [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + return ret; +} + +errno_t get_object_from_cache(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct dp_id_data *ar, + struct ldb_message **_msg) +{ + errno_t ret; + uint32_t id; + struct ldb_message *msg = NULL; + struct ldb_result *res = NULL; + char *endptr; + const char *attrs[] = { SYSDB_NAME, + SYSDB_UIDNUM, + SYSDB_SID_STR, + SYSDB_OBJECTCATEGORY, + SYSDB_UUID, + SYSDB_GHOST, + SYSDB_HOMEDIR, + NULL }; + + if (ar->filter_type == BE_FILTER_SECID) { + ret = sysdb_search_object_by_sid(mem_ctx, dom, ar->filter_value, attrs, + &res); + if (ret == EOK) { + *_msg = res->msgs[0]; + } + goto done; + } else if (ar->filter_type == BE_FILTER_UUID) { + ret = sysdb_search_object_by_uuid(mem_ctx, dom, ar->filter_value, attrs, + &res); + if (ret == EOK) { + *_msg = res->msgs[0]; + } + goto done; + } else if (ar->filter_type == BE_FILTER_CERT) { + ret = sysdb_search_object_by_cert(mem_ctx, dom, ar->filter_value, attrs, + &res); + if (ret == EOK) { + if (res->count != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "More than one result found in our cache\n"); + ret = EINVAL; + } else { + *_msg = res->msgs[0]; + } + } + goto done; + } else if (ar->filter_type == BE_FILTER_IDNUM) { + id = strtouint32(ar->filter_value, &endptr, 10); + if ((errno != 0) || *endptr || (ar->filter_value == endptr)) { + ret = errno ? errno : EINVAL; + DEBUG(SSSDBG_OP_FAILURE, "strtouint32 failed.\n"); + goto done; + } + + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_GROUP: + ret = sysdb_getgrgid_attrs(mem_ctx, dom, id, attrs, &res); + if (ret == EOK) { + if (res->count == 0) { + ret = ENOENT; + } else { + msg = res->msgs[0]; + } + } + break; + case BE_REQ_INITGROUPS: + case BE_REQ_USER: + case BE_REQ_USER_AND_GROUP: + ret = sysdb_search_user_by_uid(mem_ctx, dom, id, attrs, &msg); + if (ret == ENOENT && (ar->entry_type & BE_REQ_TYPE_MASK) + == BE_REQ_USER_AND_GROUP) { + ret = sysdb_getgrgid_attrs(mem_ctx, dom, id, attrs, &res); + if (ret == EOK) { + if (res->count == 0) { + ret = ENOENT; + } else { + msg = res->msgs[0]; + } + } + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected entry type [%d].\n", + (ar->entry_type & BE_REQ_TYPE_MASK)); + ret = EINVAL; + goto done; + } + } else if (ar->filter_type == BE_FILTER_NAME) { + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_GROUP: + ret = sysdb_search_group_by_name(mem_ctx, dom, ar->filter_value, + attrs, &msg); + break; + case BE_REQ_INITGROUPS: + case BE_REQ_USER: + case BE_REQ_USER_AND_GROUP: + if (ar->extra_value + && strcmp(ar->extra_value, EXTRA_NAME_IS_UPN) == 0) { + ret = sysdb_search_user_by_upn(mem_ctx, dom, false, ar->filter_value, + attrs, &msg); + } else { + ret = sysdb_search_user_by_name(mem_ctx, dom, ar->filter_value, + attrs, &msg); + if (ret == ENOENT && (ar->entry_type & BE_REQ_TYPE_MASK) + == BE_REQ_USER_AND_GROUP) { + ret = sysdb_search_group_by_name(mem_ctx, dom, + ar->filter_value, attrs, + &msg); + } + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected entry type [%d].\n", + (ar->entry_type & BE_REQ_TYPE_MASK)); + ret = EINVAL; + goto done; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected filter type.\n"); + ret = EINVAL; + goto done; + } + + if (ret == EOK) { + *_msg = msg; + } + +done: + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to make request to our cache: [%d]: [%s]\n", + ret, sss_strerror(ret)); + } else { + DEBUG(SSSDBG_FUNC_DATA, "Object wasn't found in cache\n"); + } + } + + return ret; +} + +static void +ipa_get_ad_acct_ad_part_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + errno_t ret; + const char *sid; + struct dp_id_data *ar; + + ret = ad_handle_acct_info_recv(subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret == ERR_SUBDOM_INACTIVE) { + tevent_req_error(req, ret); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "AD lookup failed: %d\n", ret); + tevent_req_error(req, ret); + return; + } + + ret = get_object_from_cache(state, state->obj_dom, state->ar, + &state->obj_msg); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Object not found, ending request\n"); + tevent_req_done(req); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_object_from_cache failed.\n"); + goto fail; + } + + ret = apply_subdomain_homedir(state, state->obj_dom, + state->obj_msg); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "apply_subdomain_homedir failed: [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto fail; + } + + if (state->override_attrs == NULL) { + sid = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_SID_STR, NULL); + if (sid == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find a SID.\n"); + ret = EINVAL; + goto fail; + } + + state->object_sid = talloc_strdup(state, sid); + if (state->object_sid == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto fail; + } + + ret = get_dp_id_data_for_sid(state, state->object_sid, + state->obj_dom->name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto fail; + } + + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + state->ipa_ctx->server_mode->realm, + state->ipa_ctx->view_name, + ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_get_ad_override_done, req); + } else { + ret = ipa_get_ad_apply_override_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_get_ad_apply_override_step failed.\n"); + goto fail; + } + } + + return; + +fail: + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; +} + + +static void +ipa_get_ad_override_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + errno_t ret; + + ret = ipa_get_ad_override_recv(subreq, &state->dp_error, state, + &state->override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + tevent_req_error(req, ret); + return; + + } + + ret = ipa_get_ad_apply_override_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_apply_override_step failed.\n"); + goto fail; + } + + return; + +fail: + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; +} + +static void ipa_check_ghost_members_done(struct tevent_req *subreq); +static errno_t ipa_check_ghost_members(struct tevent_req *req) +{ + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + errno_t ret; + struct tevent_req *subreq; + struct ldb_message_element *ghosts = NULL; + + + if (state->obj_msg == NULL) { + ret = get_object_from_cache(state, state->obj_dom, state->ar, + &state->obj_msg); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Object not found, ending request\n"); + return EOK; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_object_from_cache failed.\n"); + return ret; + } + } + + ghosts = ldb_msg_find_element(state->obj_msg, SYSDB_GHOST); + + if (ghosts != NULL) { + /* Resolve ghost members */ + subreq = ipa_resolve_user_list_send(state, state->ev, + state->ipa_ctx, + state->obj_dom->name, + ghosts); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_check_ghost_members_done, req); + return EAGAIN; + } + + return EOK; +} + +static void ipa_check_ghost_members_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + int ret; + + ret = ipa_resolve_user_list_recv(subreq, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list request failed [%d]\n", + ret); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +static errno_t ipa_get_ad_apply_override_step(struct tevent_req *req) +{ + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + errno_t ret; + struct tevent_req *subreq; + const char *obj_name; + int entry_type; + size_t groups_count = 0; + struct ldb_message **groups = NULL; + const char *attrs[] = SYSDB_INITGR_ATTRS; + + if (state->override_attrs != NULL) { + /* We are in ipa-server-mode, so the view is the default view by + * definition. */ + ret = sysdb_apply_default_override(state->obj_dom, + state->override_attrs, + state->obj_msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_apply_default_override failed.\n"); + return ret; + } + } + + entry_type = (state->ar->entry_type & BE_REQ_TYPE_MASK); + if (entry_type != BE_REQ_INITGROUPS + && entry_type != BE_REQ_USER + && entry_type != BE_REQ_BY_SECID + && entry_type != BE_REQ_GROUP) { + tevent_req_done(req); + return EOK; + } + + /* expand ghost members, if any, to get group members with overrides + * right. */ + if (entry_type == BE_REQ_GROUP) { + ret = ipa_check_ghost_members(req); + if (ret == EOK) { + tevent_req_done(req); + return EOK; + } else if (ret == EAGAIN) { + return EOK; + } else { + DEBUG(SSSDBG_OP_FAILURE, "ipa_check_ghost_members failed.\n"); + return ret; + } + } + + /* Replace ID with name in search filter */ + if ((entry_type == BE_REQ_USER && state->ar->filter_type == BE_FILTER_IDNUM) + || (entry_type == BE_REQ_INITGROUPS + && state->ar->filter_type == BE_FILTER_SECID) + || entry_type == BE_REQ_BY_SECID) { + if (state->obj_msg == NULL) { + ret = get_object_from_cache(state, state->obj_dom, state->ar, + &state->obj_msg); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Object not found, ending request\n"); + tevent_req_done(req); + return EOK; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_object_from_cache failed.\n"); + return ret; + } + } + + obj_name = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_NAME, + NULL); + if (obj_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cached object has no name.\n"); + return EINVAL; + } + + state->ar->filter_value = talloc_strdup(state->ar, obj_name); + if (state->ar->filter_value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + state->ar->filter_type = BE_FILTER_NAME; + state->ar->entry_type = BE_REQ_USER; + } + + /* Lookup all groups the user is a member of which do not have ORIGINALAD + * attributes set, i.e. where overrides might not have been applied. */ + ret = sysdb_asq_search(state, state->obj_dom, state->obj_msg->dn, + "(&("SYSDB_GC")("SYSDB_GIDNUM"=*)" \ + "("SYSDB_POSIX"=TRUE)" \ + "(!("ORIGINALAD_PREFIX SYSDB_GIDNUM"=*))" \ + "(!("ORIGINALAD_PREFIX SYSDB_NAME"=*)))", + SYSDB_INITGR_ATTR, + attrs, &groups_count, &groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_groups_without_orig failed.\n"); + return ret; + } + + if (groups != NULL) { + subreq = ipa_initgr_get_overrides_send(state, state->ev, state->ipa_ctx, + state->obj_dom, groups_count, + groups, SYSDB_SID_STR); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_initgr_get_overrides_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_id_get_groups_overrides_done, req); + return EOK; + } + + ret = ipa_get_ad_ipa_membership_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_ipa_membership_step failed.\n"); + return ret; + } + + return EOK; +} + +static void ipa_id_get_groups_overrides_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + errno_t ret; + + ret = ipa_initgr_get_overrides_recv(subreq, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "IPA resolve user groups overrides failed [%d].\n", ret); + tevent_req_error(req, ret); + return; + } + + ret = ipa_get_ad_ipa_membership_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_ipa_membership_step failed.\n"); + tevent_req_error(req, ret); + return; + } + + return; +} + +static errno_t ipa_get_ad_ipa_membership_step(struct tevent_req *req) +{ + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + struct tevent_req *subreq; + + /* For initgroups request we have to check IPA group memberships of AD + * users. This has to be done for other user-request as well to make sure + * IPA related attributes are not overwritten. */ + subreq = ipa_get_ad_memberships_send(state, state->ev, state->ar, + state->ipa_ctx->server_mode, + state->obj_dom, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->server_mode->realm); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_memberships_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_get_ad_acct_done, req); + + return EOK; +} + +static void +ipa_get_ad_acct_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + errno_t ret; + + ret = ipa_get_ad_memberships_recv(subreq, &state->dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA external groups lookup failed: %d\n", + ret); + tevent_req_error(req, ret); + return; + + } + + tevent_req_done(req); +} + +static errno_t +ipa_get_ad_acct_recv(struct tevent_req *req, int *dp_error_out) +{ + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_srv_ad_acct_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sysdb_attrs *override_attrs; + struct dp_id_data *ar; + + struct sss_domain_info *obj_dom; + struct be_ctx *be_ctx; + bool retry; + + int dp_error; +}; + +static int ipa_srv_ad_acct_lookup_step(struct tevent_req *req); +static void ipa_srv_ad_acct_lookup_done(struct tevent_req *subreq); +static void ipa_srv_ad_acct_retried(struct tevent_req *subreq); + +static struct tevent_req * +ipa_srv_ad_acct_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sysdb_attrs *override_attrs, + struct dp_id_data *ar) +{ + errno_t ret; + struct tevent_req *req; + struct ipa_srv_ad_acct_state *state; + + req = tevent_req_create(mem_ctx, &state, struct ipa_srv_ad_acct_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->override_attrs = override_attrs; + state->ar = ar; + state->retry = true; + state->dp_error = DP_ERR_FATAL; + state->be_ctx = ipa_ctx->sdap_id_ctx->be; + + state->obj_dom = find_domain_by_name( + state->ipa_ctx->sdap_id_ctx->be->domain, + state->ar->domain, true); + if (state->obj_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Domain not found\n"); + ret = ERR_DOMAIN_NOT_FOUND; + goto fail; + } + + ret = ipa_srv_ad_acct_lookup_step(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static int ipa_srv_ad_acct_lookup_step(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct ipa_srv_ad_acct_state *state = tevent_req_data(req, + struct ipa_srv_ad_acct_state); + + DEBUG(SSSDBG_TRACE_FUNC, "Looking up AD account\n"); + subreq = ipa_get_ad_acct_send(state, state->ev, state->ipa_ctx, + state->override_attrs, + state->ar); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_srv_ad_acct_lookup_done, req); + + return EOK; +} + +static void ipa_srv_ad_acct_lookup_done(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error = DP_ERR_FATAL; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_srv_ad_acct_state *state = tevent_req_data(req, + struct ipa_srv_ad_acct_state); + + ret = ipa_get_ad_acct_recv(subreq, &dp_error); + talloc_free(subreq); + if (ret == ERR_SUBDOM_INACTIVE && state->retry == true) { + + state->retry = false; + + DEBUG(SSSDBG_MINOR_FAILURE, + "Subdomain lookup failed, will try to reset subdomain.\n"); + subreq = ipa_server_trusted_dom_setup_send(state, state->ev, + state->be_ctx, + state->ipa_ctx, + state->obj_dom); + if (subreq == NULL) { + goto fail; + } + tevent_req_set_callback(subreq, ipa_srv_ad_acct_retried, req); + return; + } else if (ret != EOK) { + be_mark_dom_offline(state->obj_dom, state->be_ctx); + + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_*_acct request failed: [%d]: %s.\n", + ret, sss_strerror(ret)); + goto fail; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); +} + +static void ipa_srv_ad_acct_retried(struct tevent_req *subreq) +{ + errno_t ret; + struct ad_id_ctx *ad_id_ctx; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_srv_ad_acct_state *state = tevent_req_data(req, + struct ipa_srv_ad_acct_state); + + ret = ipa_server_trusted_dom_setup_recv(subreq); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to re-set subdomain [%d]: %s\n", ret, sss_strerror(ret)); + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Subdomain re-set, will retry lookup\n"); + ad_id_ctx = ipa_get_ad_id_ctx(state->ipa_ctx, state->obj_dom); + if (ad_id_ctx == NULL || ad_id_ctx->ad_options == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No AD ID ctx or no ID CTX options?\n"); + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, EINVAL); + return; + } + + ad_failover_reset(state->be_ctx, ad_id_ctx->ad_options->service); + + ret = ipa_srv_ad_acct_lookup_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to look up AD acct [%d]: %s\n", ret, sss_strerror(ret)); + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; + } +} + +static errno_t +ipa_srv_ad_acct_recv(struct tevent_req *req, int *dp_error_out) +{ + struct ipa_srv_ad_acct_state *state = tevent_req_data(req, + struct ipa_srv_ad_acct_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} diff --git a/src/providers/ipa/ipa_subdomains_passkey.c b/src/providers/ipa/ipa_subdomains_passkey.c new file mode 100644 index 0000000..d5dd275 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_passkey.c @@ -0,0 +1,146 @@ +/* + SSSD + + IPA Subdomains Passkey Module + + Authors: + Justin Stephenson + + Copyright (C) 2022 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_ops.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ipa/ipa_opts.h" +#include "providers/ipa/ipa_config.h" +#include "providers/ipa/ipa_subdomains_passkey.h" +#include "db/sysdb_passkey_user_verification.h" + +#include +#define IPA_PASSKEY_VERIFICATION "ipaRequireUserVerification" +#define IPA_PASSKEY_CONFIG_FILTER "cn=passkeyconfig" + +void ipa_subdomains_passkey_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_subdomains_passkey_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_passkey_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + const char *attrs[] = { IPA_PASSKEY_VERIFICATION, NULL }; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_passkey_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->domain = sd_ctx->be_ctx->domain; + state->sdap_opts = sd_ctx->sdap_id_ctx->opts; + + subreq = ipa_get_config_send(state, ev, sh, sd_ctx->sdap_id_ctx->opts, + state->domain->name, attrs, IPA_PASSKEY_CONFIG_FILTER, NULL); + + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_passkey_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +void ipa_subdomains_passkey_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_passkey_state *state; + struct tevent_req *req; + struct sysdb_attrs *config; + const char *user_verification = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_passkey_state); + + ret = ipa_get_config_recv(subreq, state, &config); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to get data from LDAP [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (config != NULL) { + ret = sysdb_attrs_get_string(config, IPA_PASSKEY_VERIFICATION, + &user_verification); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, "Retrieved [%s] from [%s] attribute.\n", + user_verification, IPA_PASSKEY_VERIFICATION); + } + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get passkey user verification " + "value [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } else if (ret == ENOENT) { + user_verification = NULL; + } + } + + ret = sysdb_domain_update_passkey_user_verification( + state->domain->sysdb, state->domain->name, + user_verification); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_domain_passkey_user_verification() [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t ipa_subdomains_passkey_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ipa/ipa_subdomains_passkey.h b/src/providers/ipa/ipa_subdomains_passkey.h new file mode 100644 index 0000000..ecbf239 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_passkey.h @@ -0,0 +1,45 @@ +/* + SSSD + + IPA Subdomains Passkey Module + + Authors: + Justin Stephenson + + Copyright (C) 2022 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _IPA_SUBDOMAINS_PASSKEY_H_ +#define _IPA_SUBDOMAINS_PASSKEY_H_ + +#include "providers/backend.h" +#include "providers/ipa/ipa_common.h" +#include "config.h" + +struct ipa_subdomains_passkey_state { + struct sss_domain_info *domain; + struct sdap_options *sdap_opts; +}; + +struct tevent_req * +ipa_subdomains_passkey_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh); + +errno_t ipa_subdomains_passkey_recv(struct tevent_req *req); + +#endif /* _IPA_SUBDOMAINS_PASSKEY_H_ */ diff --git a/src/providers/ipa/ipa_subdomains_server.c b/src/providers/ipa/ipa_subdomains_server.c new file mode 100644 index 0000000..aaedf62 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_server.c @@ -0,0 +1,1215 @@ +/* + SSSD + + IPA Subdomains Module - server mode + + Authors: + Sumit Bose + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_id.h" + +/* These constants are defined in MS-ADTS 6.1.6.7.1 + * https://msdn.microsoft.com/en-us/library/cc223768.aspx + */ +#define LSA_TRUST_DIRECTION_INBOUND 0x00000001 +#define LSA_TRUST_DIRECTION_OUTBOUND 0x00000002 +#define LSA_TRUST_DIRECTION_MASK (LSA_TRUST_DIRECTION_INBOUND | LSA_TRUST_DIRECTION_OUTBOUND) + +static char *forest_keytab(TALLOC_CTX *mem_ctx, const char *forest) +{ + return talloc_asprintf(mem_ctx, + "%s/%s.keytab", IPA_TRUST_KEYTAB_DIR, forest); +} + +static char *subdomain_trust_princ(TALLOC_CTX *mem_ctx, + const char *forest_realm, + struct sss_domain_info *sd) +{ + if (sd->parent->flat_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown flat name for parent %s\n", sd->parent->name); + return NULL; + } + + return talloc_asprintf(mem_ctx, "%s$@%s", + sd->parent->flat_name, forest_realm); +} + +static uint32_t default_direction(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + struct sysdb_attrs *attrs) +{ + struct ldb_dn *dn = NULL; + uint32_t direction; + + dn = ipa_subdom_ldb_dn(mem_ctx, ldb_ctx, attrs); + if (dn == NULL) { + /* Shouldn't happen, but let's try system keytab in this case */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot determine subdomain DN, falling back to two-way trust\n"); + return (LSA_TRUST_DIRECTION_INBOUND|LSA_TRUST_DIRECTION_OUTBOUND); + } + + if (ipa_subdom_is_member_dom(dn) == true) { + /* It's expected member domains do not have the direction */ + direction = 0; + } else { + /* Old server? Default to 2way trust */ + direction = (LSA_TRUST_DIRECTION_INBOUND|LSA_TRUST_DIRECTION_OUTBOUND); + } + + talloc_free(dn); + return direction; +} + +errno_t ipa_server_get_trust_direction(struct sysdb_attrs *sd, + struct ldb_context *ldb_ctx, + uint32_t *_direction) +{ + uint32_t ipa_trust_direction = 0; + uint32_t direction; + int ret; + + ret = sysdb_attrs_get_uint32_t(sd, IPA_TRUST_DIRECTION, + &ipa_trust_direction); + DEBUG(SSSDBG_TRACE_INTERNAL, + "Raw %s value: %d\n", IPA_TRUST_DIRECTION, ipa_trust_direction); + if (ret == ENOENT) { + direction = default_direction(sd, ldb_ctx, sd); + } else if (ret == EOK) { + /* Just store the AD value in SYSDB, we will check it while we're + * trying to use the trust */ + direction = ipa_trust_direction; + } else { + return ret; + } + + *_direction = direction; + return EOK; +} + +const char *ipa_trust_dir2str(uint32_t direction) +{ + if ((direction & LSA_TRUST_DIRECTION_OUTBOUND) + && (direction & LSA_TRUST_DIRECTION_INBOUND)) { + return "two-way trust"; + } else if (direction & LSA_TRUST_DIRECTION_OUTBOUND) { + return "one-way outbound: local domain is trusted by remote domain"; + } else if (direction & LSA_TRUST_DIRECTION_INBOUND) { + return "one-way inbound: local domain trusts the remote domain"; + } else if (direction == 0) { + return "not set"; + } + + return "unknown"; +} + +#ifndef IPA_GETKEYTAB_TIMEOUT +#define IPA_GETKEYTAB_TIMEOUT 5 +#endif /* IPA_GETKEYTAB_TIMEOUT */ + +static struct ad_options * +ipa_create_1way_trust_ctx(struct ipa_id_ctx *id_ctx, + struct be_ctx *be_ctx, + const char *subdom_conf_path, + const char *forest, + const char *forest_realm, + struct sss_domain_info *subdom) +{ + char *keytab; + char *principal; + struct ad_options *ad_options; + + keytab = forest_keytab(id_ctx, forest); + principal = subdomain_trust_princ(id_ctx, forest_realm, subdom); + if (keytab == NULL || principal == NULL) { + return NULL; + } + + ad_options = ad_create_1way_trust_options(id_ctx, + be_ctx->cdb, + subdom_conf_path, + be_ctx->provider, + subdom, + id_ctx->server_mode->hostname, + keytab, + principal); + if (ad_options == NULL) { + talloc_free(keytab); + talloc_free(principal); + return NULL; + } + + return ad_options; +} + +static struct ad_options *ipa_ad_options_new(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom) +{ + struct ad_options *ad_options = NULL; + uint32_t direction; + const char *forest; + const char *forest_realm; + char *subdom_conf_path; + int ret; + + /* Trusts are only established with forest roots */ + direction = subdom->forest_root->trust_direction; + forest_realm = subdom->forest_root->realm; + forest = subdom->forest_root->forest; + + subdom_conf_path = subdomain_create_conf_path(id_ctx, subdom); + if (subdom_conf_path == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "subdom_conf_path failed\n"); + return NULL; + } + + /* In both inbound and outbound trust cases we should be + * using trusted domain object in a trusted domain space, + * thus we always should be initializing principals/keytabs + * as if we are running one-way trust */ + if (direction & LSA_TRUST_DIRECTION_MASK) { + ad_options = ipa_create_1way_trust_ctx(id_ctx, be_ctx, + subdom_conf_path, forest, + forest_realm, subdom); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported trust direction!\n"); + ad_options = NULL; + } + + if (ad_options == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD options\n"); + talloc_free(subdom_conf_path); + return NULL; + } + + ret = ad_inherit_opts_if_needed(id_ctx->ipa_options->id->basic, + ad_options->id->basic, be_ctx->cdb, + subdom_conf_path, SDAP_SASL_MECH); + talloc_free(subdom_conf_path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to inherit option [%s] to sub-domain [%s]. " + "This error is ignored but might cause issues or unexpected " + "behavior later on.\n", + id_ctx->ipa_options->id->basic[SDAP_SASL_MECH].opt_name, + subdom->name); + + return NULL; + } + + return ad_options; +} + + +static errno_t +ipa_ad_ctx_new(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom, + struct ad_id_ctx **_ad_id_ctx) +{ + struct ad_options *ad_options; + struct ad_id_ctx *ad_id_ctx; + const char *gc_service_name; + const char *service_name; + struct ad_srv_plugin_ctx *srv_ctx; + const char *ad_domain; + const char *ad_site_override; + const char *ad_servers; + const char *ad_backup_servers; + struct sdap_domain *sdom; + errno_t ret; + const char *extra_attrs; + bool use_kdcinfo = false; + size_t n_lookahead_primary = (size_t)-1; + size_t n_lookahead_backup = (size_t)-1; + + ad_domain = subdom->name; + DEBUG(SSSDBG_TRACE_LIBS, "Setting up AD subdomain %s\n", subdom->name); + + ad_options = ipa_ad_options_new(be_ctx, id_ctx, subdom); + if (ad_options == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD options\n"); + talloc_free(ad_options); + return ENOMEM; + } + + extra_attrs = dp_opt_get_string(id_ctx->sdap_id_ctx->opts->basic, + SDAP_USER_EXTRA_ATTRS); + if (extra_attrs != NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "Setting extra attrs for subdomain [%s] to [%s].\n", ad_domain, + extra_attrs); + + ret = dp_opt_set_string(ad_options->id->basic, SDAP_USER_EXTRA_ATTRS, + extra_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "dp_opt_set_string failed.\n"); + talloc_free(ad_options); + return ret; + } + + ret = sdap_extend_map_with_list(ad_options->id, ad_options->id, + SDAP_USER_EXTRA_ATTRS, + ad_options->id->user_map, + SDAP_OPTS_USER, + &ad_options->id->user_map, + &ad_options->id->user_map_cnt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_extend_map_with_list failed.\n"); + talloc_free(ad_options); + return ret; + } + } else { + DEBUG(SSSDBG_TRACE_ALL, "No extra attrs set.\n"); + } + + gc_service_name = talloc_asprintf(ad_options, "sd_gc_%s", subdom->name); + if (gc_service_name == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + + service_name = talloc_asprintf(ad_options, "sd_%s", subdom->name); + if (service_name == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + + ad_servers = dp_opt_get_string(ad_options->basic, AD_SERVER); + ad_backup_servers = dp_opt_get_string(ad_options->basic, AD_BACKUP_SERVER); + + if (id_ctx->ipa_options != NULL && id_ctx->ipa_options->auth != NULL) { + use_kdcinfo = dp_opt_get_bool(id_ctx->ipa_options->auth, + KRB5_USE_KDCINFO); + sss_krb5_parse_lookahead( + dp_opt_get_string(id_ctx->ipa_options->auth, KRB5_KDCINFO_LOOKAHEAD), + &n_lookahead_primary, + &n_lookahead_backup); + } + + DEBUG(SSSDBG_TRACE_ALL, + "Init failover for [%s][%s] with use_kdcinfo [%s].\n", + subdom->name, subdom->realm, use_kdcinfo ? "true" : "false"); + + /* Set KRB5 realm to same as the one of IPA when IPA + * is able to attach PAC. For testing, use hardcoded. */ + /* Why? */ + ret = ad_failover_init(ad_options, be_ctx, ad_servers, ad_backup_servers, + subdom->realm, + service_name, gc_service_name, + subdom->name, use_kdcinfo, false, + n_lookahead_primary, n_lookahead_backup, + &ad_options->service); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD failover\n"); + talloc_free(ad_options); + return ret; + } + + ad_id_ctx = ad_id_ctx_init(ad_options, be_ctx); + if (ad_id_ctx == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + ad_id_ctx->sdap_id_ctx->opts = ad_options->id; + ad_options->id_ctx = ad_id_ctx; + + ad_site_override = dp_opt_get_string(ad_options->basic, AD_SITE); + + /* use AD plugin */ + srv_ctx = ad_srv_plugin_ctx_init(be_ctx, be_ctx, be_ctx->be_res, + default_host_dbs, + ad_id_ctx->ad_options->id, + ad_id_ctx->ad_options, + id_ctx->server_mode->hostname, + ad_domain, + ad_site_override); + if (srv_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n"); + return ENOMEM; + } + be_fo_set_srv_lookup_plugin(be_ctx, ad_srv_plugin_send, + ad_srv_plugin_recv, srv_ctx, "AD"); + + ret = sdap_domain_subdom_add(ad_id_ctx->sdap_id_ctx, + ad_id_ctx->sdap_id_ctx->opts->sdom, + subdom->parent); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize sdap domain\n"); + talloc_free(ad_options); + return ret; + } + + sdom = sdap_domain_get(ad_id_ctx->sdap_id_ctx->opts, subdom); + if (sdom == NULL) { + return EFAULT; + } + + ret = ad_set_search_bases(ad_options->id, sdom); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD search bases\n"); + talloc_free(ad_options); + return ret; + } + + sdap_inherit_options(subdom->parent->sd_inherit, + id_ctx->sdap_id_ctx->opts, + ad_id_ctx->sdap_id_ctx->opts); + + ret = sdap_id_setup_tasks(be_ctx, + ad_id_ctx->sdap_id_ctx, + sdom, + ldap_id_enumeration_send, + ldap_id_enumeration_recv, + ad_id_ctx->sdap_id_ctx); + if (ret != EOK) { + talloc_free(ad_options); + return ret; + } + + sdom->pvt = ad_id_ctx; + + /* Set up the ID mapping object */ + ad_id_ctx->sdap_id_ctx->opts->idmap_ctx = + id_ctx->sdap_id_ctx->opts->idmap_ctx; + + /* Set up the certificate mapping context */ + ad_id_ctx->sdap_id_ctx->opts->sdap_certmap_ctx = + id_ctx->sdap_id_ctx->opts->sdap_certmap_ctx; + + *_ad_id_ctx = ad_id_ctx; + return EOK; +} + +struct ipa_getkeytab_state { + int child_status; + struct sss_child_ctx_old *child_ctx; + struct tevent_timer *timeout_handler; +}; + +static void ipa_getkeytab_exec(const char *ccache, + const char *server, + const char *principal, + const char *keytab_path); +static void ipa_getkeytab_done(int child_status, + struct tevent_signal *sige, + void *pvt); +static void ipa_getkeytab_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +static struct tevent_req *ipa_getkeytab_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ccache, + const char *server, + const char *principal, + const char *keytab) + + +{ + errno_t ret; + struct tevent_req *req = NULL; + struct ipa_getkeytab_state *state; + pid_t child_pid; + struct timeval tv; + + req = tevent_req_create(mem_ctx, &state, struct ipa_getkeytab_state); + if (req == NULL) { + return NULL; + } + state->child_status = EFAULT; + + if (server == NULL || principal == NULL || keytab == NULL) { + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Retrieving keytab for %s from %s into %s using ccache %s\n", + principal, server, keytab, ccache); + + child_pid = fork(); + if (child_pid == 0) { /* child */ + ipa_getkeytab_exec(ccache, server, principal, keytab); + } else if (child_pid > 0) { /* parent */ + /* Set up SIGCHLD handler */ + ret = child_handler_setup(ev, child_pid, ipa_getkeytab_done, req, + &state->child_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_IPA_GETKEYTAB_FAILED; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(IPA_GETKEYTAB_TIMEOUT, 0); + state->timeout_handler = tevent_add_timer(ev, req, tv, + ipa_getkeytab_timeout, req); + if(state->timeout_handler == NULL) { + ret = ERR_IPA_GETKEYTAB_FAILED; + goto done; + } + + /* Now either wait for the timeout to fire or the child + * to finish + */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fork failed [%d][%s].\n", ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void ipa_getkeytab_exec(const char *ccache, + const char *server, + const char *principal, + const char *keytab_path) +{ + errno_t ret; + int debug_fd; + const char *gkt_env[3] = { NULL, "_SSS_LOOPS=NO", NULL }; + + if (debug_level >= SSSDBG_TRACE_LIBS) { + debug_fd = get_fd_from_debug_file(); + ret = dup2(debug_fd, STDERR_FILENO); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "dup2 failed [%d][%s].\n", ret, sss_strerror(ret)); + /* stderr is not fatal */ + } + } + + gkt_env[0] = talloc_asprintf(NULL, "KRB5CCNAME=%s", ccache); + if (gkt_env[0] == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to format KRB5CCNAME\n"); + exit(1); + } + + /* ipa-getkeytab cannot add keys to an empty file, let's unlink it and only + * use the filename */ + ret = unlink(keytab_path); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to unlink the temporary ccname [%d][%s]\n", + ret, sss_strerror(ret)); + exit(1); + } + + errno = 0; + ret = execle(IPA_GETKEYTAB_PATH, IPA_GETKEYTAB_PATH, + "-r", "-s", server, "-p", principal, "-k", keytab_path, NULL, + gkt_env); + + DEBUG(SSSDBG_FATAL_FAILURE, + "execle returned %d, this shouldn't happen!\n", ret); + + /* The child should never end up here */ + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "execle failed [%d][%s].\n", ret, sss_strerror(ret)); + exit(1); +} + +static void ipa_getkeytab_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct ipa_getkeytab_state *state = + tevent_req_data(req, struct ipa_getkeytab_state); + + state->child_status = child_status; + + if (WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa-getkeytab failed with status [%d]\n", child_status); + tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED); + return; + } + + if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa-getkeytab was terminated by signal [%d]\n", + WTERMSIG(child_status)); + tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED); + return; + } + + tevent_req_done(req); +} + +static void ipa_getkeytab_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = + talloc_get_type(pvt, struct tevent_req); + struct ipa_getkeytab_state *state = + tevent_req_data(req, struct ipa_getkeytab_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for retrieving keytab from IPA server\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED); +} + +static errno_t ipa_getkeytab_recv(struct tevent_req *req, int *child_status) +{ + struct ipa_getkeytab_state *state = + tevent_req_data(req, struct ipa_getkeytab_state); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "ipa-getkeytab status %d\n", state->child_status); + if (child_status) { + *child_status = state->child_status; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t ipa_check_keytab(const char *keytab, + uid_t kt_owner_uid, + gid_t kt_owner_gid) +{ + errno_t ret; + + ret = check_file(keytab, getuid(), getgid(), S_IFREG|0600, 0, NULL, false); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "Keytab %s is not present\n", keytab); + goto done; + } else if (ret != EOK) { + if (kt_owner_uid) { + ret = check_file(keytab, kt_owner_uid, kt_owner_gid, + S_IFREG|0600, 0, NULL, false); + } + + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to check for %s\n", keytab); + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Keytab %s is not present\n", keytab); + } + } + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "keytab %s already exists\n", keytab); + ret = EOK; +done: + return ret; +} + +struct ipa_server_trusted_dom_setup_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_id_ctx *id_ctx; + struct sss_domain_info *subdom; + + uint32_t direction; + const char *forest; + const char *keytab; + char *new_keytab; + const char *principal; + const char *forest_realm; + const char *ccache; +}; + +static errno_t ipa_server_trusted_dom_setup_1way(struct tevent_req *req); +static void ipa_server_trust_1way_kt_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_server_trusted_dom_setup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom) +{ + struct tevent_req *req = NULL; + struct ipa_server_trusted_dom_setup_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_server_trusted_dom_setup_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->be_ctx = be_ctx; + state->id_ctx = id_ctx; + state->subdom = subdom; + + /* Trusts are only established with forest roots */ + if (subdom->forest_root == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Subdomain %s has no forest root?\n", subdom->name); + ret = ERR_TRUST_FOREST_UNKNOWN; + goto immediate; + } + + state->direction = subdom->forest_root->trust_direction; + state->forest = subdom->forest_root->forest; + state->forest_realm = subdom->forest_root->realm; + state->ccache = talloc_asprintf(state, "%s/ccache_%s", + DB_PATH, subdom->parent->realm); + if (state->ccache == NULL) { + ret = ENOMEM; + goto immediate; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Trust direction of subdom %s from forest %s is: %s\n", + subdom->name, state->forest, + ipa_trust_dir2str(state->direction)); + + /* For both inbound and outbound trusts use a special keytab + * as this allows us to reuse the same logic in FreeIPA for + * both Microsoft AD and Samba AD */ + if (state->direction & LSA_TRUST_DIRECTION_MASK) { + /* Need special keytab */ + ret = ipa_server_trusted_dom_setup_1way(req); + if (ret == EAGAIN) { + /* In progress.. */ + return req; + } else if (ret == EOK) { + /* Keytab available, shortcut */ + ret = EOK; + goto immediate; + } + } else { + /* Even unset is an error at this point */ + DEBUG(SSSDBG_OP_FAILURE, + "Subdomain %s has trust direction %d\n", + subdom->name, subdom->trust_direction); + ret = ERR_TRUST_NOT_SUPPORTED; + } + +immediate: + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not add trusted subdomain %s from forest %s\n", + subdom->name, state->forest); + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_server_trusted_dom_setup_1way(struct tevent_req *req) +{ + errno_t ret; + struct tevent_req *subreq = NULL; + struct ipa_server_trusted_dom_setup_state *state = + tevent_req_data(req, struct ipa_server_trusted_dom_setup_state); + const char *hostname; + + state->keytab = forest_keytab(state, state->forest); + if (state->keytab == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set up ipa_get_keytab\n"); + return EIO; + } + + state->new_keytab = talloc_asprintf(state, "%sXXXXXX", state->keytab); + if (state->new_keytab == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot set up ipa_get_keytab. talloc_asprintf() failed\n"); + return ENOMEM; + } + + ret = sss_unique_filename(state, state->new_keytab); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create temporary keytab name\n"); + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Will re-fetch keytab for %s\n", state->subdom->name); + + hostname = dp_opt_get_string(state->id_ctx->ipa_options->basic, + IPA_HOSTNAME); + + state->principal = subdomain_trust_princ(state, + state->forest_realm, + state->subdom); + if (state->principal == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set up ipa_get_keytab\n"); + return EIO; + } + + subreq = ipa_getkeytab_send(state->be_ctx, state->be_ctx->ev, + state->ccache, + hostname, + state->principal, + state->new_keytab); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_server_trust_1way_kt_done, req); + return EAGAIN; +} + +static void ipa_server_trust_1way_kt_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_server_trusted_dom_setup_state *state = + tevent_req_data(req, struct ipa_server_trusted_dom_setup_state); + + ret = ipa_getkeytab_recv(subreq, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + /* Do not fail here, but try to check and use the previous keytab, + * if any */ + DEBUG(SSSDBG_MINOR_FAILURE, "ipa_getkeytab_recv failed: %d\n", ret); + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Keytab successfully retrieved to %s\n", state->new_keytab); + } + + ret = ipa_check_keytab(state->new_keytab, + state->id_ctx->server_mode->kt_owner_uid, + state->id_ctx->server_mode->kt_owner_gid); + if (ret == EOK) { + ret = rename(state->new_keytab, state->keytab); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "rename failed [%d][%s].\n", ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Keytab renamed to %s\n", state->keytab); + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Trying to recover and use the previous keytab, if available\n"); + ret = ipa_check_keytab(state->keytab, + state->id_ctx->server_mode->kt_owner_uid, + state->id_ctx->server_mode->kt_owner_gid); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "The previous keytab %s contains the expected principal\n", + state->keytab); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot use the old keytab: %d\n", ret); + /* Nothing we can do now */ + tevent_req_error(req, ret); + return; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Keytab %s contains the expected principals\n", state->new_keytab); + + DEBUG(SSSDBG_TRACE_FUNC, + "Established trust context for %s\n", state->subdom->name); + tevent_req_done(req); +} + +errno_t ipa_server_trusted_dom_setup_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ipa_server_create_trusts_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_id_ctx *id_ctx; + struct sss_domain_info *domiter; +}; + +static errno_t ipa_server_create_trusts_step(struct tevent_req *req); +static errno_t ipa_server_create_trusts_ctx(struct tevent_req *req); +static void ipa_server_create_trusts_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_server_create_trusts_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *parent) +{ + struct tevent_req *req = NULL; + struct ipa_server_create_trusts_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_server_create_trusts_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->id_ctx = id_ctx; + state->domiter = parent; + + ret = ipa_server_create_trusts_step(req); + if (ret != EAGAIN) { + goto immediate; + } + + return req; + +immediate: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_server_create_trusts_step(struct tevent_req *req) +{ + struct tevent_req *subreq = NULL; + struct ipa_ad_server_ctx *trust_iter; + struct ipa_ad_server_ctx *trust_i; + struct ipa_server_create_trusts_state *state = NULL; + + state = tevent_req_data(req, struct ipa_server_create_trusts_state); + + for (state->domiter = get_next_domain(state->domiter, SSS_GND_DESCEND); + state->domiter && IS_SUBDOMAIN(state->domiter); + state->domiter = get_next_domain(state->domiter, 0)) { + + /* Check if we already have an ID context for this subdomain */ + DLIST_FOR_EACH(trust_iter, state->id_ctx->server_mode->trusts) { + if (trust_iter->dom == state->domiter) { + break; + } + } + + /* Newly detected trust */ + if (trust_iter == NULL) { + subreq = ipa_server_trusted_dom_setup_send(state, + state->ev, + state->be_ctx, + state->id_ctx, + state->domiter); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_server_create_trusts_done, req); + return EAGAIN; + } + } + + /* Refresh all sdap_dom lists in all ipa_ad_server_ctx contexts */ + DLIST_FOR_EACH(trust_iter, state->id_ctx->server_mode->trusts) { + struct sdap_domain *sdom_a; + + sdom_a = sdap_domain_get(trust_iter->ad_id_ctx->sdap_id_ctx->opts, + trust_iter->dom); + if (sdom_a == NULL) { + continue; + } + + DLIST_FOR_EACH(trust_i, state->id_ctx->server_mode->trusts) { + struct sdap_domain *sdom_b; + + if (strcmp(trust_iter->dom->name, trust_i->dom->name) == 0) { + continue; + } + + sdom_b = sdap_domain_get(trust_i->ad_id_ctx->sdap_id_ctx->opts, + sdom_a->dom); + if (sdom_b == NULL) { + continue; + } + + /* Replace basedn and search bases from sdom_b with values + * from sdom_a */ + sdap_domain_copy_search_bases(sdom_b, sdom_a); + } + } + + return EOK; +} + +static void ipa_server_create_trusts_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = ipa_server_trusted_dom_setup_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = ipa_server_create_trusts_ctx(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = ipa_server_create_trusts_step(req); + if (ret == EOK) { + tevent_req_done(req); + return; + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + + /* Will cycle back */ +} + +static errno_t ipa_server_create_trusts_ctx(struct tevent_req *req) +{ + struct ipa_ad_server_ctx *trust_ctx; + struct ad_id_ctx *ad_id_ctx; + errno_t ret; + struct ipa_server_create_trusts_state *state = NULL; + + state = tevent_req_data(req, struct ipa_server_create_trusts_state); + + ret = ipa_ad_ctx_new(state->be_ctx, state->id_ctx, state->domiter, &ad_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot create ad_id_ctx for subdomain %s\n", state->domiter->name); + return ret; + } + + trust_ctx = talloc(state->id_ctx->server_mode, struct ipa_ad_server_ctx); + if (trust_ctx == NULL) { + return ENOMEM; + } + trust_ctx->dom = state->domiter; + trust_ctx->ad_id_ctx = ad_id_ctx; + + DLIST_ADD(state->id_ctx->server_mode->trusts, trust_ctx); + return EOK; +} + +errno_t ipa_server_create_trusts_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +void ipa_ad_subdom_remove(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom) +{ + struct ipa_ad_server_ctx *iter; + struct sdap_domain *sdom; + + if (dp_opt_get_bool(id_ctx->ipa_options->basic, + IPA_SERVER_MODE) == false) { + return; + } + + DLIST_FOR_EACH(iter, id_ctx->server_mode->trusts) { + if (iter->dom == subdom) break; + } + + if (iter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No IPA-AD context for subdomain %s\n", + subdom->name); + return; + } + + sdom = sdap_domain_get(iter->ad_id_ctx->sdap_id_ctx->opts, subdom); + if (sdom == NULL) return; + + sdap_domain_remove(iter->ad_id_ctx->sdap_id_ctx->opts, subdom); + DLIST_REMOVE(id_ctx->server_mode->trusts, iter); + + /* terminate all requests for this subdomain so we can free it */ + dp_terminate_domain_requests(be_ctx->provider, subdom->name); + talloc_zfree(sdom); +} + +struct ipa_ad_subdom_reinit_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_id_ctx *id_ctx; + struct sss_domain_info *parent; +}; + +static void create_trusts_at_startup_done(struct tevent_req *req) +{ + errno_t ret; + + ret = ipa_server_create_trusts_recv(req); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ipa_server_create_trusts_send request failed [%d]: %s\n", + ret, sss_strerror(ret)); + } +} + +static void create_trusts_at_startup(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct tevent_req *req; + struct ipa_ad_subdom_reinit_state *state; + + state = talloc_get_type(pvt, struct ipa_ad_subdom_reinit_state); + + req = ipa_server_create_trusts_send(state, state->ev, state->be_ctx, + state->id_ctx, state->parent); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_server_create_trusts_send failed.\n"); + talloc_free(state); + return; + } + + tevent_req_set_callback(req, create_trusts_at_startup_done, state); + return; +} + +static errno_t ipa_ad_subdom_reinit(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *parent) +{ + struct tevent_immediate *imm; + struct ipa_ad_subdom_reinit_state *state; + + state = talloc(mem_ctx, struct ipa_ad_subdom_reinit_state); + if (state == NULL) { + return ENOMEM; + } + state->ev = ev; + state->be_ctx = be_ctx; + state->id_ctx = id_ctx; + state->parent = parent; + + if (dp_opt_get_bool(id_ctx->ipa_options->basic, + IPA_SERVER_MODE) == false) { + return EOK; + } + + imm = tevent_create_immediate(mem_ctx); + if (imm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_create_immediate failed.\n"); + talloc_free(state); + return ENOMEM; + } + + tevent_schedule_immediate(imm, ev, create_trusts_at_startup, state); + return EOK; +} + +int ipa_ad_subdom_init(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx) +{ + char *realm; + char *hostname; + errno_t ret; + + if (dp_opt_get_bool(id_ctx->ipa_options->basic, + IPA_SERVER_MODE) == false) { + return EOK; + } + + /* The IPA code relies on the default FQDN format to unparse user + * names. Warn loudly if the full_name_format was customized on the + * IPA server + */ + if ((strcmp(be_ctx->domain->names->fq_fmt, + CONFDB_DEFAULT_FULL_NAME_FORMAT) != 0) + && (strcmp(be_ctx->domain->names->fq_fmt, + CONFDB_DEFAULT_FULL_NAME_FORMAT_INTERNAL) != 0)) { + DEBUG(SSSDBG_FATAL_FAILURE, "%s is set to a non-default value [%s] " \ + "lookups of subdomain users will likely fail!\n", + CONFDB_FULL_NAME_FORMAT, be_ctx->domain->names->fq_fmt); + sss_log(SSS_LOG_ERR, "%s is set to a non-default value [%s] " \ + "lookups of subdomain users will likely fail!\n", + CONFDB_FULL_NAME_FORMAT, be_ctx->domain->names->fq_fmt); + /* Attempt to continue */ + } + + realm = dp_opt_get_string(id_ctx->ipa_options->basic, IPA_KRB5_REALM); + if (realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm for IPA?\n"); + return EINVAL; + } + + hostname = dp_opt_get_string(id_ctx->ipa_options->basic, IPA_HOSTNAME); + if (hostname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No host name for IPA?\n"); + return EINVAL; + } + + id_ctx->server_mode = talloc_zero(id_ctx, struct ipa_server_mode_ctx); + if (id_ctx->server_mode == NULL) { + return ENOMEM; + } + id_ctx->server_mode->realm = realm; + id_ctx->server_mode->hostname = hostname; + id_ctx->server_mode->trusts = NULL; + id_ctx->server_mode->ext_groups = NULL; + id_ctx->server_mode->kt_owner_uid = 0; + id_ctx->server_mode->kt_owner_gid = 0; + + if (getuid() == 0) { + /* We need to handle keytabs created by IPA oddjob script gracefully + * even if we're running as root and IPA creates them as the SSSD user + */ + ret = sss_user_by_name_or_uid(SSSD_USER, + &id_ctx->server_mode->kt_owner_uid, + &id_ctx->server_mode->kt_owner_gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get ID of %s\n", SSSD_USER); + } + } + + ret = ipa_ad_subdom_reinit(be_ctx, be_ctx->ev, + be_ctx, id_ctx, be_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_ad_subdom_refresh failed.\n"); + return ret; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_subdomains_utils.c b/src/providers/ipa/ipa_subdomains_utils.c new file mode 100644 index 0000000..27fc0a4 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_utils.c @@ -0,0 +1,100 @@ +/* + SSSD + + IPA Subdomains Module - utilities + + Authors: + Sumit Bose + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_id.h" + +struct ldb_dn *ipa_subdom_ldb_dn(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + struct sysdb_attrs *attrs) +{ + int ret; + const char *orig_dn; + struct ldb_dn *dn = NULL; + + if (attrs == NULL || ldb_ctx == NULL) { + return NULL; + } + + ret = sysdb_attrs_get_string(attrs, SYSDB_ORIG_DN, &orig_dn); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed: %d\n", ret); + return NULL; + } + + dn = ldb_dn_new(mem_ctx, ldb_ctx, orig_dn); + if (dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_new failed.\n"); + return NULL; + } + + if (!ldb_dn_validate(dn)) { + DEBUG(SSSDBG_OP_FAILURE, "Original DN [%s] is not a valid DN.\n", + orig_dn); + talloc_free(dn); + return NULL; + } + + return dn; +} + +bool ipa_subdom_is_member_dom(struct ldb_dn *dn) +{ + const struct ldb_val *val; + + if (dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Wrong input!\n"); + return false; + } + + if (ldb_dn_get_comp_num(dn) < 5) { + /* We are only interested in the member domain objects. In IPA the + * forest root object is stored as e.g. + * cn=AD.DOM,cn=ad,cn=trusts,dc=example,dc=com. Member domains in the + * forest are children of the forest root object e.g. + * cn=SUB.AD.DOM,cn=AD.DOM,cn=ad,cn=trusts,dc=example,dc=com. Since + * the forest name is not stored in the member objects we derive it + * from the RDN of the forest root object. */ + DEBUG(SSSDBG_TRACE_FUNC, + "DN too short, not a member domain\n"); + return false; + } + + val = ldb_dn_get_component_val(dn, 3); + if (strncasecmp("trusts", (const char *) val->data, val->length) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "4th component is not 'trust', not a member domain\n"); + return false; + } + + val = ldb_dn_get_component_val(dn, 2); + if (strncasecmp("ad", (const char *) val->data, val->length) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "3rd component is not 'ad', not a member domain\n"); + return false; + } + + return true; +} diff --git a/src/providers/ipa/ipa_sudo.c b/src/providers/ipa/ipa_sudo.c new file mode 100644 index 0000000..32ff1ce --- /dev/null +++ b/src/providers/ipa/ipa_sudo.c @@ -0,0 +1,337 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ipa/ipa_opts.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ldap/sdap_sudo.h" +#include "providers/ldap/ldap_opts.h" +#include "providers/ipa/ipa_sudo.h" +#include "db/sysdb_sudo.h" + +struct ipa_sudo_handler_state { + uint32_t type; + struct dp_reply_std reply; + struct ipa_sudo_ctx *sudo_ctx; +}; + +static void ipa_sudo_handler_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_sudo_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_sudo_ctx *sudo_ctx, + struct dp_sudo_data *data, + struct dp_req_params *params) +{ + struct ipa_sudo_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ipa_sudo_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->type = data->type; + state->sudo_ctx = sudo_ctx; + + switch (data->type) { + case BE_REQ_SUDO_FULL: + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a full refresh of sudo rules\n"); + subreq = ipa_sudo_full_refresh_send(state, params->ev, sudo_ctx); + break; + case BE_REQ_SUDO_RULES: + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a refresh of specific sudo rules\n"); + subreq = ipa_sudo_rules_refresh_send(state, params->ev, sudo_ctx, + data->rules); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request type: %d\n", data->type); + ret = EINVAL; + goto immediately; + } + + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request: %d\n", data->type); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_sudo_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_sudo_handler_done(struct tevent_req *subreq) +{ + struct ipa_sudo_handler_state *state; + struct tevent_req *req; + int dp_error; + bool deleted; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_handler_state); + + switch (state->type) { + case BE_REQ_SUDO_FULL: + ret = ipa_sudo_full_refresh_recv(subreq, &dp_error); + talloc_zfree(subreq); + + /* Postpone the periodic task since the refresh was just finished + * per user request. */ + if (ret == EOK && dp_error == DP_ERR_OK) { + be_ptask_postpone(state->sudo_ctx->full_refresh); + } + break; + case BE_REQ_SUDO_RULES: + ret = ipa_sudo_rules_refresh_recv(subreq, &dp_error, &deleted); + talloc_zfree(subreq); + if (ret == EOK && deleted == true) { + ret = ENOENT; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request type: %d\n", state->type); + dp_error = DP_ERR_FATAL; + ret = ERR_INTERNAL; + break; + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +static errno_t +ipa_sudo_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct ipa_sudo_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_sudo_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} + +enum sudo_schema { + SUDO_SCHEMA_IPA, + SUDO_SCHEMA_LDAP +}; + +static errno_t +ipa_sudo_choose_schema(struct dp_option *ipa_opts, + struct dp_option *sdap_opts, + enum sudo_schema *_schema) +{ + TALLOC_CTX *tmp_ctx; + char *ipa_search_base; + char *search_base; + char *basedn; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = domain_to_basedn(tmp_ctx, dp_opt_get_string(ipa_opts, + IPA_KRB5_REALM), &basedn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to obtain basedn\n"); + goto done; + } + + ipa_search_base = talloc_asprintf(tmp_ctx, "cn=sudo,%s", basedn); + if (ipa_search_base == NULL) { + ret = ENOMEM; + goto done; + } + + search_base = dp_opt_get_string(sdap_opts, SDAP_SUDO_SEARCH_BASE); + if (search_base == NULL) { + ret = dp_opt_set_string(sdap_opts, SDAP_SUDO_SEARCH_BASE, + ipa_search_base); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + sdap_opts[SDAP_SUDO_SEARCH_BASE].opt_name, ipa_search_base); + + search_base = ipa_search_base; + } + + /* Use IPA schema only if search base is cn=sudo,$dc. */ + if (strcmp(ipa_search_base, search_base) == 0) { + *_schema = SUDO_SCHEMA_IPA; + } else { + *_schema = SUDO_SCHEMA_LDAP; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int +ipa_sudo_init_ipa_schema(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + struct ipa_sudo_ctx *sudo_ctx; + errno_t ret; + + sudo_ctx = talloc_zero(be_ctx, struct ipa_sudo_ctx); + if (sudo_ctx == NULL) { + return ENOMEM; + } + + sudo_ctx->id_ctx = id_ctx->sdap_id_ctx; + sudo_ctx->ipa_opts = id_ctx->ipa_options; + sudo_ctx->sdap_opts = id_ctx->sdap_id_ctx->opts; + + ret = sdap_get_map(sudo_ctx, be_ctx->cdb, be_ctx->conf_path, + ipa_sudorule_map, IPA_OPTS_SUDORULE, + &sudo_ctx->sudorule_map); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse attribute map (rule) " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sdap_get_map(sudo_ctx, be_ctx->cdb, be_ctx->conf_path, + ipa_sudocmdgroup_map, IPA_OPTS_SUDOCMDGROUP, + &sudo_ctx->sudocmdgroup_map); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse attribute map (cmdgroup) " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sdap_get_map(sudo_ctx, be_ctx->cdb, be_ctx->conf_path, + ipa_sudocmd_map, IPA_OPTS_SUDOCMD, + &sudo_ctx->sudocmd_map); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse attribute map (cmd) " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_int(be_ctx->cdb, CONFDB_SUDO_CONF_ENTRY, + CONFDB_SUDO_THRESHOLD, CONFDB_DEFAULT_SUDO_THRESHOLD, + &sudo_ctx->sudocmd_threshold); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not get sudo threshold\n"); + goto done; + } + + ret = sdap_parse_search_base(sudo_ctx, + sysdb_ctx_get_ldb(be_ctx->domain->sysdb), + sudo_ctx->sdap_opts->basic, + SDAP_SUDO_SEARCH_BASE, + &sudo_ctx->sudo_sb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not parse sudo search base\n"); + goto done; + } + + ret = ipa_sudo_ptask_setup(be_ctx, sudo_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup periodic tasks " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + dp_set_method(dp_methods, DPM_SUDO_HANDLER, + ipa_sudo_handler_send, ipa_sudo_handler_recv, sudo_ctx, + struct ipa_sudo_ctx, struct dp_sudo_data, struct dp_reply_std); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(sudo_ctx); + } + + return ret; +} + +int ipa_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + enum sudo_schema schema; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing IPA sudo back end\n"); + + ret = ipa_sudo_choose_schema(id_ctx->ipa_options->basic, + id_ctx->ipa_options->id->basic, + &schema); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to choose sudo schema [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + switch (schema) { + case SUDO_SCHEMA_IPA: + DEBUG(SSSDBG_TRACE_FUNC, "Using IPA schema for sudo\n"); + ret = ipa_sudo_init_ipa_schema(mem_ctx, be_ctx, id_ctx, dp_methods); + break; + case SUDO_SCHEMA_LDAP: + DEBUG(SSSDBG_TRACE_FUNC, "Using LDAP schema for sudo\n"); + ret = sdap_sudo_init(mem_ctx, + be_ctx, + id_ctx->sdap_id_ctx, + native_sudorule_map, + dp_methods); + break; + } + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize sudo provider" + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_sudo.h b/src/providers/ipa/ipa_sudo.h new file mode 100644 index 0000000..026fc29 --- /dev/null +++ b/src/providers/ipa/ipa_sudo.h @@ -0,0 +1,134 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _IPA_SUDO_H_ +#define _IPA_SUDO_H_ + +#include "providers/ipa/ipa_common.h" + +struct ipa_sudo_ctx { + struct sdap_id_ctx *id_ctx; + struct ipa_options *ipa_opts; + struct sdap_options *sdap_opts; + struct be_ptask *full_refresh; + struct be_ptask *smart_refresh; + + /* sudo */ + struct sdap_attr_map *sudocmdgroup_map; + struct sdap_attr_map *sudorule_map; + struct sdap_attr_map *sudocmd_map; + struct sdap_search_base **sudo_sb; + int sudocmd_threshold; +}; + +errno_t +ipa_sudo_ptask_setup(struct be_ctx *be_ctx, struct ipa_sudo_ctx *sudo_ctx); + +struct tevent_req * +ipa_sudo_full_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx); + +int +ipa_sudo_full_refresh_recv(struct tevent_req *req, + int *dp_error); + +int +ipa_sudo_rules_refresh_recv(struct tevent_req *req, + int *dp_error, + bool *deleted); + +struct tevent_req * +ipa_sudo_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx, + const char *cmdgroups_filter, + const char *search_filter, + const char *delete_filter, + bool update_usn); + +struct tevent_req * +ipa_sudo_rules_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx, + const char **rules); + +errno_t +ipa_sudo_refresh_recv(struct tevent_req *req, + int *dp_error, + size_t *_num_rules); + +struct ipa_sudo_conv; + +struct ipa_sudo_conv * +ipa_sudo_conv_init(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct sdap_attr_map *map_rule, + struct sdap_attr_map *map_cmdgroup, + struct sdap_attr_map *map_cmd, + struct sdap_attr_map *map_user, + struct sdap_attr_map *map_group, + struct sdap_attr_map *map_host, + struct sdap_attr_map *map_hostgroup); + +errno_t +ipa_sudo_conv_rules(struct ipa_sudo_conv *conv, + struct sysdb_attrs **rules, + size_t num_rules); + +errno_t +ipa_sudo_conv_cmdgroups(struct ipa_sudo_conv *conv, + struct sysdb_attrs **cmdgroups, + size_t num_cmdgroups); + +errno_t +ipa_sudo_conv_cmds(struct ipa_sudo_conv *conv, + struct sysdb_attrs **cmds, + size_t num_cmds); + +bool +ipa_sudo_conv_has_cmdgroups(struct ipa_sudo_conv *conv); + +bool +ipa_sudo_conv_has_cmds(struct ipa_sudo_conv *conv); + +bool +ipa_sudo_cmdgroups_exceed_threshold(struct ipa_sudo_conv *conv, int threshold); + +bool +ipa_sudo_cmds_exceed_threshold(struct ipa_sudo_conv *conv, int threshold); + +char * +ipa_sudo_conv_cmdgroup_filter(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + int cmd_threshold); + +char * +ipa_sudo_conv_cmd_filter(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + int cmd_threshold); + +errno_t +ipa_sudo_conv_result(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + struct sysdb_attrs ***_rules, + size_t *_num_rules); + +#endif /* _IPA_SUDO_H_ */ diff --git a/src/providers/ipa/ipa_sudo_async.c b/src/providers/ipa/ipa_sudo_async.c new file mode 100644 index 0000000..c531ecb --- /dev/null +++ b/src/providers/ipa/ipa_sudo_async.c @@ -0,0 +1,1141 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "providers/ldap/sdap_ops.h" +#include "providers/ldap/sdap_sudo_shared.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_hosts.h" +#include "providers/ipa/ipa_sudo.h" +#include "providers/ipa/ipa_dn.h" +#include "db/sysdb.h" +#include "db/sysdb_sudo.h" + +struct ipa_hostinfo { + size_t num_hosts; + size_t num_hostgroups; + struct sysdb_attrs **hosts; + struct sysdb_attrs **hostgroups; +}; + +static char * +ipa_sudo_filter_append_origdn(char *filter, + struct sysdb_attrs *attrs, + const char *attr_name) +{ + const char *origdn; + char *sanitizeddn; + errno_t ret; + + ret = sysdb_attrs_get_string(attrs, SYSDB_ORIG_DN, &origdn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get original DN " + "[%d]: %s\n", ret, sss_strerror(ret)); + return NULL; + } + + ret = sss_filter_sanitize(NULL, origdn, &sanitizeddn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to sanitize DN " + "[%d]: %s\n", ret, sss_strerror(ret)); + return NULL; + } + + filter = talloc_asprintf_append(filter, "(%s=%s)", attr_name, sanitizeddn); + talloc_free(sanitizeddn); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append() failed\n"); + } + + return filter; +} + +/** + * (|(hostCategory=ALL)(memberHost=$DN(fqdn))(memberHost=$DN(hostgroup))...) + */ +static char * +ipa_sudo_host_filter(TALLOC_CTX *mem_ctx, + struct ipa_hostinfo *host, + struct sdap_attr_map *map) +{ + TALLOC_CTX *tmp_ctx; + char *filter; + size_t i; + + /* If realloc fails we will free all data through tmp_ctx. */ + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + filter = talloc_asprintf(tmp_ctx, "(&(!(%s=*))(%s=defaults))", + map[IPA_AT_SUDORULE_HOST].name, + map[IPA_AT_SUDORULE_NAME].name); + if (filter == NULL) { + goto fail; + } + + /* Append hostCategory=ALL */ + filter = talloc_asprintf_append(filter, "(%s=ALL)", + map[IPA_AT_SUDORULE_HOSTCATEGORY].name); + if (filter == NULL) { + goto fail; + } + + /* Append client machine */ + for (i = 0; i < host->num_hosts; i++) { + filter = ipa_sudo_filter_append_origdn(filter, host->hosts[i], + map[IPA_AT_SUDORULE_HOST].name); + if (filter == NULL) { + goto fail; + } + } + + /* Append hostgroups */ + for (i = 0; i < host->num_hostgroups; i++) { + filter = ipa_sudo_filter_append_origdn(filter, host->hostgroups[i], + map[IPA_AT_SUDORULE_HOST].name); + if (filter == NULL) { + goto fail; + } + } + + /* OR filters */ + filter = talloc_asprintf(tmp_ctx, "(|%s)", filter); + if (filter == NULL) { + goto fail; + } + + talloc_steal(mem_ctx, filter); + talloc_free(tmp_ctx); + return filter; + +fail: + talloc_free(tmp_ctx); + return NULL; +} + +static errno_t +ipa_sudo_highest_usn(TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attrs, + size_t num_attrs, + char **current_usn) +{ + errno_t ret; + char *usn; + + ret = sysdb_get_highest_usn(mem_ctx, attrs, num_attrs, &usn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to get highest USN [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (sysdb_compare_usn(usn, *current_usn) > 0) { + talloc_free(*current_usn); + *current_usn = usn; + return EOK; + } + + talloc_free(usn); + return EOK; +} + +static errno_t +ipa_sudo_assoc_rules_filter(TALLOC_CTX *mem_ctx, + struct sysdb_attrs **cmdgroups, + size_t num_cmdgroups, + char **_filter) +{ + TALLOC_CTX *tmp_ctx; + const char *origdn; + char *sanitized; + char *filter; + errno_t ret; + size_t i; + + if (num_cmdgroups == 0) { + return ENOENT; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = talloc_strdup(tmp_ctx, ""); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_cmdgroups; i++) { + ret = sysdb_attrs_get_string(cmdgroups[i], SYSDB_ORIG_DN, &origdn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get original dn [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_INTERNAL; + goto done; + } + + ret = sss_filter_sanitize(tmp_ctx, origdn, &sanitized); + if (ret != EOK) { + goto done; + } + + filter = talloc_asprintf_append(filter, "(%s=%s)", + SYSDB_IPA_SUDORULE_ORIGCMD, sanitized); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + } + + filter = talloc_asprintf(tmp_ctx, "(&(objectClass=%s)(|%s)))", + SYSDB_SUDO_CACHE_OC, filter); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + *_filter = talloc_steal(mem_ctx, filter); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_sudo_assoc_rules(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sysdb_attrs **cmdgroups, + size_t num_cmdgroups, + struct sysdb_attrs ***_rules, + size_t *_num_rules) +{ + TALLOC_CTX *tmp_ctx; + const char *attrs[] = {SYSDB_NAME, NULL}; + struct sysdb_attrs **rules; + struct ldb_message **msgs; + size_t num_rules; + char *filter; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = ipa_sudo_assoc_rules_filter(tmp_ctx, cmdgroups, + num_cmdgroups, &filter); + if (ret != EOK) { + goto done; + } + + ret = sysdb_search_custom(tmp_ctx, domain, filter, + SUDORULE_SUBDIR, attrs, + &num_rules, &msgs); + if (ret == ENOENT) { + *_rules = NULL; + *_num_rules = 0; + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error looking up sudo rules [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_msg2attrs(tmp_ctx, num_rules, msgs, &rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not convert ldb message to " + "sysdb_attrs [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + *_rules = talloc_steal(mem_ctx, rules); + *_num_rules = num_rules; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_sudo_filter_rules_bycmdgroups(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sysdb_attrs **cmdgroups, + size_t num_cmdgroups, + struct sdap_attr_map *map_rule, + char **_filter) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs **rules; + size_t num_rules; + const char *name; + char *sanitized; + char *filter; + errno_t ret; + size_t i; + + if (num_cmdgroups == 0) { + *_filter = NULL; + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = ipa_sudo_assoc_rules(tmp_ctx, domain, cmdgroups, num_cmdgroups, + &rules, &num_rules); + if (ret != EOK) { + goto done; + } + + if (num_rules == 0) { + *_filter = NULL; + ret = EOK; + goto done; + } + + filter = talloc_strdup(tmp_ctx, ""); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_rules; i++) { + ret = sysdb_attrs_get_string(rules[i], SYSDB_NAME, &name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get name [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_filter_sanitize(tmp_ctx, name, &sanitized); + if (ret != EOK) { + goto done; + } + + filter = talloc_asprintf_append(filter, "(%s=%s)", + map_rule[IPA_AT_SUDORULE_NAME].name, sanitized); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + } + + filter = talloc_asprintf(tmp_ctx, "(|%s)", filter); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + *_filter = talloc_steal(mem_ctx, filter); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +struct ipa_sudo_fetch_state { + struct tevent_context *ev; + struct sss_domain_info *domain; + struct ipa_sudo_ctx *sudo_ctx; + struct sdap_options *sdap_opts; + struct ipa_hostinfo *host; + struct sdap_handle *sh; + const char *search_filter; + const char *cmdgroups_filter; + + struct sdap_attr_map *map_cmdgroup; + struct sdap_attr_map *map_rule; + struct sdap_attr_map *map_cmd; + struct sdap_search_base **sudo_sb; + + struct ipa_sudo_conv *conv; + struct sysdb_attrs **rules; + size_t num_rules; + int cmd_threshold; + char *usn; +}; + +static errno_t ipa_sudo_fetch_addtl_cmdgroups(struct tevent_req *req); +static void ipa_sudo_fetch_addtl_cmdgroups_done(struct tevent_req *subreq); +static errno_t ipa_sudo_fetch_rules(struct tevent_req *req); +static void ipa_sudo_fetch_rules_done(struct tevent_req *subreq); +static errno_t ipa_sudo_fetch_cmdgroups(struct tevent_req *req); +static void ipa_sudo_fetch_cmdgroups_done(struct tevent_req *subreq); +static errno_t ipa_sudo_fetch_cmds(struct tevent_req *req); +static void ipa_sudo_fetch_cmds_done(struct tevent_req *subreq); +static void ipa_sudo_fetch_done(struct tevent_req *req); + +static struct tevent_req * +ipa_sudo_fetch_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *domain, + struct ipa_sudo_ctx *sudo_ctx, + struct ipa_hostinfo *host, + struct sdap_attr_map *map_user, + struct sdap_attr_map *map_group, + struct sdap_attr_map *map_host, + struct sdap_attr_map *map_hostgroup, + struct sdap_handle *sh, + const char *cmdgroups_filter, + const char *search_filter) +{ + struct ipa_sudo_fetch_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_sudo_fetch_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->domain = domain; + state->sudo_ctx = sudo_ctx; + state->sdap_opts = sudo_ctx->sdap_opts; + state->host = host; + state->sh = sh; + state->search_filter = search_filter == NULL ? "" : search_filter; + state->cmdgroups_filter = cmdgroups_filter; + + state->map_cmdgroup = sudo_ctx->sudocmdgroup_map; + state->map_rule = sudo_ctx->sudorule_map; + state->map_cmd = sudo_ctx->sudocmd_map; + state->sudo_sb = sudo_ctx->sudo_sb; + state->cmd_threshold = sudo_ctx->sudocmd_threshold; + + state->conv = ipa_sudo_conv_init(state, domain, state->map_rule, + state->map_cmdgroup, state->map_cmd, + map_user, map_group, map_host, + map_hostgroup); + if (state->conv == NULL) { + ret = ENOMEM; + goto immediately; + } + + if (state->cmdgroups_filter != NULL) { + /* We need to fetch additional cmdgroups that may not be revealed + * during normal search. Such as when using entryUSN filter in smart + * refresh, some command groups may have change but none rule was + * modified but we need to fetch associated rules anyway. */ + ret = ipa_sudo_fetch_addtl_cmdgroups(req); + } else { + ret = ipa_sudo_fetch_rules(req); + } + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, state->ev); + + return req; +} + +static errno_t +ipa_sudo_fetch_addtl_cmdgroups(struct tevent_req *req) +{ + struct ipa_sudo_fetch_state *state; + struct tevent_req *subreq; + struct sdap_attr_map *map; + char *filter; + + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch additional command groups\n"); + + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + map = state->map_cmdgroup; + + filter = talloc_asprintf(state, "(&(objectClass=%s)%s)", + map[IPA_OC_SUDOCMDGROUP].name, + state->cmdgroups_filter); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build filter\n"); + return ENOMEM; + } + + subreq = sdap_search_bases_send(state, state->ev, state->sdap_opts, + state->sh, state->sudo_sb, map, true, 0, + filter, NULL, NULL); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_sudo_fetch_addtl_cmdgroups_done, req); + return EAGAIN; +} + +static void +ipa_sudo_fetch_addtl_cmdgroups_done(struct tevent_req *subreq) +{ + struct ipa_sudo_fetch_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **attrs; + size_t num_attrs; + char *filter; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + ret = sdap_search_bases_recv(subreq, state, &num_attrs, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Received %zu additional command groups\n", + num_attrs); + + ret = ipa_sudo_filter_rules_bycmdgroups(state, state->domain, attrs, + num_attrs, state->map_rule, + &filter); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to construct rules filter " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + state->search_filter = sdap_or_filters(state, state->search_filter, filter); + if (state->search_filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ipa_sudo_fetch_rules(req); + +done: + if (ret == EOK) { + ipa_sudo_fetch_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t +ipa_sudo_fetch_rules(struct tevent_req *req) +{ + struct ipa_sudo_fetch_state *state; + struct tevent_req *subreq; + struct sdap_attr_map *map; + char *host_filter; + char *filter; + + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch sudo rules\n"); + + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + map = state->map_rule; + + host_filter = ipa_sudo_host_filter(state, state->host, map); + if (host_filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build host filter\n"); + return ENOMEM; + } + + filter = talloc_asprintf(state, "(&(objectClass=%s)(%s=TRUE)%s%s)", + map[IPA_OC_SUDORULE].name, + map[IPA_AT_SUDORULE_ENABLED].name, + host_filter, state->search_filter); + talloc_zfree(host_filter); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build filter\n"); + return ENOMEM; + } + + subreq = sdap_search_bases_send(state, state->ev, state->sdap_opts, + state->sh, state->sudo_sb, map, true, 0, + filter, NULL, NULL); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_sudo_fetch_rules_done, req); + return EAGAIN; +} + +static void +ipa_sudo_fetch_rules_done(struct tevent_req *subreq) +{ + struct ipa_sudo_fetch_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **attrs; + size_t num_attrs; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + ret = sdap_search_bases_recv(subreq, state, &num_attrs, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Received %zu sudo rules\n", num_attrs); + + ret = ipa_sudo_conv_rules(state->conv, attrs, num_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed when converting rules " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_sudo_highest_usn(state, attrs, num_attrs, &state->usn); + if (ret != EOK) { + goto done; + } + + ret = ipa_sudo_fetch_cmdgroups(req); + +done: + if (ret == EOK) { + ipa_sudo_fetch_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t +ipa_sudo_fetch_cmdgroups(struct tevent_req *req) +{ + struct ipa_sudo_fetch_state *state; + struct tevent_req *subreq; + char *filter; + + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch sudo command groups\n"); + + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + if (ipa_sudo_conv_has_cmdgroups(state->conv)) { + DEBUG(SSSDBG_TRACE_FUNC, "No command groups needs to be downloaded\n"); + return ipa_sudo_fetch_cmds(req); + } + + filter = ipa_sudo_conv_cmdgroup_filter(state, state->conv, + state->cmd_threshold); + + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build filter\n"); + return ENOMEM; + } + + subreq = sdap_search_bases_send(state, state->ev, state->sdap_opts, + state->sh, state->sudo_sb, + state->map_cmdgroup, true, 0, + filter, NULL, NULL); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_sudo_fetch_cmdgroups_done, req); + return EAGAIN; +} + +static void +ipa_sudo_fetch_cmdgroups_done(struct tevent_req *subreq) +{ + struct ipa_sudo_fetch_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **attrs; + size_t num_attrs; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + ret = sdap_search_bases_recv(subreq, state, &num_attrs, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Received %zu sudo command groups\n", + num_attrs); + + ret = ipa_sudo_conv_cmdgroups(state->conv, attrs, num_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed when converting command groups " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_sudo_highest_usn(state, attrs, num_attrs, &state->usn); + if (ret != EOK) { + goto done; + } + + ret = ipa_sudo_fetch_cmds(req); + +done: + if (ret == EOK) { + ipa_sudo_fetch_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t +ipa_sudo_fetch_cmds(struct tevent_req *req) +{ + struct ipa_sudo_fetch_state *state; + struct tevent_req *subreq; + char *filter; + + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch sudo commands\n"); + + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + if (ipa_sudo_conv_has_cmds(state->conv)) { + DEBUG(SSSDBG_TRACE_FUNC, "No commands needs to be downloaded\n"); + return EOK; + } + + filter = ipa_sudo_conv_cmd_filter(state, state->conv, state->cmd_threshold); + + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build filter\n"); + return ENOMEM; + } + + subreq = sdap_search_bases_send(state, state->ev, state->sdap_opts, + state->sh, state->sudo_sb, + state->map_cmd, true, 0, + filter, NULL, NULL); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_sudo_fetch_cmds_done, req); + return EAGAIN; +} + +static void +ipa_sudo_fetch_cmds_done(struct tevent_req *subreq) +{ + struct ipa_sudo_fetch_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **attrs; + size_t num_attrs; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + ret = sdap_search_bases_recv(subreq, state, &num_attrs, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Received %zu sudo commands\n", num_attrs); + + ret = ipa_sudo_conv_cmds(state->conv, attrs, num_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed when converting commands " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + +done: + if (ret == EOK) { + ipa_sudo_fetch_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static void +ipa_sudo_fetch_done(struct tevent_req *req) +{ + struct ipa_sudo_fetch_state *state = NULL; + errno_t ret; + + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + DEBUG(SSSDBG_TRACE_FUNC, "About to convert rules\n"); + + ret = ipa_sudo_conv_result(state, state->conv, + &state->rules, &state->num_rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to convert rules [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t +ipa_sudo_fetch_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs ***_rules, + size_t *_num_rules, + char **_usn) +{ + struct ipa_sudo_fetch_state *state = NULL; + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_rules = talloc_steal(mem_ctx, state->rules); + *_num_rules = state->num_rules; + *_usn = talloc_steal(mem_ctx, state->usn); + + return EOK; +} + + +struct ipa_sudo_refresh_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct ipa_sudo_ctx *sudo_ctx; + struct ipa_options *ipa_opts; + struct sdap_options *sdap_opts; + const char *cmdgroups_filter; + const char *search_filter; + const char *delete_filter; + bool update_usn; + + struct sdap_id_op *sdap_op; + struct sdap_handle *sh; + int dp_error; + + struct sysdb_attrs **rules; + size_t num_rules; +}; + +static errno_t ipa_sudo_refresh_retry(struct tevent_req *req); +static void ipa_sudo_refresh_connect_done(struct tevent_req *subreq); +static void ipa_sudo_refresh_host_done(struct tevent_req *subreq); +static void ipa_sudo_refresh_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_sudo_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx, + const char *cmdgroups_filter, + const char *search_filter, + const char *delete_filter, + bool update_usn) +{ + struct ipa_sudo_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ipa_sudo_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->sysdb = sudo_ctx->id_ctx->be->domain->sysdb; + state->domain = sudo_ctx->id_ctx->be->domain; + state->sudo_ctx = sudo_ctx; + state->ipa_opts = sudo_ctx->ipa_opts; + state->sdap_opts = sudo_ctx->sdap_opts; + state->dp_error = DP_ERR_FATAL; + state->update_usn = update_usn; + + state->sdap_op = sdap_id_op_create(state, + sudo_ctx->id_ctx->conn->conn_cache); + if (!state->sdap_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + state->cmdgroups_filter = talloc_strdup(state, cmdgroups_filter); + if (cmdgroups_filter != NULL && state->cmdgroups_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->search_filter = talloc_strdup(state, search_filter); + if (search_filter != NULL && state->search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->delete_filter = talloc_strdup(state, delete_filter); + if (delete_filter != NULL && state->delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = ipa_sudo_refresh_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, state->ev); + + return req; +} + +static errno_t +ipa_sudo_refresh_retry(struct tevent_req *req) +{ + struct ipa_sudo_refresh_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct ipa_sudo_refresh_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: " + "%d(%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, ipa_sudo_refresh_connect_done, req); + + return EAGAIN; +} + +static void +ipa_sudo_refresh_connect_done(struct tevent_req *subreq) +{ + struct ipa_sudo_refresh_state *state; + const char *hostname; + struct tevent_req *req; + int dp_error; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_refresh_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "SUDO LDAP connection failed " + "[%d]: %s\n", ret, strerror(ret)); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + state->sh = sdap_id_op_handle(state->sdap_op); + + DEBUG(SSSDBG_TRACE_FUNC, "SUDO LDAP connection successful\n"); + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch host information\n"); + + /* Obtain host information. */ + hostname = dp_opt_get_string(state->ipa_opts->basic, IPA_HOSTNAME); + + subreq = ipa_host_info_send(state, state->ev, + state->sh, state->sdap_opts, hostname, + state->ipa_opts->id->host_map, + state->ipa_opts->hostgroup_map, + state->ipa_opts->id->sdom->host_search_bases); + if (subreq == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_sudo_refresh_host_done, req); +} + +static void +ipa_sudo_refresh_host_done(struct tevent_req *subreq) +{ + struct ipa_sudo_refresh_state *state; + struct ipa_hostinfo *host; + struct tevent_req *req; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_refresh_state); + + host = talloc_zero(state, struct ipa_hostinfo); + if (host == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + ret = ipa_host_info_recv(subreq, host, &host->num_hosts, &host->hosts, + &host->num_hostgroups, &host->hostgroups); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve host information " + "[%d]: %s\n", ret, sss_strerror(ret)); + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; + } + + subreq = ipa_sudo_fetch_send(state, state->ev, state->domain, + state->sudo_ctx, host, + state->sdap_opts->user_map, + state->sdap_opts->group_map, + state->ipa_opts->id->host_map, + state->ipa_opts->hostgroup_map, state->sh, + state->cmdgroups_filter, state->search_filter); + if (subreq == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_sudo_refresh_done, req); +} + +static void +ipa_sudo_refresh_done(struct tevent_req *subreq) +{ + struct ipa_sudo_refresh_state *state; + struct tevent_req *req; + char *usn = NULL; + bool in_transaction = false; + errno_t sret; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_refresh_state); + + ret = ipa_sudo_fetch_recv(state, subreq, &state->rules, + &state->num_rules, &usn); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &state->dp_error); + if (state->dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ipa_sudo_refresh_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = sysdb_sudo_purge(state->domain, state->delete_filter, + state->rules, state->num_rules); + if (ret != EOK) { + goto done; + } + + ret = sysdb_sudo_store(state->domain, state->rules, state->num_rules); + if (ret != EOK) { + goto done; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + if (usn != NULL && state->update_usn) { + sdap_sudo_set_usn(state->sudo_ctx->id_ctx->srv_opts, usn); + } + + DEBUG(SSSDBG_TRACE_FUNC, "Sudo rules are successfully stored in cache\n"); + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(state->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +ipa_sudo_refresh_recv(struct tevent_req *req, + int *dp_error, + size_t *_num_rules) +{ + struct ipa_sudo_refresh_state *state = NULL; + state = tevent_req_data(req, struct ipa_sudo_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + if (_num_rules != NULL) { + *_num_rules = state->num_rules; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_sudo_conversion.c b/src/providers/ipa/ipa_sudo_conversion.c new file mode 100644 index 0000000..220d937 --- /dev/null +++ b/src/providers/ipa/ipa_sudo_conversion.c @@ -0,0 +1,1369 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "providers/ldap/sdap.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_dn.h" +#include "db/sysdb_sudo.h" +#include "db/sysdb.h" +#include "util/util.h" + +#define SUDO_DN_CMDGROUPS "sudocmdgroups" +#define SUDO_DN_CMDS "sudocmds" +#define SUDO_DN_CONTAINER "sudo" +#define SUDO_DN_CN "cn" + +#define MATCHDN(cat) SUDO_DN_CN, (cat), SUDO_DN_CN, SUDO_DN_CONTAINER +#define MATCHDN_CMDGROUPS MATCHDN(SUDO_DN_CMDGROUPS) +#define MATCHDN_CMDS MATCHDN(SUDO_DN_CMDS) + +#define MATCHRDN_CMDGROUPS(map) (map)[IPA_AT_SUDOCMDGROUP_NAME].name, MATCHDN_CMDGROUPS +#define MATCHRDN_CMDS(attr, map) (map)[attr].name, MATCHDN_CMDS + +#define MATCHRDN_USER(map) (map)[SDAP_AT_USER_NAME].name, "cn", "users", "cn", "accounts" +#define MATCHRDN_GROUP(map) (map)[SDAP_AT_GROUP_NAME].name, "cn", "groups", "cn", "accounts" +#define MATCHRDN_HOST(map) (map)[SDAP_AT_HOST_FQDN].name, "cn", "computers", "cn", "accounts" +#define MATCHRDN_HOSTGROUP(map) (map)[IPA_AT_HOSTGROUP_NAME].name, "cn", "hostgroups", "cn", "accounts" + +struct ipa_sudo_conv { + struct sss_domain_info *dom; + + struct sdap_attr_map *map_rule; + struct sdap_attr_map *map_cmdgroup; + struct sdap_attr_map *map_cmd; + struct sdap_attr_map *map_user; + struct sdap_attr_map *map_group; + struct sdap_attr_map *map_host; + struct sdap_attr_map *map_hostgroup; + + hash_table_t *rules; + hash_table_t *cmdgroups; + hash_table_t *cmds; +}; + +struct ipa_sudo_dn_list { + struct ipa_sudo_dn_list *prev, *next; + const char *dn; +}; + +struct ipa_sudo_rulemember { + struct ipa_sudo_dn_list *cmdgroups; + struct ipa_sudo_dn_list *cmds; +}; + +struct ipa_sudo_rule { + struct sysdb_attrs *attrs; + struct ipa_sudo_rulemember allow; + struct ipa_sudo_rulemember deny; +}; + +struct ipa_sudo_cmdgroup { + struct ipa_sudo_dn_list *cmds; + const char **expanded; +}; + +static size_t +ipa_sudo_dn_list_count(struct ipa_sudo_dn_list *list) +{ + struct ipa_sudo_dn_list *item; + size_t i; + + for (i = 0, item = list; item != NULL; item = item->next, i++) { + /* no op */ + } + + return i; +} + +static errno_t +ipa_sudo_conv_store(hash_table_t *table, + const char *key, + void *value) +{ + hash_key_t hkey; + hash_value_t hvalue; + int hret; + + if (table == NULL || key == NULL) { + return EINVAL; + } + + hkey.type = HASH_KEY_STRING; + hkey.str = discard_const(key); + + /* If value is NULL we don't want to override existing entry. */ + if (value == NULL && hash_has_key(table, &hkey)) { + return EEXIST; + } + + hvalue.type = HASH_VALUE_PTR; + hvalue.ptr = value; + + hret = hash_enter(table, &hkey, &hvalue); + if (hret != HASH_SUCCESS) { + return EIO; + } + + if (value != NULL) { + talloc_steal(table, value); + } + + return EOK; +} + +static void * +ipa_sudo_conv_lookup(hash_table_t *table, + const char *key) +{ + hash_key_t hkey; + hash_value_t hvalue; + int hret; + + hkey.type = HASH_KEY_STRING; + hkey.str = discard_const(key); + + hret = hash_lookup(table, &hkey, &hvalue); + if (hret == HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_OP_FAILURE, "Key not found %s\n", key); + return NULL; + } else if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup value [%d]\n", hret); + return NULL; + } + + return hvalue.ptr; +} + +static errno_t +store_rulemember(TALLOC_CTX *mem_ctx, + struct ipa_sudo_dn_list **list, + hash_table_t *table, + const char *dn) +{ + struct ipa_sudo_dn_list *item; + errno_t ret; + + item = talloc_zero(mem_ctx, struct ipa_sudo_dn_list); + if (item == NULL) { + return ENOMEM; + } + + ret = ipa_sudo_conv_store(table, dn, NULL); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to store DN %s [%d]: %s\n", + dn, ret, sss_strerror(ret)); + goto done; + } + + item->dn = talloc_steal(item, dn); + DLIST_ADD(*list, item); + +done: + if (ret != EOK && ret != EEXIST) { + talloc_free(item); + } + + return ret; +} + +static bool is_ipacmdgroup(struct ipa_sudo_conv *conv, const char *dn) +{ + if (ipa_check_rdn_bool(conv->dom->sysdb, dn, + MATCHRDN_CMDGROUPS(conv->map_cmdgroup))) { + return true; + } + + return false; +} + +static bool is_ipacmd(struct ipa_sudo_conv *conv, const char *dn) +{ + if (ipa_check_rdn_bool(conv->dom->sysdb, dn, + MATCHRDN_CMDS(IPA_AT_SUDOCMD_UUID, conv->map_cmd))) { + return true; + } + + /* For older versions of FreeIPA than 3.1. */ + if (ipa_check_rdn_bool(conv->dom->sysdb, dn, + MATCHRDN_CMDS(IPA_AT_SUDOCMD_CMD, conv->map_cmd))) { + return true; + } + + return false; +} + +static errno_t +process_rulemember(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + struct ipa_sudo_rulemember *rulemember, + struct sysdb_attrs *rule, + const char *attr) +{ + TALLOC_CTX *tmp_ctx; + const char **members; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_attrs_get_string_array(rule, attr, tmp_ctx, &members); + if (ret == ENOENT) { + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + + for (i = 0; members[i] != NULL; i++) { + if (is_ipacmdgroup(conv, members[i])) { + ret = store_rulemember(mem_ctx, &rulemember->cmdgroups, + conv->cmdgroups, members[i]); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Found sudo command group %s\n", + members[i]); + } else if (ret != EEXIST) { + goto done; + } + } else if (is_ipacmd(conv, members[i])) { + ret = store_rulemember(mem_ctx, &rulemember->cmds, + conv->cmds, members[i]); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Found sudo command %s\n", + members[i]); + } else if (ret != EEXIST) { + goto done; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Invalid member DN %s, skipping...\n", + members[i]); + continue; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +process_allowcmd(struct ipa_sudo_conv *conv, + struct ipa_sudo_rule *rule) +{ + return process_rulemember(rule, conv, &rule->allow, rule->attrs, + SYSDB_IPA_SUDORULE_ALLOWCMD); +} + +static errno_t +process_denycmd(struct ipa_sudo_conv *conv, + struct ipa_sudo_rule *rule) +{ + return process_rulemember(rule, conv, &rule->deny, rule->attrs, + SYSDB_IPA_SUDORULE_DENYCMD); +} + +static errno_t +process_cmdgroupmember(struct ipa_sudo_conv *conv, + struct ipa_sudo_cmdgroup *cmdgroup, + struct sysdb_attrs *attrs) +{ + TALLOC_CTX *tmp_ctx; + struct ipa_sudo_dn_list *item; + const char **members; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_attrs_get_string_array(attrs, SYSDB_MEMBER, tmp_ctx, &members); + if (ret == ENOENT) { + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + + for (i = 0; members[i] != NULL; i++) { + ret = ipa_sudo_conv_store(conv->cmds, members[i], NULL); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Found sudo command %s\n", + members[i]); + } else if (ret != EEXIST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to store DN [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + item = talloc_zero(tmp_ctx, struct ipa_sudo_dn_list); + if (item == NULL) { + ret = ENOMEM; + goto done; + } + + item->dn = talloc_steal(item, members[i]); + DLIST_ADD(cmdgroup->cmds, item); + talloc_steal(cmdgroup, item); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +struct ipa_sudo_conv * +ipa_sudo_conv_init(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct sdap_attr_map *map_rule, + struct sdap_attr_map *map_cmdgroup, + struct sdap_attr_map *map_cmd, + struct sdap_attr_map *map_user, + struct sdap_attr_map *map_group, + struct sdap_attr_map *map_host, + struct sdap_attr_map *map_hostgroup) +{ + struct ipa_sudo_conv *conv; + errno_t ret; + + conv = talloc_zero(mem_ctx, struct ipa_sudo_conv); + if (conv == NULL) { + return NULL; + } + + conv->dom = dom; + conv->map_rule = map_rule; + conv->map_cmdgroup = map_cmdgroup; + conv->map_cmd = map_cmd; + conv->map_user = map_user; + conv->map_group = map_group; + conv->map_host = map_host; + conv->map_hostgroup = map_hostgroup; + + ret = sss_hash_create(conv, 0, &conv->rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_hash_create(conv, 0, &conv->cmdgroups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_hash_create(conv, 0, &conv->cmds); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + if (ret != EOK) { + talloc_free(conv); + return NULL; + } + + return conv; +} + +errno_t +ipa_sudo_conv_rules(struct ipa_sudo_conv *conv, + struct sysdb_attrs **rules, + size_t num_rules) +{ + struct ipa_sudo_rule *rule = NULL; + const char *key; + errno_t ret; + size_t i; + + if (num_rules == 0) { + /* We're done here. */ + return EOK; + } + + for (i = 0; i < num_rules; i++) { + ret = sysdb_attrs_get_string(rules[i], SYSDB_NAME, &key); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get rule name, skipping " + "[%d]: %s\n", ret, sss_strerror(ret)); + continue; + } + + rule = talloc_zero(conv->rules, struct ipa_sudo_rule); + if (rule == NULL) { + ret = ENOMEM; + goto done; + } + + rule->attrs = rules[i]; + + ret = process_allowcmd(conv, rule); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to process memberAllowCmd " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + ret = process_denycmd(conv, rule); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to process memberDenyCmd " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + ret = ipa_sudo_conv_store(conv->rules, key, rule); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store rule into table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + talloc_steal(rule, rule->attrs); + rule = NULL; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(rule); + } + + return ret; +} + +errno_t +ipa_sudo_conv_cmdgroups(struct ipa_sudo_conv *conv, + struct sysdb_attrs **cmdgroups, + size_t num_cmdgroups) +{ + struct ipa_sudo_cmdgroup *cmdgroup = NULL; + const char *key; + errno_t ret; + size_t i; + + if (num_cmdgroups == 0) { + /* We're done here. */ + return EOK; + } + + for (i = 0; i < num_cmdgroups; i++) { + ret = sysdb_attrs_get_string(cmdgroups[i], SYSDB_ORIG_DN, &key); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get command group DN, " + "skipping [%d]: %s\n", ret, sss_strerror(ret)); + continue; + } + + cmdgroup = talloc_zero(conv->cmdgroups, struct ipa_sudo_cmdgroup); + if (cmdgroup == NULL) { + ret = ENOMEM; + goto done; + } + + ret = process_cmdgroupmember(conv, cmdgroup, cmdgroups[i]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to process member " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + ret = ipa_sudo_conv_store(conv->cmdgroups, key, cmdgroup); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store command group into " + "table [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + cmdgroup = NULL; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmdgroup); + } + + return ret; +} + +errno_t +ipa_sudo_conv_cmds(struct ipa_sudo_conv *conv, + struct sysdb_attrs **cmds, + size_t num_cmds) +{ + const char *key; + const char *cmd; + errno_t ret; + size_t i; + + if (num_cmds == 0) { + /* We're done here. */ + return EOK; + } + + for (i = 0; i < num_cmds; i++) { + ret = sysdb_attrs_get_string(cmds[i], SYSDB_ORIG_DN, &key); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get command DN, skipping " + "[%d]: %s\n", ret, sss_strerror(ret)); + continue; + } + + ret = sysdb_attrs_get_string(cmds[i], SYSDB_IPA_SUDOCMD_SUDOCMD, &cmd); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get command, skipping " + "[%d]: %s\n", ret, sss_strerror(ret)); + continue; + } + + ret = ipa_sudo_conv_store(conv->cmds, key, discard_const(cmd)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store command into table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + return ret; +} + +bool +ipa_sudo_conv_has_cmdgroups(struct ipa_sudo_conv *conv) +{ + return hash_count(conv->cmdgroups) == 0; +} + +bool +ipa_sudo_conv_has_cmds(struct ipa_sudo_conv *conv) +{ + return hash_count(conv->cmds) == 0; +} + +bool +ipa_sudo_cmdgroups_exceed_threshold(struct ipa_sudo_conv *conv, int threshold) +{ + return (hash_count(conv->cmdgroups)) > threshold; +} +bool +ipa_sudo_cmds_exceed_threshold(struct ipa_sudo_conv *conv, int threshold) +{ + return (hash_count(conv->cmds)) > threshold; +} + +typedef errno_t (*ipa_sudo_conv_rdn_fn)(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + struct sysdb_ctx *sysdb, + const char *dn, + char **_rdn_val, + const char **_rdn_attr); + +static errno_t get_sudo_cmdgroup_rdn(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + struct sysdb_ctx *sysdb, + const char *dn, + char **_rdn_val, + const char **_rdn_attr) +{ + char *rdn_val; + errno_t ret; + + ret = ipa_get_rdn(mem_ctx, sysdb, dn, &rdn_val, + MATCHRDN_CMDGROUPS(map)); + if (ret != EOK) { + return ret; + } + + *_rdn_val = rdn_val; + *_rdn_attr = map[IPA_AT_SUDOCMDGROUP_NAME].name; + + return EOK; +} + +static errno_t get_sudo_cmd_rdn(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + struct sysdb_ctx *sysdb, + const char *dn, + char **_rdn_val, + const char **_rdn_attr) +{ + char *rdn_val; + errno_t ret; + + ret = ipa_get_rdn(mem_ctx, sysdb, dn, &rdn_val, + MATCHRDN_CMDS(IPA_AT_SUDOCMD_UUID, map)); + if (ret == EOK) { + *_rdn_val = rdn_val; + *_rdn_attr = map[IPA_AT_SUDOCMD_UUID].name; + + return EOK; + } else if (ret != ENOENT) { + return ret; + } + + /* For older versions of FreeIPA than 3.1. */ + ret = ipa_get_rdn(mem_ctx, sysdb, dn, &rdn_val, + MATCHRDN_CMDS(IPA_AT_SUDOCMD_CMD, map)); + if (ret != EOK) { + return ret; + } + + *_rdn_val = rdn_val; + *_rdn_attr = map[IPA_AT_SUDOCMD_CMD].name; + + return EOK; +} + +static char * +build_filter(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + hash_table_t *table, + struct sdap_attr_map *map, + ipa_sudo_conv_rdn_fn rdn_fn) +{ + TALLOC_CTX *tmp_ctx; + hash_key_t *keys; + unsigned long int count; + unsigned long int i; + char *filter = NULL; + char *rdn_val; + const char *rdn_attr; + char *safe_rdn; + errno_t ret; + int hret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + hret = hash_keys(table, &count, &keys); + if (hret != HASH_SUCCESS) { + ret = ENOMEM; + goto done; + } + + talloc_steal(tmp_ctx, keys); + + filter = talloc_strdup(tmp_ctx, ""); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < count; i++) { + ret = rdn_fn(tmp_ctx, map, sysdb, keys[i].str, &rdn_val, &rdn_attr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get member %s [%d]: %s\n", + keys[i].str, ret, sss_strerror(ret)); + goto done; + } + + ret = sss_filter_sanitize(tmp_ctx, rdn_val, &safe_rdn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to sanitize DN " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + filter = talloc_asprintf_append(filter, "(%s=%s)", rdn_attr, safe_rdn); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* objectClass is always first */ + filter = talloc_asprintf(filter, "(&(objectClass=%s)(|%s))", + map[0].name, filter); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_steal(mem_ctx, filter); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + return NULL; + } + + return filter; +} + +char * +ipa_sudo_conv_cmdgroup_filter(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + int cmd_threshold) +{ + if (ipa_sudo_cmdgroups_exceed_threshold(conv, cmd_threshold)) { + DEBUG(SSSDBG_TRACE_FUNC, + "Command threshold [%d] exceeded, retrieving all sudo command " + "groups\n", cmd_threshold); + return talloc_asprintf(mem_ctx, "(objectClass=%s)", + conv->map_cmdgroup->name); + } else { + return build_filter(mem_ctx, conv->dom->sysdb, conv->cmdgroups, + conv->map_cmdgroup, get_sudo_cmdgroup_rdn); + } +} + +char * +ipa_sudo_conv_cmd_filter(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + int cmd_threshold) +{ + if (ipa_sudo_cmdgroups_exceed_threshold(conv, cmd_threshold)) { + DEBUG(SSSDBG_TRACE_FUNC, + "Command threshold [%d] exceeded, retrieving all sudo commands\n", + cmd_threshold); + return talloc_asprintf(mem_ctx, "(objectClass=%s)", + conv->map_cmd->name); + } else { + return build_filter(mem_ctx, conv->dom->sysdb, conv->cmds, + conv->map_cmd, get_sudo_cmd_rdn); + } +} + +struct ipa_sudo_conv_result_ctx { + struct ipa_sudo_conv *conv; + struct sysdb_attrs **rules; + size_t num_rules; + errno_t ret; +}; + +static const char * +convert_host(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + char *rdn; + const char *group; + errno_t ret; + + *skip_entry = false; + + ret = ipa_get_rdn(mem_ctx, conv->dom->sysdb, value, &rdn, + MATCHRDN_HOST(conv->map_host)); + if (ret == EOK) { + return rdn; + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_rdn() failed on value %s [%d]: %s\n", + value, ret, sss_strerror(ret)); + return NULL; + } + + ret = ipa_get_rdn(mem_ctx, conv->dom->sysdb, value, &rdn, + MATCHRDN_HOSTGROUP(conv->map_hostgroup)); + if (ret == ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected DN %s: Skipping\n", value); + *skip_entry = true; + return NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ipa_get_rdn() failed on value %s [%d]: %s\n", + value, ret, sss_strerror(ret)); + return NULL; + } + + group = talloc_asprintf(mem_ctx, "+%s", rdn); + talloc_free(rdn); + + return group; +} + +static const char * +convert_user(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + char *rdn; + const char *group; + errno_t ret; + + *skip_entry = false; + + ret = ipa_get_rdn(mem_ctx, conv->dom->sysdb, value, &rdn, + MATCHRDN_USER(conv->map_user)); + if (ret == EOK) { + return rdn; + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_rdn() failed on value %s [%d]: %s\n", + value, ret, sss_strerror(ret)); + return NULL; + } + + ret = ipa_get_rdn(mem_ctx, conv->dom->sysdb, value, &rdn, + MATCHRDN_GROUP(conv->map_group)); + if (ret == ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected DN %s: Skipping\n", value); + *skip_entry = true; + return NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ipa_get_rdn() failed on value %s [%d]: %s\n", + value, ret, sss_strerror(ret)); + return NULL; + } + + group = talloc_asprintf(mem_ctx, "%%%s", rdn); + talloc_free(rdn); + + return group; +} + +static const char * +convert_user_fqdn(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + const char *shortname = NULL; + char *fqdn = NULL; + + *skip_entry = false; + + shortname = convert_user(mem_ctx, conv, value, skip_entry); + if (shortname == NULL) { + return NULL; + } + + fqdn = sss_create_internal_fqname(mem_ctx, shortname, conv->dom->name); + talloc_free(discard_const(shortname)); + return fqdn; +} + +static const char * +convert_ext_user(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + /* If value is already fully qualified, return it as it is */ + if (strrchr(value, '@') != NULL) { + return talloc_strdup(mem_ctx, value); + } + return sss_create_internal_fqname(mem_ctx, value, conv->dom->name); +} + +static const char * +convert_group(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + char *rdn; + errno_t ret; + + *skip_entry = false; + + ret = ipa_get_rdn(mem_ctx, conv->dom->sysdb, value, &rdn, + MATCHRDN_GROUP(conv->map_group)); + if (ret == ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected DN %s: Skipping\n", value); + *skip_entry = true; + return NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ipa_get_rdn() failed on value %s [%d]: %s\n", + value, ret, sss_strerror(ret)); + return NULL; + } + + return rdn; +} + +static const char * +convert_group_fqdn(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + const char *shortname = NULL; + char *fqdn = NULL; + + *skip_entry = false; + + shortname = convert_group(mem_ctx, conv, value, skip_entry); + if (shortname == NULL) { + return NULL; + } + + fqdn = sss_create_internal_fqname(mem_ctx, shortname, conv->dom->name); + talloc_free(discard_const(shortname)); + return fqdn; +} + +static const char * +convert_runasextusergroup(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + if (value == NULL) + return NULL; + + if (value[0] == '%') + return talloc_strdup(mem_ctx, value); + + return talloc_asprintf(mem_ctx, "%%%s", value); +} + +static const char * +convert_cat(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + + *skip_entry = false; + + if (strcmp(value, "all") == 0) { + return talloc_strdup(mem_ctx, "ALL"); + } + + return value; +} + +static errno_t +convert_attributes(struct ipa_sudo_conv *conv, + struct ipa_sudo_rule *rule, + struct sysdb_attrs *attrs) +{ + TALLOC_CTX *tmp_ctx; + const char **values; + const char *value; + errno_t ret; + int i, j; + bool skip_entry; + static struct { + const char *ipa; + const char *sudo; + const char *(*conv_fn)(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry); + } table[] = {{SYSDB_NAME, SYSDB_SUDO_CACHE_AT_CN , NULL}, + {SYSDB_IPA_SUDORULE_HOST, SYSDB_SUDO_CACHE_AT_HOST , convert_host}, + {SYSDB_IPA_SUDORULE_USER, SYSDB_SUDO_CACHE_AT_USER , convert_user_fqdn}, + {SYSDB_IPA_SUDORULE_RUNASUSER, SYSDB_SUDO_CACHE_AT_RUNASUSER , convert_user_fqdn}, + {SYSDB_IPA_SUDORULE_RUNASGROUP, SYSDB_SUDO_CACHE_AT_RUNASGROUP , convert_group_fqdn}, + {SYSDB_IPA_SUDORULE_OPTION, SYSDB_SUDO_CACHE_AT_OPTION , NULL}, + {SYSDB_IPA_SUDORULE_NOTAFTER, SYSDB_SUDO_CACHE_AT_NOTAFTER , NULL}, + {SYSDB_IPA_SUDORULE_NOTBEFORE, SYSDB_SUDO_CACHE_AT_NOTBEFORE , NULL}, + {SYSDB_IPA_SUDORULE_SUDOORDER, SYSDB_SUDO_CACHE_AT_ORDER , NULL}, + {SYSDB_IPA_SUDORULE_CMDCATEGORY, SYSDB_SUDO_CACHE_AT_COMMAND , convert_cat}, + {SYSDB_IPA_SUDORULE_HOSTCATEGORY, SYSDB_SUDO_CACHE_AT_HOST , convert_cat}, + {SYSDB_IPA_SUDORULE_USERCATEGORY, SYSDB_SUDO_CACHE_AT_USER , convert_cat}, + {SYSDB_IPA_SUDORULE_RUNASUSERCATEGORY, SYSDB_SUDO_CACHE_AT_RUNASUSER , convert_cat}, + {SYSDB_IPA_SUDORULE_RUNASGROUPCATEGORY, SYSDB_SUDO_CACHE_AT_RUNASGROUP , convert_cat}, + {SYSDB_IPA_SUDORULE_RUNASEXTUSER, SYSDB_SUDO_CACHE_AT_RUNASUSER , NULL}, + {SYSDB_IPA_SUDORULE_RUNASEXTGROUP, SYSDB_SUDO_CACHE_AT_RUNASGROUP , NULL}, + {SYSDB_IPA_SUDORULE_RUNASEXTUSERGROUP, SYSDB_SUDO_CACHE_AT_RUNASUSER , convert_runasextusergroup}, + {SYSDB_IPA_SUDORULE_EXTUSER, SYSDB_SUDO_CACHE_AT_USER , convert_ext_user}, + {SYSDB_IPA_SUDORULE_ALLOWCMD, SYSDB_IPA_SUDORULE_ORIGCMD , NULL}, + {SYSDB_IPA_SUDORULE_DENYCMD, SYSDB_IPA_SUDORULE_ORIGCMD , NULL}, + {NULL, NULL, NULL}}; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + for (i = 0; table[i].ipa != NULL; i++) { + ret = sysdb_attrs_get_string_array(rule->attrs, table[i].ipa, + tmp_ctx, &values); + if (ret == ENOENT) { + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read attribute " + "%s [%d]: %s\n", table[i].ipa, ret, sss_strerror(ret)); + goto done; + } + + for (j = 0; values[j] != NULL; j++) { + if (table[i].conv_fn != NULL) { + value = table[i].conv_fn(tmp_ctx, conv, values[j], &skip_entry); + if (value == NULL) { + if (skip_entry) { + continue; + } else { + ret = ENOMEM; + goto done; + } + } + } else { + value = values[j]; + } + + ret = sysdb_attrs_add_string_safe(attrs, table[i].sudo, value); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add attribute " + "%s [%d]: %s\n", table[i].sudo, ret, sss_strerror(ret)); + goto done; + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char ** +combine_cmdgroups(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + struct ipa_sudo_dn_list *list) +{ + TALLOC_CTX *tmp_ctx; + struct ipa_sudo_cmdgroup *cmdgroup; + struct ipa_sudo_dn_list *listitem; + const char **values = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + values = talloc_zero_array(tmp_ctx, const char *, 1); + if (values == NULL) { + talloc_free(tmp_ctx); + return NULL; + } + + DLIST_FOR_EACH(listitem, list) { + cmdgroup = ipa_sudo_conv_lookup(conv->cmdgroups, listitem->dn); + if (cmdgroup == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ipa_sudo_conv_lookup failed for DN:%s\n", listitem->dn); + continue; + } + + ret = add_strings_lists(mem_ctx, values, cmdgroup->expanded, + false, &values); + if (ret != EOK) { + talloc_free(tmp_ctx); + return NULL; + } + } + + talloc_steal(mem_ctx, values); + talloc_free(tmp_ctx); + + return values; +} + +static const char ** +combine_cmds(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + struct ipa_sudo_dn_list *list) +{ + struct ipa_sudo_dn_list *listitem; + const char **values; + const char *command; + size_t count; + size_t i; + + count = ipa_sudo_dn_list_count(list); + + values = talloc_zero_array(mem_ctx, const char *, count + 1); + if (values == NULL) { + return NULL; + } + + i = 0; + DLIST_FOR_EACH(listitem, list) { + command = ipa_sudo_conv_lookup(conv->cmds, listitem->dn); + if (command == NULL) { + continue; + } + + values[i] = command; + i++; + } + + return values; +} + +static errno_t +build_sudocommand(struct ipa_sudo_conv *conv, + struct ipa_sudo_rulemember *mlist, + struct sysdb_attrs *attrs, + char prefix) +{ + TALLOC_CTX *tmp_ctx; + const char **cmds[2]; + const char *command; + errno_t ret; + int i, j; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + cmds[0] = combine_cmdgroups(tmp_ctx, conv, mlist->cmdgroups); + if (cmds[0] == NULL) { + ret = ENOMEM; + goto done; + } + + cmds[1] = combine_cmds(tmp_ctx, conv, mlist->cmds); + if (cmds[1] == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < 2; i++) { + for (j = 0; cmds[i][j] != NULL; j++) { + if (prefix == '\0') { + command = cmds[i][j]; + } else { + command = talloc_asprintf(tmp_ctx, "%c%s", prefix, cmds[i][j]); + if (command == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = sysdb_attrs_add_string_safe(attrs, + SYSDB_SUDO_CACHE_AT_COMMAND, command); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add attribute " + "%s [%d]: %s\n", SYSDB_SUDO_CACHE_AT_COMMAND, + ret, sss_strerror(ret)); + goto done; + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +convert_sudocommand(struct ipa_sudo_conv *conv, + struct ipa_sudo_rule *rule, + struct sysdb_attrs *attrs) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = build_sudocommand(conv, &rule->allow, attrs, '\0'); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build allow commands " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = build_sudocommand(conv, &rule->deny, attrs, '!'); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build deny commands " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static bool +rules_iterator(hash_entry_t *item, + void *user_data) +{ + struct ipa_sudo_conv_result_ctx *ctx = user_data; + struct ipa_sudo_rule *rule = item->value.ptr; + struct sysdb_attrs *attrs; + + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: ctx is NULL\n"); + return false; + } + + if (rule == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: rule is NULL\n"); + ctx->ret = ERR_INTERNAL; + return false; + } + + attrs = sysdb_new_attrs(ctx->rules); + if (attrs == NULL) { + ctx->ret = ENOMEM; + return false; + } + + ctx->ret = convert_attributes(ctx->conv, rule, attrs); + if (ctx->ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to convert attributes [%d]: %s\n", + ctx->ret, sss_strerror(ctx->ret)); + talloc_free(attrs); + return false; + } + + ctx->ret = convert_sudocommand(ctx->conv, rule, attrs); + if (ctx->ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to build sudoCommand [%d]: %s\n", + ctx->ret, sss_strerror(ctx->ret)); + talloc_free(attrs); + return false; + } + + ctx->rules[ctx->num_rules] = attrs; + ctx->num_rules++; + + return true; +} + +static bool +cmdgroups_iterator(hash_entry_t *item, + void *user_data) +{ + struct ipa_sudo_conv_result_ctx *ctx = user_data; + struct ipa_sudo_cmdgroup *cmdgroup = item->value.ptr; + const char **values; + + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: ctx is NULL\n"); + return false; + } + + if (cmdgroup == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: rule is NULL\n"); + ctx->ret = ERR_INTERNAL; + return false; + } + + values = combine_cmds(cmdgroup, ctx->conv, cmdgroup->cmds); + if (values == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to expand commands\n"); + ctx->ret = ENOMEM; + return false; + } + + cmdgroup->expanded = values; + ctx->ret = EOK; + + return true; +} + +errno_t +ipa_sudo_conv_result(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + struct sysdb_attrs ***_rules, + size_t *_num_rules) +{ + struct ipa_sudo_conv_result_ctx ctx; + struct sysdb_attrs **rules; + unsigned long num_rules; + int hret; + + num_rules = hash_count(conv->rules); + if (num_rules == 0) { + *_rules = NULL; + *_num_rules = 0; + return EOK; + } + + ctx.conv = conv; + ctx.rules = NULL; + ctx.num_rules = 0; + + /* If there are no cmdgroups the iterator is not called and ctx.ret is + * uninitialized. Since it is ok that there are no cmdgroups initializing + * ctx.ret to EOK. */ + ctx.ret = EOK; + + /* Expand commands in command groups. */ + hret = hash_iterate(conv->cmdgroups, cmdgroups_iterator, &ctx); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to iterate over command groups " + "[%d]\n", hret); + return EIO; + } + + if (ctx.ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to expand command groups " + "[%d]: %s\n", ctx.ret, sss_strerror(ctx.ret)); + return ctx.ret; + } + + /* Convert rules. */ + rules = talloc_zero_array(mem_ctx, struct sysdb_attrs *, num_rules); + if (rules == NULL) { + return ENOMEM; + } + + ctx.rules = rules; + ctx.num_rules = 0; + + hret = hash_iterate(conv->rules, rules_iterator, &ctx); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to iterate over rules [%d]\n", hret); + return EIO; + } + + if (ctx.ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to convert rules [%d]: %s\n", + ctx.ret, sss_strerror(ctx.ret)); + talloc_free(rules); + return ctx.ret; + } + + *_rules = ctx.rules; + *_num_rules = ctx.num_rules; + + return EOK; +} diff --git a/src/providers/ipa/ipa_sudo_refresh.c b/src/providers/ipa/ipa_sudo_refresh.c new file mode 100644 index 0000000..7386a01 --- /dev/null +++ b/src/providers/ipa/ipa_sudo_refresh.c @@ -0,0 +1,470 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "util/util.h" +#include "providers/be_ptask.h" +#include "providers/ipa/ipa_sudo.h" +#include "providers/ldap/sdap_sudo_shared.h" +#include "db/sysdb_sudo.h" + +struct ipa_sudo_full_refresh_state { + struct ipa_sudo_ctx *sudo_ctx; + struct sss_domain_info *domain; + int dp_error; +}; + +static void ipa_sudo_full_refresh_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_sudo_full_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx) +{ + struct ipa_sudo_full_refresh_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + char *delete_filter; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_sudo_full_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->domain = sudo_ctx->id_ctx->be->domain; + state->sudo_ctx = sudo_ctx; + + /* Remove all rules from cache */ + delete_filter = talloc_asprintf(state, "(%s=%s)", SYSDB_OBJECTCLASS, + SYSDB_SUDO_CACHE_OC); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a full refresh of sudo rules\n"); + + subreq = ipa_sudo_refresh_send(state, ev, sudo_ctx, + NULL, NULL, delete_filter, true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_sudo_full_refresh_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void +ipa_sudo_full_refresh_done(struct tevent_req *subreq) +{ + struct ipa_sudo_full_refresh_state *state; + struct tevent_req *req; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_full_refresh_state); + + ret = ipa_sudo_refresh_recv(subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + ret = sysdb_sudo_set_last_full_refresh(state->domain, time(NULL)); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to save time of " + "a successful full refresh\n"); + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successful full refresh of sudo rules\n"); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* We just finished full request, we can postpone smart refresh. */ + be_ptask_postpone(state->sudo_ctx->smart_refresh); + + tevent_req_done(req); +} + +int +ipa_sudo_full_refresh_recv(struct tevent_req *req, + int *dp_error) +{ + struct ipa_sudo_full_refresh_state *state; + state = tevent_req_data(req, struct ipa_sudo_full_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +struct ipa_sudo_smart_refresh_state { + int dp_error; +}; + +static void ipa_sudo_smart_refresh_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_sudo_smart_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx) +{ + struct sdap_server_opts *srv_opts = sudo_ctx->id_ctx->srv_opts; + struct ipa_sudo_smart_refresh_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + char *cmdgroups_filter; + char *search_filter; + const char *usn; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_sudo_smart_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (be_ptask_running(sudo_ctx->full_refresh)) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping smart refresh because " + "there is ongoing full refresh.\n"); + state->dp_error = DP_ERR_OK; + ret = EOK; + goto immediately; + } + + /* Download all rules from LDAP that are newer than usn */ + if (srv_opts == NULL || srv_opts->max_sudo_value == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "USN value is unknown, assuming zero.\n"); + usn = "0"; + search_filter = NULL; + } else { + usn = srv_opts->max_sudo_value; + search_filter = talloc_asprintf(state, "(%s>=%s)", + sudo_ctx->sudorule_map[IPA_AT_SUDORULE_ENTRYUSN].name, usn); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + } + + cmdgroups_filter = talloc_asprintf(state, "(%s>=%s)", + sudo_ctx->sudocmdgroup_map[IPA_AT_SUDOCMDGROUP_ENTRYUSN].name, usn); + if (cmdgroups_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* Do not remove any rules that are already in the sysdb. */ + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a smart refresh of sudo rules " + "(USN >= %s)\n", usn); + + subreq = ipa_sudo_refresh_send(state, ev, sudo_ctx, cmdgroups_filter, + search_filter, NULL, true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_sudo_smart_refresh_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + tevent_req_post(req, ev); + + return req; +} + +static void ipa_sudo_smart_refresh_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct ipa_sudo_smart_refresh_state *state = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_smart_refresh_state); + + ret = ipa_sudo_refresh_recv(subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successful smart refresh of sudo rules\n"); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int ipa_sudo_smart_refresh_recv(struct tevent_req *req, + int *dp_error) +{ + struct ipa_sudo_smart_refresh_state *state = NULL; + state = tevent_req_data(req, struct ipa_sudo_smart_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +struct ipa_sudo_rules_refresh_state { + size_t num_rules; + int dp_error; + bool deleted; +}; + +static void ipa_sudo_rules_refresh_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_sudo_rules_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx, + const char **rules) +{ + TALLOC_CTX *tmp_ctx; + struct ipa_sudo_rules_refresh_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + char *search_filter; + char *delete_filter; + char *safe_rule; + errno_t ret; + int i; + + req = tevent_req_create(mem_ctx, &state, struct ipa_sudo_rules_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + ret = ENOMEM; + goto immediately; + } + + if (rules == NULL || rules[0] == NULL) { + state->dp_error = DP_ERR_OK; + state->num_rules = 0; + state->deleted = false; + ret = EOK; + goto immediately; + } + + search_filter = talloc_zero(tmp_ctx, char); /* assign to tmp_ctx */ + delete_filter = talloc_zero(tmp_ctx, char); /* assign to tmp_ctx */ + + /* Download only selected rules from LDAP. */ + /* Remove all selected rules from cache. */ + for (i = 0; rules[i] != NULL; i++) { + ret = sss_filter_sanitize(tmp_ctx, rules[i], &safe_rule); + if (ret != EOK) { + ret = ENOMEM; + goto immediately; + } + + search_filter = talloc_asprintf_append_buffer(search_filter, "(%s=%s)", + sudo_ctx->sudorule_map[IPA_AT_SUDORULE_NAME].name, + safe_rule); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + delete_filter = talloc_asprintf_append_buffer(delete_filter, "(%s=%s)", + SYSDB_NAME, safe_rule); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + } + + state->num_rules = i; + + search_filter = talloc_asprintf(tmp_ctx, "(|%s)", search_filter); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + delete_filter = talloc_asprintf(tmp_ctx, "(&(%s=%s)(|%s))", + SYSDB_OBJECTCLASS, SYSDB_SUDO_CACHE_OC, + delete_filter); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + subreq = ipa_sudo_refresh_send(req, ev, sudo_ctx, NULL, search_filter, + delete_filter, false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_sudo_rules_refresh_done, req); + + ret = EOK; + +immediately: + talloc_free(tmp_ctx); + + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void +ipa_sudo_rules_refresh_done(struct tevent_req *subreq) +{ + struct ipa_sudo_rules_refresh_state *state; + struct tevent_req *req = NULL; + size_t downloaded_rules_num; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_rules_refresh_state); + + ret = ipa_sudo_refresh_recv(subreq, &state->dp_error, &downloaded_rules_num); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + state->deleted = downloaded_rules_num != state->num_rules ? true : false; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int +ipa_sudo_rules_refresh_recv(struct tevent_req *req, + int *dp_error, + bool *deleted) +{ + struct ipa_sudo_rules_refresh_state *state; + state = tevent_req_data(req, struct ipa_sudo_rules_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + *deleted = state->deleted; + + return EOK; +} + +static struct tevent_req * +ipa_sudo_ptask_full_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ipa_sudo_ctx *sudo_ctx; + sudo_ctx = talloc_get_type(pvt, struct ipa_sudo_ctx); + + return ipa_sudo_full_refresh_send(mem_ctx, be_ctx->ev, sudo_ctx); +} + +static errno_t +ipa_sudo_ptask_full_refresh_recv(struct tevent_req *req) +{ + int dp_error; + + return ipa_sudo_full_refresh_recv(req, &dp_error); +} + +static struct tevent_req * +ipa_sudo_ptask_smart_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ipa_sudo_ctx *sudo_ctx; + sudo_ctx = talloc_get_type(pvt, struct ipa_sudo_ctx); + + return ipa_sudo_smart_refresh_send(mem_ctx, be_ctx->ev, sudo_ctx); +} + +static errno_t +ipa_sudo_ptask_smart_refresh_recv(struct tevent_req *req) +{ + int dp_error; + + return ipa_sudo_smart_refresh_recv(req, &dp_error); +} + +errno_t +ipa_sudo_ptask_setup(struct be_ctx *be_ctx, struct ipa_sudo_ctx *sudo_ctx) +{ + return sdap_sudo_ptask_setup_generic(be_ctx, sudo_ctx->id_ctx->opts->basic, + ipa_sudo_ptask_full_refresh_send, + ipa_sudo_ptask_full_refresh_recv, + ipa_sudo_ptask_smart_refresh_send, + ipa_sudo_ptask_smart_refresh_recv, + sudo_ctx, + &sudo_ctx->full_refresh, + &sudo_ctx->smart_refresh); +} diff --git a/src/providers/ipa/ipa_utils.c b/src/providers/ipa/ipa_utils.c new file mode 100644 index 0000000..86ba51c --- /dev/null +++ b/src/providers/ipa/ipa_utils.c @@ -0,0 +1,63 @@ +/* + SSSD + + IPA Module utility functions + + Authors: + Sumit Bose + + Copyright (C) 2014 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" + +#define OVERRIDE_ANCHOR_IPA_PREFIX ":IPA:" +#define OVERRIDE_ANCHOR_IPA_PREFIX_LEN (sizeof(OVERRIDE_ANCHOR_IPA_PREFIX) -1 ) + +errno_t split_ipa_anchor(TALLOC_CTX *mem_ctx, const char *anchor, + char **_anchor_domain, char **_ipa_uuid) +{ + const char *sep; + + if (anchor == NULL) { + return EINVAL; + } + if (strncmp(OVERRIDE_ANCHOR_IPA_PREFIX, anchor, + OVERRIDE_ANCHOR_IPA_PREFIX_LEN) != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "No IPA anchor [%s].\n", anchor); + return ENOMSG; + } + + sep = strchr(anchor + OVERRIDE_ANCHOR_IPA_PREFIX_LEN, ':'); + if (sep == NULL || sep[1] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Broken IPA anchor [%s].\n", anchor); + return EINVAL; + } + + *_anchor_domain = talloc_strndup(mem_ctx, + anchor + OVERRIDE_ANCHOR_IPA_PREFIX_LEN, + sep - anchor - OVERRIDE_ANCHOR_IPA_PREFIX_LEN); + *_ipa_uuid = talloc_strdup(mem_ctx, sep + 1); + + if (*_anchor_domain == NULL || *_ipa_uuid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + talloc_free(*_anchor_domain); + talloc_free(*_ipa_uuid); + return ENOMEM; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_views.c b/src/providers/ipa/ipa_views.c new file mode 100644 index 0000000..3e58949 --- /dev/null +++ b/src/providers/ipa/ipa_views.c @@ -0,0 +1,653 @@ +/* + SSSD + + IPA Identity Backend Module for views and overrides + + Authors: + Sumit Bose + + Copyright (C) 2014 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "util/strtonum.h" +#include "util/cert.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ipa/ipa_id.h" +#include "db/sysdb.h" + +#define MAX_USER_AND_GROUP_REPLIES 2 + +static errno_t get_user_or_group(TALLOC_CTX *mem_ctx, + struct ipa_options *ipa_opts, + struct sysdb_attrs *attrs, + enum sysdb_obj_type *_what_is) +{ + errno_t ret; + const char **values; + const char **value; + bool is_user = false; + bool is_group = false; + const char *ov_user_name = ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name; + const char *ov_group_name = ipa_opts->override_map[IPA_OC_OVERRIDE_GROUP].name; + + ret = sysdb_attrs_get_string_array(attrs, SYSDB_ORIG_OBJECTCLASS, mem_ctx, &values); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to retrieve attribute [%s].\n", + SYSDB_ORIG_OBJECTCLASS); + return ret; + } + + /* We assume an entry can be a user or a group override but not both. + * So we leave as soon as we identify one of them. */ + if (values != NULL) { + for (value = values; *value != NULL; value++) { + if (strcasecmp(*value, ov_user_name) == 0) { + is_user = true; + break; + } else if (strcasecmp(*value, ov_group_name) == 0) { + is_group = true; + break; + } + } + talloc_free(values); + } + + /* We also assume it must be necessarily a user or a group. */ + if (!is_user && !is_group) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected override found.\n"); + return EINVAL; + } + + if (_what_is != NULL) { + *_what_is = is_user ? SYSDB_USER : SYSDB_GROUP; + } + + return EOK; +} + +/* Verify there are exactly 1 user and 1 group override. Any other combination + * is wrong. Then keep only the group override. */ +static errno_t check_and_filter_user_and_group(struct ipa_options *ipa_opts, + struct sysdb_attrs **reply, + size_t *reply_count) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + enum sysdb_obj_type entry_is[MAX_USER_AND_GROUP_REPLIES]; + int i; + + if (*reply_count != MAX_USER_AND_GROUP_REPLIES) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Expected %i replies but got %lu\n", + MAX_USER_AND_GROUP_REPLIES, *reply_count); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate memory.\n"); + return ENOMEM; + } + + for (i = 0; i < MAX_USER_AND_GROUP_REPLIES; i++) { + ret = get_user_or_group(tmp_ctx, ipa_opts, reply[i], &entry_is[i]); + if (ret != EOK) { + goto done; + } + } + + if (entry_is[0] == SYSDB_USER && entry_is[1] == SYSDB_USER) { + DEBUG(SSSDBG_CRIT_FAILURE, "Found 2 user overrides.\n"); + ret = EINVAL; + goto done; + } else if (entry_is[0] == SYSDB_GROUP && entry_is[1] == SYSDB_GROUP) { + DEBUG(SSSDBG_CRIT_FAILURE, "Found 2 group overrides.\n"); + ret = EINVAL; + goto done; + } + + /* We have one user and one group override. Keep only the group override. */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Keeping only the group override.\n"); + if (entry_is[0] == SYSDB_USER) { + talloc_free(reply[0]); + reply[0] = reply[1]; + } else { + talloc_free(reply[1]); + } + reply[1] = NULL; + *reply_count = 1; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t dp_id_data_to_override_filter(TALLOC_CTX *mem_ctx, + struct ipa_options *ipa_opts, + struct dp_id_data *ar, + char **override_filter) +{ + char *filter; + uint32_t id; + char *endptr; + char *cert_filter; + int ret; + char *shortname; + char *sanitized_name; + + switch (ar->filter_type) { + case BE_FILTER_NAME: + ret = sss_parse_internal_fqname(mem_ctx, ar->filter_value, + &shortname, NULL); + if (ret != EOK) { + return ret; + } + + ret = sss_filter_sanitize(mem_ctx, shortname, &sanitized_name); + talloc_free(shortname); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_filter_sanitize failed.\n"); + return ret; + } + + switch ((ar->entry_type & BE_REQ_TYPE_MASK)) { + case BE_REQ_USER: + case BE_REQ_INITGROUPS: + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(%s=%s))", + ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_USER_NAME].name, + sanitized_name); + break; + + case BE_REQ_GROUP: + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(%s=%s))", + ipa_opts->override_map[IPA_OC_OVERRIDE_GROUP].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_GROUP_NAME].name, + sanitized_name); + break; + + case BE_REQ_USER_AND_GROUP: + filter = talloc_asprintf(mem_ctx, + "(|(&(objectClass=%s)(%s=%s))(&(objectClass=%s)(%s=%s)))", + ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_USER_NAME].name, + sanitized_name, + ipa_opts->override_map[IPA_OC_OVERRIDE_GROUP].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_GROUP_NAME].name, + sanitized_name); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected entry type [%d] for name filter.\n", + ar->entry_type); + talloc_free(sanitized_name); + return EINVAL; + } + talloc_free(sanitized_name); + break; + + case BE_FILTER_IDNUM: + id = strtouint32(ar->filter_value, &endptr, 10); + if (errno != 0|| *endptr != '\0' || (ar->filter_value == endptr)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid id value [%s].\n", + ar->filter_value); + return EINVAL; + } + switch ((ar->entry_type & BE_REQ_TYPE_MASK)) { + case BE_REQ_USER: + case BE_REQ_INITGROUPS: + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(%s=%"PRIu32"))", + ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_UID_NUMBER].name, + id); + break; + + case BE_REQ_GROUP: + filter = talloc_asprintf(mem_ctx, + "(&(objectClass=%s)(%s=%"PRIu32"))", + ipa_opts->override_map[IPA_OC_OVERRIDE_GROUP].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_GROUP_GID_NUMBER].name, + id); + break; + + case BE_REQ_USER_AND_GROUP: + filter = talloc_asprintf(mem_ctx, + "(|(&(objectClass=%s)(%s=%"PRIu32"))(&(objectClass=%s)(%s=%"PRIu32")))", + ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_UID_NUMBER].name, + id, + ipa_opts->override_map[IPA_OC_OVERRIDE_GROUP].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_GROUP_GID_NUMBER].name, + id); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected entry type [%d] for id filter.\n", + ar->entry_type); + return EINVAL; + } + break; + + case BE_FILTER_SECID: + if ((ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_BY_SECID) { + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(%s=:SID:%s))", + ipa_opts->override_map[IPA_OC_OVERRIDE].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_ANCHOR_UUID].name, + ar->filter_value); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected entry type [%d] for SID filter.\n", + ar->entry_type); + return EINVAL; + } + break; + + case BE_FILTER_UUID: + if ((ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_BY_UUID) { + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(%s=:IPA:%s:%s))", + ipa_opts->override_map[IPA_OC_OVERRIDE].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_ANCHOR_UUID].name, + dp_opt_get_string(ipa_opts->basic, IPA_DOMAIN), + ar->filter_value); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected entry type [%d] for UUID filter.\n", + ar->entry_type); + return EINVAL; + } + break; + + case BE_FILTER_CERT: + if ((ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_BY_CERT) { + ret = sss_cert_derb64_to_ldap_filter(mem_ctx, ar->filter_value, + ipa_opts->override_map[IPA_AT_OVERRIDE_USER_CERT].name, + NULL, NULL, &cert_filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter failed.\n"); + return ret; + } + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)%s)", + ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name, + cert_filter); + talloc_free(cert_filter); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected entry type [%d] for certificate filter.\n", + ar->entry_type); + return EINVAL; + } + break; + + default: + DEBUG(SSSDBG_OP_FAILURE, "Invalid sub-domain filter type.\n"); + return EINVAL; + } + + if (filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + *override_filter = filter; + + return EOK; +} + +static errno_t get_dp_id_data_for_xyz(TALLOC_CTX *mem_ctx, const char *val, + const char *domain_name, + int type, + struct dp_id_data **_ar) +{ + struct dp_id_data *ar; + + ar = talloc_zero(mem_ctx, struct dp_id_data); + if (ar == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + switch (type) { + case BE_REQ_BY_SECID: + ar->entry_type = BE_REQ_BY_SECID; + ar->filter_type = BE_FILTER_SECID; + break; + case BE_REQ_BY_UUID: + ar->entry_type = BE_REQ_BY_UUID; + ar->filter_type = BE_FILTER_UUID; + break; + case BE_REQ_USER: + ar->entry_type = BE_REQ_USER; + ar->filter_type = BE_FILTER_NAME; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported request type [%d].\n", type); + talloc_free(ar); + return EINVAL; + } + + ar->filter_value = talloc_strdup(ar, val); + ar->domain = talloc_strdup(ar, domain_name); + if (ar->filter_value == NULL || ar->domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + talloc_free(ar); + return ENOMEM; + } + + + *_ar = ar; + + return EOK; +} + +errno_t get_dp_id_data_for_sid(TALLOC_CTX *mem_ctx, const char *sid, + const char *domain_name, + struct dp_id_data **_ar) +{ + return get_dp_id_data_for_xyz(mem_ctx, sid, domain_name, BE_REQ_BY_SECID, + _ar); +} + +errno_t get_dp_id_data_for_uuid(TALLOC_CTX *mem_ctx, const char *uuid, + const char *domain_name, + struct dp_id_data **_ar) +{ + return get_dp_id_data_for_xyz(mem_ctx, uuid, domain_name, BE_REQ_BY_UUID, + _ar); +} + +errno_t get_dp_id_data_for_user_name(TALLOC_CTX *mem_ctx, + const char *user_name, + const char *domain_name, + struct dp_id_data **_ar) +{ + return get_dp_id_data_for_xyz(mem_ctx, user_name, domain_name, BE_REQ_USER, + _ar); +} + +struct ipa_get_ad_override_state { + struct tevent_context *ev; + struct sdap_id_ctx *sdap_id_ctx; + struct ipa_options *ipa_options; + const char *ipa_realm; + const char *ipa_view_name; + struct dp_id_data *ar; + + struct sdap_id_op *sdap_op; + int dp_error; + struct sysdb_attrs *override_attrs; + char *filter; +}; + +static void ipa_get_ad_override_connect_done(struct tevent_req *subreq); +static errno_t ipa_get_ad_override_qualify_name( + struct ipa_get_ad_override_state *state); +static void ipa_get_ad_override_done(struct tevent_req *subreq); + +struct tevent_req *ipa_get_ad_override_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_id_ctx, + struct ipa_options *ipa_options, + const char *ipa_realm, + const char *view_name, + struct dp_id_data *ar) +{ + int ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_get_ad_override_state *state; + + req = tevent_req_create(mem_ctx, &state, struct ipa_get_ad_override_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->sdap_id_ctx = sdap_id_ctx; + state->ipa_options = ipa_options; + state->ipa_realm = ipa_realm; + state->ar = ar; + state->dp_error = -1; + state->override_attrs = NULL; + state->filter = NULL; + + if (view_name == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "View not defined, nothing to do.\n"); + ret = EOK; + goto done; + } + + if (is_default_view(view_name)) { + state->ipa_view_name = IPA_DEFAULT_VIEW_NAME; + } else { + state->ipa_view_name = view_name; + } + + state->sdap_op = sdap_id_op_create(state, + state->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto done; + } + + tevent_req_set_callback(subreq, ipa_get_ad_override_connect_done, req); + + return req; + +done: + if (ret != EOK) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + } else { + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + } + tevent_req_post(req, state->ev); + + return req; +} + +static void ipa_get_ad_override_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_ad_override_state *state = tevent_req_data(req, + struct ipa_get_ad_override_state); + int ret; + char *basedn; + char *search_base; + struct ipa_options *ipa_opts = state->ipa_options; + + ret = sdap_id_op_connect_recv(subreq, &state->dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (state->dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No IPA server is available, going offline\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to IPA server: [%d](%s)\n", + ret, strerror(ret)); + } + + goto fail; + } + + ret = domain_to_basedn(state, state->ipa_realm, &basedn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "domain_to_basedn failed.\n"); + goto fail; + } + + search_base = talloc_asprintf(state, "cn=%s,%s", state->ipa_view_name, + ipa_opts->views_search_bases[0]->basedn); + if (search_base == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto fail; + } + + ret = dp_id_data_to_override_filter(state, state->ipa_options, state->ar, + &state->filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "dp_id_data_to_override_filter failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Searching for overrides in view [%s] with filter [%s].\n", + state->ipa_view_name, state->filter); + + subreq = sdap_get_generic_send(state, state->ev, state->sdap_id_ctx->opts, + sdap_id_op_handle(state->sdap_op), search_base, + LDAP_SCOPE_SUBTREE, + state->filter, NULL, + state->ipa_options->override_map, + IPA_OPTS_OVERRIDE, + dp_opt_get_int(state->sdap_id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_get_ad_override_done, req); + return; + +fail: + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; +} + +static void ipa_get_ad_override_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_ad_override_state *state = tevent_req_data(req, + struct ipa_get_ad_override_state); + int ret; + size_t reply_count = 0; + struct sysdb_attrs **reply = NULL; + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override request failed.\n"); + goto fail; + } + + if (reply_count == 0) { + DEBUG(SSSDBG_TRACE_ALL, "No override found with filter [%s].\n", + state->filter); + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + } else if (reply_count == MAX_USER_AND_GROUP_REPLIES && + (state->ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_USER_AND_GROUP) { + DEBUG(SSSDBG_TRACE_ALL, + "Found two overrides with BE_REQ_USER_AND_GROUP filter [%s].\n", + state->filter); + ret = check_and_filter_user_and_group(state->ipa_options, reply, + &reply_count); + if (ret != EOK) { + goto fail; + } + } else if (reply_count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Found [%zu] overrides with filter [%s], expected only 1.\n", + reply_count, state->filter); + ret = EINVAL; + goto fail; + } + + DEBUG(SSSDBG_TRACE_ALL, "Found override for object with filter [%s].\n", + state->filter); + state->override_attrs = reply[0]; + + ret = ipa_get_ad_override_qualify_name(state); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot qualify object name\n"); + goto fail; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; +} + +static errno_t ipa_get_ad_override_qualify_name( + struct ipa_get_ad_override_state *state) +{ + int ret; + struct ldb_message_element *name; + char *fqdn; + + ret = sysdb_attrs_get_el_ext(state->override_attrs, SYSDB_NAME, + false, &name); + if (ret == ENOENT) { + return EOK; /* Does not override name */ + } else if (ret != EOK && ret != ENOENT) { + return ret; + } + + fqdn = sss_create_internal_fqname(name->values, + (const char *) name->values[0].data, + state->ar->domain); + if (fqdn == NULL) { + return ENOMEM; + } + + name->values[0].data = (uint8_t *) fqdn; + name->values[0].length = strlen(fqdn); + return EOK; +} + +errno_t ipa_get_ad_override_recv(struct tevent_req *req, int *dp_error_out, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **override_attrs) +{ + struct ipa_get_ad_override_state *state = tevent_req_data(req, + struct ipa_get_ad_override_state); + + if (dp_error_out != NULL) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (override_attrs != NULL) { + *override_attrs = talloc_steal(mem_ctx, state->override_attrs); + } + + return EOK; +} diff --git a/src/providers/ipa/selinux_child.c b/src/providers/ipa/selinux_child.c new file mode 100644 index 0000000..063bea4 --- /dev/null +++ b/src/providers/ipa/selinux_child.c @@ -0,0 +1,422 @@ +/* + SSSD + + IPA back end -- set SELinux context in a child module + + Authors: + Jakub Hrozek + + Copyright (C) 2014 Red Hat + + 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 3 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; 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, see . +*/ + + +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "util/child_common.h" +#include "util/sss_chain_id.h" +#include "providers/backend.h" + +struct input_buffer { + const char *seuser; + const char *mls_range; + const char *username; +}; + +static errno_t unpack_buffer(uint8_t *buf, + size_t size, + struct input_buffer *ibuf) +{ + size_t p = 0; + uint32_t len; + + /* seuser */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_INTERNAL, "seuser length: %d\n", len); + if (len == 0) { + ibuf->seuser = ""; + DEBUG(SSSDBG_TRACE_INTERNAL, + "Empty SELinux user, will delete the mapping\n"); + } else { + if (len > size - p) return EINVAL; + ibuf->seuser = talloc_strndup(ibuf, (char *)(buf + p), len); + if (ibuf->seuser == NULL) return ENOMEM; + DEBUG(SSSDBG_TRACE_INTERNAL, "seuser: %s\n", ibuf->seuser); + p += len; + } + + /* MLS range */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_INTERNAL, "mls_range length: %d\n", len); + if (len == 0) { + if (strcmp(ibuf->seuser, "") != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "No MLS mapping!\n"); + return EINVAL; + } + } else { + if (len > size - p) return EINVAL; + ibuf->mls_range = talloc_strndup(ibuf, (char *)(buf + p), len); + if (ibuf->mls_range == NULL) return ENOMEM; + DEBUG(SSSDBG_TRACE_INTERNAL, "mls_range: %s\n", ibuf->mls_range); + p += len; + } + + /* username */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_INTERNAL, "username length: %d\n", len); + if (len == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "No username set!\n"); + return EINVAL; + } else { + if (len > size - p) return EINVAL; + ibuf->username = talloc_strndup(ibuf, (char *)(buf + p), len); + if (ibuf->username == NULL) return ENOMEM; + DEBUG(SSSDBG_TRACE_INTERNAL, "username: %s\n", ibuf->username); + p += len; + } + + return EOK; +} + +static errno_t pack_buffer(struct response *r, int result) +{ + size_t p = 0; + + /* A buffer with the following structure must be created: + * uint32_t status of the request (required) + */ + r->size = sizeof(uint32_t); + + r->buf = talloc_array(r, uint8_t, r->size); + if(r->buf == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, "result [%d]\n", result); + + /* result */ + SAFEALIGN_SET_UINT32(&r->buf[p], result, &p); + + return EOK; +} + +static errno_t prepare_response(TALLOC_CTX *mem_ctx, + int result, + struct response **rsp) +{ + int ret; + struct response *r = NULL; + + r = talloc_zero(mem_ctx, struct response); + if (r == NULL) { + return ENOMEM; + } + + r->buf = NULL; + r->size = 0; + + ret = pack_buffer(r, result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pack_buffer failed\n"); + return ret; + } + + *rsp = r; + DEBUG(SSSDBG_TRACE_ALL, "r->size: %zu\n", r->size); + return EOK; +} + +static int sc_set_seuser(const char *login_name, const char *seuser_name, + const char *mls) +{ + int ret; + mode_t old_mask; + + /* Bug origin: https://bugzilla.redhat.com/show_bug.cgi?id=1186422 + * This workaround is required for libsemanage < 2.5-13.el7 + * It will remain here as a precaution in case of unexpected + * libsemanage behaviour. + */ + old_mask = umask(0); + if (strcmp(seuser_name, "") == 0) { + /* An empty SELinux user should cause SSSD to use the system + * default. We need to remove the SELinux user from the DB + * in that case + */ + ret = sss_del_seuser(login_name); + } else { + ret = sss_set_seuser(login_name, seuser_name, mls); + } + umask(old_mask); + return ret; +} + +static bool seuser_needs_update(const char *username, + const char *seuser, + const char *mls_range) +{ + bool needs_update = true; + char *db_seuser = NULL; + char *db_mls_range = NULL; + errno_t ret; + + ret = sss_get_seuser(username, &db_seuser, &db_mls_range); + DEBUG(SSSDBG_TRACE_INTERNAL, + "sss_get_seuser: ret: %d seuser: %s mls: %s\n", + ret, db_seuser ? db_seuser : "unknown", + db_mls_range ? db_mls_range : "unknown"); + if (ret == EOK && db_seuser && db_mls_range && + strcmp(db_seuser, seuser) == 0 && + strcmp(db_mls_range, mls_range) == 0) { + ret = sss_seuser_exists(username); + if (ret == EOK) { + needs_update = false; + } + } + /* OR */ + if (ret == ERR_SELINUX_NOT_MANAGED) { + needs_update = false; + } + + free(db_seuser); + free(db_mls_range); + DEBUG(SSSDBG_TRACE_FUNC, + "The SELinux user does %sneed an update\n", + needs_update ? "" : "not "); + return needs_update; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + int debug_fd = -1; + int dumpable = 1; + errno_t ret; + TALLOC_CTX *main_ctx = NULL; + uint8_t *buf = NULL; + ssize_t len = 0; + struct input_buffer *ibuf = NULL; + struct response *resp = NULL; + struct passwd *passwd = NULL; + ssize_t written; + bool needs_update; + const char *username; + const char *opt_logger = NULL; + long chain_id; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, + _("Allow core dumps"), NULL }, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + {"chain-id", 0, POPT_ARG_LONG, &chain_id, + 0, _("Tevent chain ID used for logging purposes"), NULL}, + SSSD_LOGGER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + } + + poptFreeContext(pc); + + prctl(PR_SET_DUMPABLE, (dumpable == 0) ? 0 : 1); + + debug_prg_name = talloc_asprintf(NULL, "selinux_child[%d]", getpid()); + if (debug_prg_name == NULL) { + ERROR("talloc_asprintf failed.\n"); + goto fail; + } + + if (debug_fd != -1) { + opt_logger = sss_logger_str[FILES_LOGGER]; + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + opt_logger = sss_logger_str[STDERR_LOGGER]; + ERROR("set_debug_file_from_fd failed.\n"); + } + } + + sss_chain_id_set_format(DEBUG_CHAIN_ID_FMT_RID); + sss_chain_id_set((uint64_t)chain_id); + + DEBUG_INIT(debug_level, opt_logger); + + DEBUG(SSSDBG_TRACE_FUNC, "selinux_child started.\n"); + DEBUG(SSSDBG_TRACE_INTERNAL, + "Running with effective IDs: [%"SPRIuid"][%"SPRIgid"].\n", + geteuid(), getegid()); + + /* The functions semanage_genhomedircon and getseuserbyname use gepwnam_r + * and they might fail to return values if they are not in memory cache. + * [main] (0x0400): performing selinux operations + * [seuser_needs_update] (0x2000): getseuserbyname: ret: 0 + * seuser: unconfined_u mls: s0-s0:c0.c15 + * [libsemanage] (0x0020): semanage_genhomedircon returned error code -1. + * [sss_set_seuser] (0x0020): Cannot commit SELinux transaction + * [main] (0x0020): Cannot set SELinux login context. + * [main] (0x0020): selinux_child failed! + */ + if (unsetenv("_SSS_LOOPS") != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to unset _SSS_LOOPS, some libsemanage functions might " + "fail.\n"); + } + + /* libsemanage calls access(2) which works with real IDs, not effective. + * We need to switch also the real ID to 0. + */ + if (getuid() != 0) { + ret = setuid(0); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "setuid failed: %d, selinux_child might not work!\n", ret); + } + } + + if (getgid() != 0) { + ret = setgid(0); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "setgid failed: %d, selinux_child might not work!\n", ret); + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Running with real IDs [%"SPRIuid"][%"SPRIgid"].\n", + getuid(), getgid()); + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + talloc_free(discard_const(debug_prg_name)); + goto fail; + } + talloc_steal(main_ctx, debug_prg_name); + + buf = talloc_size(main_ctx, sizeof(uint8_t)*IN_BUF_SIZE); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + goto fail; + } + + ibuf = talloc_zero(main_ctx, struct input_buffer); + if (ibuf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "context initialized\n"); + + errno = 0; + len = sss_atomic_read_s(STDIN_FILENO, buf, IN_BUF_SIZE); + if (len == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "read failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + close(STDIN_FILENO); + + ret = unpack_buffer(buf, len, ibuf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "unpack_buffer failed.[%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "performing selinux operations\n"); + + /* When using domain_resolution_order the username will always be + * fully-qualified, what has been causing some SELinux issues as mappings + * for user 'admin' are not applied for 'admin@ipa.example'. + * + * In order to work this around we can take advantage that selinux_child + * queries SSSD since commit 92addd7ba and call getpwnam() in order to get + * the username in the correct format. */ + passwd = getpwnam(ibuf->username); + if (passwd == NULL) { + username = ibuf->username; + DEBUG(SSSDBG_MINOR_FAILURE, + "getpwnam() failed to get info for the user \"%s\". SELinux label " + "setting might fail as well!\n", + ibuf->username); + } else { + username = passwd->pw_name; + } + + needs_update = seuser_needs_update(username, ibuf->seuser, + ibuf->mls_range); + if (needs_update == true) { + ret = sc_set_seuser(username, ibuf->seuser, ibuf->mls_range); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set SELinux login context.\n"); + goto fail; + } + } + + ret = prepare_response(main_ctx, ret, &resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to prepare response buffer.\n"); + goto fail; + } + + errno = 0; + + written = sss_atomic_write_s(STDOUT_FILENO, resp->buf, resp->size); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "write failed [%d][%s].\n", ret, + strerror(ret)); + goto fail; + } + + if (written != resp->size) { + DEBUG(SSSDBG_CRIT_FAILURE, "Expected to write %zu bytes, wrote %zu\n", + resp->size, written); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "selinux_child completed successfully\n"); + close(STDOUT_FILENO); + talloc_free(main_ctx); + return EXIT_SUCCESS; +fail: + DEBUG(SSSDBG_CRIT_FAILURE, "selinux_child failed!\n"); + close(STDOUT_FILENO); + talloc_free(main_ctx); + return EXIT_FAILURE; +} diff --git a/src/providers/krb5/krb5_access.c b/src/providers/krb5/krb5_access.c new file mode 100644 index 0000000..2ae5abe --- /dev/null +++ b/src/providers/krb5/krb5_access.c @@ -0,0 +1,220 @@ +/* + SSSD + + Kerberos 5 Backend Module - access control + + Authors: + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_utils.h" + +struct krb5_access_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + + struct pam_data *pd; + struct krb5_ctx *krb5_ctx; + struct krb5child_req *kr; + + bool access_allowed; +}; + +static void krb5_access_done(struct tevent_req *subreq); +struct tevent_req *krb5_access_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct pam_data *pd, + struct krb5_ctx *krb5_ctx) +{ + struct krb5_access_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + int ret; + const char **attrs; + struct ldb_result *res; + struct sss_domain_info *dom; + + req = tevent_req_create(mem_ctx, &state, struct krb5_access_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->pd = pd; + state->krb5_ctx = krb5_ctx; + state->access_allowed = false; + + ret = get_domain_or_subdomain(be_ctx, pd->domain, &dom); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_domain_or_subdomain failed.\n"); + goto done; + } + + ret = krb5_setup(state, pd, dom, krb5_ctx, &state->kr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_setup failed.\n"); + goto done; + } + + if (pd->cmd != SSS_PAM_ACCT_MGMT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected pam task %d.\n", pd->cmd); + ret = EINVAL; + goto done; + } + + attrs = talloc_array(state, const char *, 5); + if (attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + + attrs[0] = SYSDB_UPN; + attrs[1] = SYSDB_UIDNUM; + attrs[2] = SYSDB_GIDNUM; + attrs[3] = SYSDB_CANONICAL_UPN; + attrs[4] = NULL; + + ret = sysdb_get_user_attr(state, be_ctx->domain, state->pd->user, attrs, + &res); + if (ret) { + DEBUG(SSSDBG_FUNC_DATA, + "sysdb search for upn of user [%s] failed.\n", pd->user); + goto done; + } + + switch (res->count) { + case 0: + DEBUG(SSSDBG_FUNC_DATA, + "No attributes for user [%s] found.\n", pd->user); + ret = ENOENT; + goto done; + break; + case 1: + ret = find_or_guess_upn(state, res->msgs[0], krb5_ctx, be_ctx->domain, + state->kr->user, pd->domain, &state->kr->upn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "find_or_guess_upn failed.\n"); + goto done; + } + + state->kr->uid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_UIDNUM, + 0); + if (state->kr->uid == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "UID for user [%s] not known.\n", pd->user); + ret = ENOENT; + goto done; + } + + state->kr->gid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_GIDNUM, + 0); + if (state->kr->gid == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "GID for user [%s] not known.\n", pd->user); + ret = ENOENT; + goto done; + } + + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "User search for [%s] returned > 1 results!\n", pd->user); + ret = EINVAL; + goto done; + break; + } + + subreq = handle_child_send(state, state->ev, state->kr); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "handle_child_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, krb5_access_done, req); + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, state->ev); + return req; +} + +static void krb5_access_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct krb5_access_state *state = tevent_req_data(req, + struct krb5_access_state); + int ret; + uint8_t *buf = NULL; + ssize_t len = -1; + int32_t msg_status; + + ret = handle_child_recv(subreq, state, &buf, &len); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "child failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + if ((size_t) len != sizeof(int32_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, "message has the wrong size.\n"); + ret = EINVAL; + goto fail; + } + + SAFEALIGN_COPY_INT32(&msg_status, buf, NULL); + + if (msg_status == EOK) { + state->access_allowed = true; + } else { + state->access_allowed = false; + } + + tevent_req_done(req); + return; + +fail: + tevent_req_error(req, ret); + return; +} + +int krb5_access_recv(struct tevent_req *req, bool *access_allowed) +{ + struct krb5_access_state *state = tevent_req_data(req, + struct krb5_access_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *access_allowed = state->access_allowed; + + return EOK; +} diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c new file mode 100644 index 0000000..be34880 --- /dev/null +++ b/src/providers/krb5/krb5_auth.c @@ -0,0 +1,1356 @@ +/* + SSSD + + Kerberos 5 Backend Module + + Authors: + Sumit Bose + + Copyright (C) 2009-2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include +#include +#include +#include + +#include + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/find_uid.h" +#include "util/auth_utils.h" +#include "db/sysdb.h" +#include "util/sss_utf8.h" +#include "util/child_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_utils.h" +#include "providers/krb5/krb5_ccache.h" + +#define NON_POSIX_CCNAME_FMT "MEMORY:sssd_nonposix_dummy_%u" + +static int krb5_mod_ccname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + const char *ccname, + int mod_op) +{ + TALLOC_CTX *tmpctx; + struct sysdb_attrs *attrs; + int ret; + errno_t sret; + bool in_transaction = false; + + if (name == NULL || ccname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user or ccache name.\n"); + return EINVAL; + } + + if (mod_op != SYSDB_MOD_REP && mod_op != SYSDB_MOD_DEL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported operation [%d].\n", mod_op); + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_ALL, "%s ccname [%s] for user [%s].\n", + mod_op == SYSDB_MOD_REP ? "Save" : "Delete", ccname, name); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + attrs = sysdb_new_attrs(tmpctx); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(attrs, SYSDB_CCACHE_FILE, ccname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_add_string failed.\n"); + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error %d starting transaction (%s)\n", ret, strerror(ret)); + goto done; + } + in_transaction = true; + + ret = sysdb_set_user_attr(domain, name, attrs, mod_op); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_zfree(tmpctx); + return ret; +} + +static int krb5_save_ccname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + const char *ccname) +{ + return krb5_mod_ccname(mem_ctx, sysdb, domain, name, ccname, + SYSDB_MOD_REP); +} + +static int krb5_delete_ccname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + const char *ccname) +{ + return krb5_mod_ccname(mem_ctx, sysdb, domain, name, ccname, + SYSDB_MOD_DEL); +} + +static int krb5_cleanup(void *ptr) +{ + struct krb5child_req *kr = talloc_get_type(ptr, struct krb5child_req); + + if (kr == NULL) return EOK; + + memset(kr, 0, sizeof(struct krb5child_req)); + + return EOK; +} + +static errno_t +get_krb_primary(struct map_id_name_to_krb_primary *name_to_primary, + char *id_prov_name, bool cs, const char **_krb_primary) +{ + errno_t ret; + int i = 0; + + while(name_to_primary != NULL && + name_to_primary[i].id_name != NULL && + name_to_primary[i].krb_primary != NULL) { + + if (sss_string_equal(cs, name_to_primary[i].id_name, id_prov_name)) { + *_krb_primary = name_to_primary[i].krb_primary; + ret = EOK; + goto done; + } + i++; + } + + /* Handle also the case of name_to_primary being NULL */ + ret = ENOENT; + +done: + return ret; +} + +errno_t krb5_setup(TALLOC_CTX *mem_ctx, + struct pam_data *pd, + struct sss_domain_info *dom, + struct krb5_ctx *krb5_ctx, + struct krb5child_req **_krb5_req) +{ + struct krb5child_req *kr; + const char *mapped_name; + TALLOC_CTX *tmp_ctx; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + kr = talloc_zero(tmp_ctx, struct krb5child_req); + if (kr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + kr->is_offline = false; + talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup); + + kr->pd = pd; + kr->dom = dom; + kr->krb5_ctx = krb5_ctx; + + ret = get_krb_primary(krb5_ctx->name_to_primary, + pd->user, dom->case_sensitive, &mapped_name); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Setting mapped name to: %s\n", mapped_name); + kr->user = mapped_name; + + kr->kuserok_user = sss_output_name(kr, kr->user, + dom->case_sensitive, 0); + if (kr->kuserok_user == NULL) { + ret = ENOMEM; + goto done; + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "No mapping for: %s\n", pd->user); + kr->user = pd->user; + + kr->kuserok_user = sss_output_name(kr, kr->user, + dom->case_sensitive, 0); + if (kr->kuserok_user == NULL) { + ret = ENOMEM; + goto done; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "get_krb_primary failed - %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *_krb5_req = talloc_steal(mem_ctx, kr); + } + talloc_free(tmp_ctx); + return ret; +} + + +static void krb5_auth_cache_creds(struct krb5_ctx *krb5_ctx, + struct sss_domain_info *domain, + struct confdb_ctx *cdb, + struct pam_data *pd, uid_t uid, + int *pam_status, int *dp_err) +{ + const char *password = NULL; + errno_t ret; + + ret = sss_authtok_get_password(pd->authtok, &password, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get password [%d] %s. Delayed authentication is only " + "available for password authentication (single factor).\n", + ret, strerror(ret)); + *pam_status = PAM_SYSTEM_ERR; + *dp_err = DP_ERR_OK; + return; + } + + ret = sysdb_cache_auth(domain, pd->user, + password, cdb, true, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Offline authentication failed\n"); + *pam_status = cached_login_pam_status(ret); + *dp_err = DP_ERR_OK; + return; + } + + ret = add_user_to_delayed_online_authentication(krb5_ctx, domain, pd, uid); + if (ret == ENOTSUP) { + /* This error is not fatal */ + DEBUG(SSSDBG_MINOR_FAILURE, "Delayed authentication not supported\n"); + } else if (ret != EOK) { + /* This error is not fatal */ + DEBUG(SSSDBG_CRIT_FAILURE, + "add_user_to_delayed_online_authentication failed.\n"); + } + *pam_status = PAM_AUTHINFO_UNAVAIL; + *dp_err = DP_ERR_OFFLINE; +} + +static errno_t krb5_auth_prepare_ccache_name(struct krb5child_req *kr, + struct ldb_message *user_msg, + struct be_ctx *be_ctx) +{ + const char *ccname_template; + + switch (kr->dom->type) { + case DOM_TYPE_POSIX: + ccname_template = dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_CCNAME_TMPL); + + kr->ccname = expand_ccname_template(kr, kr, ccname_template, + kr->krb5_ctx->illegal_path_re, true, + be_ctx->domain->case_sensitive); + if (kr->ccname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "expand_ccname_template failed.\n"); + return ENOMEM; + } + + kr->old_ccname = ldb_msg_find_attr_as_string(user_msg, + SYSDB_CCACHE_FILE, NULL); + if (kr->old_ccname == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, + "No ccache file for user [%s] found.\n", kr->pd->user); + } + break; + case DOM_TYPE_APPLICATION: + DEBUG(SSSDBG_TRACE_FUNC, + "Domain type application, will use in-memory ccache\n"); + kr->ccname = talloc_asprintf(kr, + NON_POSIX_CCNAME_FMT, + sss_rand() % UINT_MAX); + if (kr->ccname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported domain type\n"); + return EINVAL; + } + + return EOK; +} + +static void krb5_auth_store_creds(struct sss_domain_info *domain, + struct pam_data *pd) +{ + const char *password = NULL; + const char *fa2; + size_t password_len; + size_t fa2_len = 0; + int ret = EOK; + + switch(pd->cmd) { + case SSS_CMD_RENEW: + /* The authtok is set to the credential cache + * during renewal. We don't want to save this + * as the cached password. + */ + break; + case SSS_PAM_PREAUTH: + /* There are no credentials available during pre-authentication, + * nothing to do. */ + break; + case SSS_PAM_AUTHENTICATE: + case SSS_PAM_CHAUTHTOK_PRELIM: + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_2FA) { + ret = sss_authtok_get_2fa(pd->authtok, &password, &password_len, + &fa2, &fa2_len); + if (ret == EOK && password_len < + domain->cache_credentials_min_ff_length) { + DEBUG(SSSDBG_FATAL_FAILURE, + "First factor is too short to be cache, " + "minimum length is [%u].\n", + domain->cache_credentials_min_ff_length); + ret = EINVAL; + } + } else if (sss_authtok_get_type(pd->authtok) == + SSS_AUTHTOK_TYPE_PASSWORD) { + ret = sss_authtok_get_password(pd->authtok, &password, NULL); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot cache authtok type [%d].\n", + sss_authtok_get_type(pd->authtok)); + ret = EINVAL; + } + break; + case SSS_PAM_CHAUTHTOK: + ret = sss_authtok_get_password(pd->newauthtok, &password, NULL); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, + "unsupported PAM command [%d].\n", pd->cmd); + } + + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get password [%d] %s\n", ret, strerror(ret)); + /* password caching failures are not fatal errors */ + return; + } + + if (password == NULL) { + if (pd->cmd != SSS_CMD_RENEW && pd->cmd != SSS_PAM_PREAUTH) { + DEBUG(SSSDBG_FATAL_FAILURE, + "password not available, offline auth may not work.\n"); + /* password caching failures are not fatal errors */ + } + return; + } + + ret = sysdb_cache_password_ex(domain, pd->user, password, + sss_authtok_get_type(pd->authtok), fa2_len); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to cache password, offline auth may not work." + " (%d)[%s]!?\n", ret, strerror(ret)); + /* password caching failures are not fatal errors */ + } +} + +static bool is_otp_enabled(struct ldb_message *user_msg) +{ + struct ldb_message_element *el; + size_t i; + + el = ldb_msg_find_element(user_msg, SYSDB_AUTH_TYPE); + if (el == NULL) { + return false; + } + + for (i = 0; i < el->num_values; i++) { + if (strcmp((const char * )el->values[i].data, "otp") == 0) { + return true; + } + } + + return false; +} + +/* krb5_auth request */ + +struct krb5_auth_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct pam_data *pd; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct krb5_ctx *krb5_ctx; + struct krb5child_req *kr; + + bool search_kpasswd; + + int pam_status; + int dp_err; +}; + +static void krb5_auth_resolve_done(struct tevent_req *subreq); +static void krb5_auth_done(struct tevent_req *subreq); + +struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct pam_data *pd, + struct krb5_ctx *krb5_ctx) +{ + const char **attrs; + struct krb5_auth_state *state; + struct ldb_result *res; + struct krb5child_req *kr = NULL; + const char *realm; + struct tevent_req *req; + struct tevent_req *subreq; + enum sss_authtok_type authtok_type; + int ret; + bool otp; + + req = tevent_req_create(mem_ctx, &state, struct krb5_auth_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->pd = pd; + state->krb5_ctx = krb5_ctx; + state->kr = NULL; + state->pam_status = PAM_SYSTEM_ERR; + state->dp_err = DP_ERR_FATAL; + + ret = get_domain_or_subdomain(be_ctx, pd->domain, &state->domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_domain_or_subdomain failed.\n"); + goto done; + } + + state->sysdb = state->domain->sysdb; + + authtok_type = sss_authtok_get_type(pd->authtok); + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + case SSS_PAM_CHAUTHTOK: + if (authtok_type != SSS_AUTHTOK_TYPE_PASSWORD + && authtok_type != SSS_AUTHTOK_TYPE_2FA + && authtok_type != SSS_AUTHTOK_TYPE_2FA_SINGLE + && authtok_type != SSS_AUTHTOK_TYPE_SC_PIN + && authtok_type != SSS_AUTHTOK_TYPE_SC_KEYPAD + && authtok_type != SSS_AUTHTOK_TYPE_OAUTH2 + && authtok_type != SSS_AUTHTOK_TYPE_PASSKEY + && authtok_type != SSS_AUTHTOK_TYPE_PASSKEY_KRB + && authtok_type != SSS_AUTHTOK_TYPE_PASSKEY_REPLY) { + /* handle empty password gracefully */ + if (authtok_type == SSS_AUTHTOK_TYPE_EMPTY) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Illegal empty authtok for user [%s]\n", + pd->user); + state->pam_status = PAM_AUTH_ERR; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_CRIT_FAILURE, + "Wrong authtok type for user [%s]. " \ + "Expected [%d], got [%d]\n", pd->user, + SSS_AUTHTOK_TYPE_PASSWORD, + authtok_type); + state->pam_status = PAM_SYSTEM_ERR; + state->dp_err = DP_ERR_FATAL; + ret = EINVAL; + goto done; + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + if (pd->priv == 1 && + authtok_type != SSS_AUTHTOK_TYPE_PASSWORD) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Password reset by root is not supported.\n"); + state->pam_status = PAM_PERM_DENIED; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + } + break; + case SSS_CMD_RENEW: + if (authtok_type != SSS_AUTHTOK_TYPE_CCFILE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Wrong authtok type for user [%s]. " \ + "Expected [%d], got [%d]\n", pd->user, + SSS_AUTHTOK_TYPE_CCFILE, + authtok_type); + state->pam_status = PAM_SYSTEM_ERR; + state->dp_err = DP_ERR_FATAL; + ret = EINVAL; + goto done; + } + break; + case SSS_PAM_PREAUTH: + break; + default: + DEBUG(SSSDBG_CONF_SETTINGS, "Unexpected pam task %d.\n", pd->cmd); + state->pam_status = PAM_SYSTEM_ERR; + state->dp_err = DP_ERR_FATAL; + ret = EINVAL; + goto done; + } + + if (be_is_offline(be_ctx) && + (pd->cmd == SSS_PAM_CHAUTHTOK || pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM || + pd->cmd == SSS_CMD_RENEW)) { + DEBUG(SSSDBG_TRACE_ALL, + "Password changes and ticket renewal are not possible " + "while offline.\n"); + state->pam_status = PAM_AUTHINFO_UNAVAIL; + state->dp_err = DP_ERR_OFFLINE; + ret = EOK; + goto done; + } + + attrs = talloc_array(state, const char *, 8); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + attrs[0] = SYSDB_UPN; + attrs[1] = SYSDB_HOMEDIR; + attrs[2] = SYSDB_CCACHE_FILE; + attrs[3] = SYSDB_UIDNUM; + attrs[4] = SYSDB_GIDNUM; + attrs[5] = SYSDB_CANONICAL_UPN; + attrs[6] = SYSDB_AUTH_TYPE; + attrs[7] = NULL; + + ret = krb5_setup(state, pd, state->domain, krb5_ctx, + &state->kr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_setup failed.\n"); + goto done; + } + kr = state->kr; + + ret = sysdb_get_user_attr_with_views(state, state->domain, state->pd->user, + attrs, &res); + if (ret) { + DEBUG(SSSDBG_FUNC_DATA, + "sysdb search for upn of user [%s] failed.\n", pd->user); + state->pam_status = PAM_SYSTEM_ERR; + state->dp_err = DP_ERR_OK; + goto done; + } + + realm = dp_opt_get_cstring(krb5_ctx->opts, KRB5_REALM); + if (realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing Kerberos realm.\n"); + ret = ENOENT; + goto done; + } + + switch (res->count) { + case 0: + DEBUG(SSSDBG_FUNC_DATA, + "No attributes for user [%s] found.\n", pd->user); + ret = ENOENT; + goto done; + break; + + case 1: + ret = find_or_guess_upn(state, res->msgs[0], krb5_ctx, be_ctx->domain, + kr->user, pd->domain, &kr->upn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "find_or_guess_upn failed.\n"); + goto done; + } + + ret = compare_principal_realm(kr->upn, realm, + &kr->upn_from_different_realm); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "compare_principal_realm failed.\n"); + goto done; + } + + kr->homedir = sss_view_ldb_msg_find_attr_as_string(state->domain, + res->msgs[0], + SYSDB_HOMEDIR, + NULL); + if (kr->homedir == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Home directory for user [%s] not known.\n", pd->user); + } + + kr->uid = sss_view_ldb_msg_find_attr_as_uint64(state->domain, + res->msgs[0], + SYSDB_UIDNUM, 0); + if (kr->uid == 0 && state->domain->type == DOM_TYPE_POSIX) { + DEBUG(SSSDBG_CONF_SETTINGS, + "UID for user [%s] not known.\n", pd->user); + ret = ENOENT; + goto done; + } + + kr->gid = sss_view_ldb_msg_find_attr_as_uint64(state->domain, + res->msgs[0], + SYSDB_GIDNUM, 0); + if (kr->gid == 0 && state->domain->type == DOM_TYPE_POSIX) { + DEBUG(SSSDBG_CONF_SETTINGS, + "GID for user [%s] not known.\n", pd->user); + ret = ENOENT; + goto done; + } + + ret = krb5_auth_prepare_ccache_name(kr, res->msgs[0], state->be_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot prepare ccache names!\n"); + goto done; + } + break; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "User search for (%s) returned > 1 results!\n", pd->user); + ret = EINVAL; + goto done; + break; + } + + otp = is_otp_enabled(res->msgs[0]); + if (pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM && otp == true) { + /* To avoid consuming the OTP */ + DEBUG(SSSDBG_TRACE_FUNC, + "Skipping password checks for OTP-enabled user\n"); + state->pam_status = PAM_SUCCESS; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + } + + kr->srv = NULL; + kr->kpasswd_srv = NULL; + + state->search_kpasswd = false; + subreq = be_resolve_server_send(state, state->ev, state->be_ctx, + state->krb5_ctx->service->name, + state->kr->srv == NULL ? true : false); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed resolver request.\n"); + ret = EIO; + goto done; + } + tevent_req_set_callback(subreq, krb5_auth_resolve_done, req); + + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, state->ev); + return req; +} + +static void krb5_auth_resolve_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state); + struct krb5child_req *kr = state->kr; + int ret; + + if (!state->search_kpasswd) { + ret = be_resolve_server_recv(subreq, kr, &kr->srv); + } else { + ret = be_resolve_server_recv(subreq, kr, &kr->kpasswd_srv); + } + talloc_zfree(subreq); + + if (state->search_kpasswd) { + if ((ret != EOK) && + (kr->pd->cmd == SSS_PAM_CHAUTHTOK || + kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) { + /* all kpasswd servers have been tried and none was found good, + * but the kdc seems ok. Password changes are not possible but + * authentication is. We return an PAM error here, but do not + * mark the backend offline. */ + state->pam_status = PAM_AUTHTOK_LOCK_BUSY; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + } + } else { + if (ret != EOK) { + /* all servers have been tried and none + * was found good, setting offline, + * but we still have to call the child to setup + * the ccache file if we are performing auth */ + be_mark_dom_offline(state->domain, state->be_ctx); + kr->is_offline = true; + + if (kr->pd->cmd == SSS_PAM_CHAUTHTOK || + kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { + DEBUG(SSSDBG_TRACE_FUNC, + "No KDC suitable for password change is available\n"); + state->pam_status = PAM_AUTHTOK_LOCK_BUSY; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + } + } else { + if (kr->krb5_ctx->kpasswd_service != NULL) { + state->search_kpasswd = true; + subreq = be_resolve_server_send(state, + state->ev, state->be_ctx, + state->krb5_ctx->kpasswd_service->name, + kr->kpasswd_srv == NULL ? true : false); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Resolver request failed.\n"); + ret = EIO; + goto done; + } + tevent_req_set_callback(subreq, krb5_auth_resolve_done, req); + return; + } + } + } + + if (!kr->is_offline) { + kr->is_offline = be_is_offline(state->be_ctx); + } + + if (!kr->is_offline + && sss_domain_get_state(state->domain) == DOM_INACTIVE) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Subdomain %s is inactive, will proceed offline\n", + state->domain->name); + kr->is_offline = true; + } + + if (kr->is_offline + && sss_krb5_realm_has_proxy(dp_opt_get_cstring(kr->krb5_ctx->opts, + KRB5_REALM))) { + DEBUG(SSSDBG_TRACE_FUNC, + "Resetting offline status, KDC proxy is in use\n"); + kr->is_offline = false; + } + + subreq = handle_child_send(state, state->ev, kr); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "handle_child_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, krb5_auth_done, req); + return; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static void krb5_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state); + struct krb5child_req *kr = state->kr; + struct pam_data *pd = state->pd; + int ret; + uint8_t *buf = NULL; + ssize_t len = -1; + struct krb5_child_response *res; + struct fo_server *search_srv; + krb5_deltat renew_interval_delta; + char *renew_interval_str; + time_t renew_interval_time = 0; + bool use_enterprise_principal; + bool canonicalize; + + ret = handle_child_recv(subreq, pd, &buf, &len); + talloc_zfree(subreq); + if (ret == ETIMEDOUT) { + + DEBUG(SSSDBG_CRIT_FAILURE, "child timed out!\n"); + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + case SSS_CMD_RENEW: + state->search_kpasswd = false; + search_srv = kr->srv; + break; + case SSS_PAM_CHAUTHTOK: + case SSS_PAM_CHAUTHTOK_PRELIM: + if (state->kr->kpasswd_srv) { + state->search_kpasswd = true; + search_srv = kr->kpasswd_srv; + break; + } else { + state->search_kpasswd = false; + search_srv = kr->srv; + break; + } + case SSS_PAM_PREAUTH: + state->pam_status = PAM_CRED_UNAVAIL; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected PAM task %d\n", pd->cmd); + ret = EINVAL; + goto done; + } + + be_fo_set_port_status(state->be_ctx, state->krb5_ctx->service->name, + search_srv, PORT_NOT_WORKING); + subreq = be_resolve_server_send(state, state->ev, state->be_ctx, + state->krb5_ctx->service->name, + search_srv == NULL ? true : false); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed resolved request.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, krb5_auth_resolve_done, req); + return; + + } else if (ret != EOK) { + + DEBUG(SSSDBG_CRIT_FAILURE, + "child failed (%d [%s])\n", ret, strerror(ret)); + goto done; + } + + /* EOK */ + + ret = parse_krb5_child_response(state, buf, len, pd, + state->be_ctx->domain->pwd_expiration_warning, + &res); + if (ret) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "The krb5_child process returned an error. Please inspect the " + "krb5_child.log file or the journal for more information\n"); + DEBUG(SSSDBG_OP_FAILURE, "Could not parse child response [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + if (res->ccname) { + kr->ccname = talloc_strdup(kr, res->ccname); + if (!kr->ccname) { + ret = ENOMEM; + goto done; + } + } + + use_enterprise_principal = dp_opt_get_bool(kr->krb5_ctx->opts, + KRB5_USE_ENTERPRISE_PRINCIPAL); + canonicalize = dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_CANONICALIZE); + + /* Check if the cases of our upn are correct and update it if needed. + * Fail if the upn differs by more than just the case for non-enterprise + * principals. */ + if (res->correct_upn != NULL && + strcmp(kr->upn, res->correct_upn) != 0) { + if (strcasecmp(kr->upn, res->correct_upn) == 0 || + canonicalize == true || + use_enterprise_principal == true) { + talloc_free(kr->upn); + kr->upn = talloc_strdup(kr, res->correct_upn); + if (kr->upn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = check_if_cached_upn_needs_update(state->sysdb, state->domain, + pd->user, res->correct_upn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "check_if_cached_upn_needs_update failed.\n"); + goto done; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "UPN used in the request [%s] and " \ + "returned UPN [%s] differ by more " \ + "than just the case.\n", + kr->upn, res->correct_upn); + ret = EINVAL; + goto done; + } + } + + /* If the child request failed, but did not return an offline error code, + * return with the status */ + switch (res->msg_status) { + case ERR_OK: + /* If the child request was successful and we run the first pass of the + * change password request just return success. */ + if (pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { + state->pam_status = PAM_SUCCESS; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + } + break; + + case ERR_NETWORK_IO: + if (kr->kpasswd_srv != NULL && + (pd->cmd == SSS_PAM_CHAUTHTOK || + pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) { + /* if using a dedicated kpasswd server for a chpass operation... */ + + be_fo_set_port_status(state->be_ctx, + state->krb5_ctx->kpasswd_service->name, + kr->kpasswd_srv, PORT_NOT_WORKING); + /* ..try to resolve next kpasswd server */ + state->search_kpasswd = true; + subreq = be_resolve_server_send(state, state->ev, state->be_ctx, + state->krb5_ctx->kpasswd_service->name, + state->kr->kpasswd_srv == NULL ? true : false); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Resolver request failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, krb5_auth_resolve_done, req); + return; + } else if (kr->srv != NULL) { + /* failed to use the KDC... */ + be_fo_set_port_status(state->be_ctx, + state->krb5_ctx->service->name, + kr->srv, PORT_NOT_WORKING); + /* ..try to resolve next KDC */ + state->search_kpasswd = false; + subreq = be_resolve_server_send(state, state->ev, state->be_ctx, + state->krb5_ctx->service->name, + kr->srv == NULL ? true : false); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Resolver request failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, krb5_auth_resolve_done, req); + return; + } + break; + + case ERR_CREDS_EXPIRED_CCACHE: + ret = krb5_delete_ccname(state, state->sysdb, state->domain, + pd->user, kr->old_ccname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_delete_ccname failed.\n"); + } + /* FALLTHROUGH */ + SSS_ATTRIBUTE_FALLTHROUGH; + + case ERR_CREDS_EXPIRED: + /* If the password is expired we can safely remove the ccache from the + * cache and disk if it is not actively used anymore. This will allow + * to create a new random ccache if sshd with privilege separation is + * used. */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && !kr->active_ccache) { + if (kr->old_ccname != NULL) { + ret = krb5_delete_ccname(state, state->sysdb, state->domain, + pd->user, kr->old_ccname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_delete_ccname failed.\n"); + } + } + } + + state->pam_status = PAM_NEW_AUTHTOK_REQD; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + + case ERR_CREDS_INVALID: + state->pam_status = PAM_CRED_ERR; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + + case ERR_ACCOUNT_EXPIRED: + state->pam_status = PAM_ACCT_EXPIRED; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + + case ERR_ACCOUNT_LOCKED: + state->pam_status = PAM_PERM_DENIED; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + + case ERR_NO_CREDS: + state->pam_status = PAM_CRED_UNAVAIL; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + + case ERR_AUTH_FAILED: + state->pam_status = PAM_AUTH_ERR; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + + case ERR_CHPASS_FAILED: + state->pam_status = PAM_AUTHTOK_ERR; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + + case ERR_NO_AUTH_METHOD_AVAILABLE: + state->pam_status = PAM_NO_MODULE_DATA; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + + default: + DEBUG(SSSDBG_IMPORTANT_INFO, + "The krb5_child process returned an error. Please inspect the " + "krb5_child.log file or the journal for more information\n"); + state->pam_status = PAM_SYSTEM_ERR; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + } + + if (kr->kpasswd_srv != NULL && + (pd->cmd == SSS_PAM_CHAUTHTOK || + pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) { + /* found a dedicated kpasswd server for a chpass operation */ + be_fo_set_port_status(state->be_ctx, + state->krb5_ctx->service->name, + kr->kpasswd_srv, PORT_WORKING); + } else if (kr->srv != NULL) { + /* found a KDC */ + be_fo_set_port_status(state->be_ctx, state->krb5_ctx->service->name, + kr->srv, PORT_WORKING); + } + + if (pd->cmd == SSS_PAM_PREAUTH) { + state->pam_status = PAM_SUCCESS; + state->dp_err = DP_ERR_OK; + ret = EOK; + goto done; + } + + /* Now only a successful authentication or password change is left. + * + * We expect that one of the messages in the received buffer contains + * the name of the credential cache file. */ + if (kr->ccname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing ccache name in child response.\n"); + ret = EINVAL; + goto done; + } + + ret = krb5_save_ccname(state, state->sysdb, state->domain, + pd->user, kr->ccname); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_save_ccname failed.\n"); + goto done; + } + renew_interval_str = dp_opt_get_string(kr->krb5_ctx->opts, + KRB5_RENEW_INTERVAL); + if (renew_interval_str != NULL) { + ret = krb5_string_to_deltat(renew_interval_str, &renew_interval_delta); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Reading krb5_renew_interval failed.\n"); + renew_interval_delta = 0; + } + renew_interval_time = renew_interval_delta; + } + if (res->msg_status == ERR_OK && renew_interval_time > 0 && + (pd->cmd == SSS_PAM_AUTHENTICATE || + pd->cmd == SSS_CMD_RENEW || + pd->cmd == SSS_PAM_CHAUTHTOK) && + (res->tgtt.renew_till > res->tgtt.endtime) && + (kr->ccname != NULL)) { + DEBUG(SSSDBG_TRACE_LIBS, + "Adding [%s] for automatic renewal.\n", kr->ccname); + ret = add_tgt_to_renew_table(kr->krb5_ctx, kr->ccname, &(res->tgtt), + pd, kr->upn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_tgt_to_renew_table failed, " + "automatic renewal not possible.\n"); + } + } + + if (kr->is_offline) { + if (dp_opt_get_bool(kr->krb5_ctx->opts, + KRB5_STORE_PASSWORD_IF_OFFLINE) + && sss_authtok_get_type(pd->authtok) + == SSS_AUTHTOK_TYPE_PASSWORD) { + krb5_auth_cache_creds(state->kr->krb5_ctx, + state->domain, + state->be_ctx->cdb, + state->pd, state->kr->uid, + &state->pam_status, &state->dp_err); + } else { + DEBUG(SSSDBG_CONF_SETTINGS, + "Backend is marked offline, retry later!\n"); + state->pam_status = PAM_AUTHINFO_UNAVAIL; + state->dp_err = DP_ERR_OFFLINE; + } + ret = EOK; + goto done; + } + + if (state->be_ctx->domain->cache_credentials == TRUE + && (!res->otp + || (res->otp && sss_authtok_get_type(pd->authtok) == + SSS_AUTHTOK_TYPE_2FA))) { + krb5_auth_store_creds(state->domain, pd); + } + + /* The SSS_OTP message will prevent pam_sss from putting the entered + * password on the PAM stack for other modules to use. This is not needed + * when both factors were entered separately because here the first factor + * (long term password) can be passed to the other modules. */ + if (res->otp == true && pd->cmd == SSS_PAM_AUTHENTICATE + && sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_2FA) { + uint32_t otp_flag = 1; + ret = pam_add_response(pd, SSS_OTP, sizeof(uint32_t), + (const uint8_t *) &otp_flag); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pam_add_response failed: %d (%s).\n", + ret, sss_strerror(ret)); + state->pam_status = PAM_SYSTEM_ERR; + state->dp_err = DP_ERR_OK; + goto done; + } + } + + state->pam_status = PAM_SUCCESS; + state->dp_err = DP_ERR_OK; + ret = EOK; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + +} + +int krb5_auth_recv(struct tevent_req *req, int *pam_status, int *dp_err) +{ + struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state); + *pam_status = state->pam_status; + *dp_err = state->dp_err; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct krb5_pam_handler_state { + struct pam_data *pd; +}; + +static void krb5_pam_handler_auth_done(struct tevent_req *subreq); +static void krb5_pam_handler_access_done(struct tevent_req *subreq); + +struct tevent_req * +krb5_pam_handler_send(TALLOC_CTX *mem_ctx, + struct krb5_ctx *krb5_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct krb5_pam_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct krb5_pam_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + case SSS_PAM_PREAUTH: + case SSS_CMD_RENEW: + case SSS_PAM_CHAUTHTOK_PRELIM: + case SSS_PAM_CHAUTHTOK: + subreq = krb5_auth_queue_send(state, params->ev, params->be_ctx, + pd, krb5_ctx); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n"); + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, krb5_pam_handler_auth_done, req); + break; + case SSS_PAM_ACCT_MGMT: + subreq = krb5_access_send(state, params->ev, params->be_ctx, + pd, krb5_ctx); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_access_send failed.\n"); + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, krb5_pam_handler_access_done, req); + break; + case SSS_PAM_SETCRED: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + pd->pam_status = PAM_SUCCESS; + goto immediately; + break; + default: + DEBUG(SSSDBG_CONF_SETTINGS, + "krb5 does not handles pam task %d.\n", pd->cmd); + pd->pam_status = PAM_MODULE_UNKNOWN; + goto immediately; + } + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void krb5_pam_handler_auth_done(struct tevent_req *subreq) +{ + struct krb5_pam_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct krb5_pam_handler_state); + + ret = krb5_auth_queue_recv(subreq, &state->pd->pam_status, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + state->pd->pam_status = PAM_SYSTEM_ERR; + } + + /* PAM_CRED_ERR is used to indicate to the IPA provider that trying + * password migration would make sense. From this point on it isn't + * necessary to keep this status, so it can be translated to PAM_AUTH_ERR. + */ + if (state->pd->pam_status == PAM_CRED_ERR) { + state->pd->pam_status = PAM_AUTH_ERR; + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void krb5_pam_handler_access_done(struct tevent_req *subreq) +{ + struct krb5_pam_handler_state *state; + struct tevent_req *req; + bool access_allowed; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct krb5_pam_handler_state); + + ret = krb5_access_recv(subreq, &access_allowed); + talloc_zfree(subreq); + if (ret != EOK) { + state->pd->pam_status = PAM_SYSTEM_ERR; + } + + + DEBUG(SSSDBG_TRACE_LIBS, "Access %s for user [%s].\n", + access_allowed ? "allowed" : "denied", state->pd->user); + state->pd->pam_status = access_allowed ? PAM_SUCCESS : PAM_PERM_DENIED; + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +krb5_pam_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct krb5_pam_handler_state *state = NULL; + + state = tevent_req_data(req, struct krb5_pam_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/krb5/krb5_auth.h b/src/providers/krb5/krb5_auth.h new file mode 100644 index 0000000..bbdbf61 --- /dev/null +++ b/src/providers/krb5/krb5_auth.h @@ -0,0 +1,158 @@ +/* + SSSD + + Kerberos Backend, private header file + + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + + + 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 3 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; 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, see . +*/ + +#ifndef __KRB5_AUTH_H__ +#define __KRB5_AUTH_H__ + + +#include "util/sss_regexp.h" +#include "util/sss_krb5.h" +#include "providers/backend.h" +#include "util/child_common.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_ccache.h" + +#define CCACHE_ENV_NAME "KRB5CCNAME" + +#define ILLEGAL_PATH_PATTERN "//|/\\./|/\\.\\./" + +#define CHILD_OPT_FAST_CCACHE_UID "fast-ccache-uid" +#define CHILD_OPT_FAST_CCACHE_GID "fast-ccache-gid" +#define CHILD_OPT_FAST_USE_ANONYMOUS_PKINIT "fast-use-anonymous-pkinit" +#define CHILD_OPT_REALM "realm" +#define CHILD_OPT_LIFETIME "lifetime" +#define CHILD_OPT_RENEWABLE_LIFETIME "renewable-lifetime" +#define CHILD_OPT_USE_FAST "use-fast" +#define CHILD_OPT_FAST_PRINCIPAL "fast-principal" +#define CHILD_OPT_CANONICALIZE "canonicalize" +#define CHILD_OPT_SSS_CREDS_PASSWORD "sss-creds-password" +#define CHILD_OPT_CHAIN_ID "chain-id" +#define CHILD_OPT_CHECK_PAC "check-pac" + +struct krb5child_req { + struct pam_data *pd; + struct krb5_ctx *krb5_ctx; + struct sss_domain_info *dom; + + const char *ccname; + const char *old_ccname; + const char *homedir; + char *upn; + uid_t uid; + gid_t gid; + bool is_offline; + struct fo_server *srv; + struct fo_server *kpasswd_srv; + bool active_ccache; + bool valid_tgt; + bool upn_from_different_realm; + bool send_pac; + + const char *user; + const char *kuserok_user; +}; + +errno_t krb5_setup(TALLOC_CTX *mem_ctx, + struct pam_data *pd, + struct sss_domain_info *dom, + struct krb5_ctx *krb5_ctx, + struct krb5child_req **_krb5_req); + +struct tevent_req * +krb5_pam_handler_send(TALLOC_CTX *mem_ctx, + struct krb5_ctx *krb5_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +krb5_pam_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +/* Please use krb5_auth_send/recv *only* if you're certain there can't + * be concurrent logins happening. With some ccache back ends, the ccache + * files might clobber one another. Please use krb5_auth_queue_send() + * instead that queues the requests + */ +struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct pam_data *pd, + struct krb5_ctx *krb5_ctx); +int krb5_auth_recv(struct tevent_req *req, int *pam_status, int *dp_err); + +struct tevent_req *handle_child_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct krb5child_req *kr); +int handle_child_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint8_t **buf, ssize_t *len); + +struct krb5_child_response { + int32_t msg_status; + struct tgt_times tgtt; + char *ccname; + char *correct_upn; + bool otp; +}; + +errno_t +parse_krb5_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf, ssize_t len, + struct pam_data *pd, int pwd_exp_warning, + struct krb5_child_response **_res); + +errno_t add_user_to_delayed_online_authentication(struct krb5_ctx *krb5_ctx, + struct sss_domain_info *domain, + struct pam_data *pd, + uid_t uid); +errno_t init_delayed_online_authentication(struct krb5_ctx *krb5_ctx, + struct be_ctx *be_ctx, + struct tevent_context *ev); + +errno_t init_renew_tgt(struct krb5_ctx *krb5_ctx, struct be_ctx *be_ctx, + struct tevent_context *ev, time_t renew_intv); +errno_t add_tgt_to_renew_table(struct krb5_ctx *krb5_ctx, const char *ccfile, + struct tgt_times *tgtt, struct pam_data *pd, + const char *upn); + +/* krb5_access.c */ +struct tevent_req *krb5_access_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct pam_data *pd, + struct krb5_ctx *krb5_ctx); +int krb5_access_recv(struct tevent_req *req, bool *access_allowed); + +/* krb5_wait_queue.c */ +struct tevent_req *krb5_auth_queue_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct pam_data *pd, + struct krb5_ctx *krb5_ctx); + +int krb5_auth_queue_recv(struct tevent_req *req, + int *_pam_status, + int *_dp_err); + +#endif /* __KRB5_AUTH_H__ */ diff --git a/src/providers/krb5/krb5_ccache.c b/src/providers/krb5/krb5_ccache.c new file mode 100644 index 0000000..88f75a8 --- /dev/null +++ b/src/providers/krb5/krb5_ccache.c @@ -0,0 +1,796 @@ +/* + SSSD + + Kerberos 5 Backend Module -- ccache related utilities + + Authors: + Sumit Bose + Jakub Hrozek + + Copyright (C) 2014 Red Hat + + 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 3 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; 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, see . +*/ + +#ifdef HAVE_KRB5_KRB5_H +#include +#else +#include +#endif + +#include "providers/krb5/krb5_ccache.h" +#include "util/sss_krb5.h" +#include "util/util.h" + +struct string_list { + struct string_list *next; + struct string_list *prev; + char *s; +}; + +static errno_t find_ccdir_parent_data(TALLOC_CTX *mem_ctx, + const char *ccdirname, + struct stat *parent_stat, + struct string_list **missing_parents) +{ + int ret = EFAULT; + char *parent = NULL; + char *end; + struct string_list *li; + + ret = stat(ccdirname, parent_stat); + if (ret == EOK) { + if ( !S_ISDIR(parent_stat->st_mode) ) { + DEBUG(SSSDBG_MINOR_FAILURE, + "[%s] is not a directory.\n", ccdirname); + return EINVAL; + } + return EOK; + } else { + if (errno != ENOENT) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "stat for [%s] failed: [%d][%s].\n", ccdirname, ret, + strerror(ret)); + return ret; + } + } + + li = talloc_zero(mem_ctx, struct string_list); + if (li == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_zero failed.\n"); + return ENOMEM; + } + + li->s = talloc_strdup(li, ccdirname); + if (li->s == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_strdup failed.\n"); + return ENOMEM; + } + + DLIST_ADD(*missing_parents, li); + + parent = talloc_strdup(mem_ctx, ccdirname); + if (parent == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_strdup failed.\n"); + return ENOMEM; + } + + /* We'll remove all trailing slashes from the back so that + * we only pass /some/path to find_ccdir_parent_data, not + * /some/path */ + do { + end = strrchr(parent, '/'); + if (end == NULL || end == parent) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot find parent directory of [%s], / is not allowed.\n", + ccdirname); + ret = EINVAL; + goto done; + } + *end = '\0'; + } while (*(end+1) == '\0'); + + ret = find_ccdir_parent_data(mem_ctx, parent, parent_stat, missing_parents); + +done: + talloc_free(parent); + return ret; +} + +static errno_t check_parent_stat(struct stat *parent_stat, uid_t uid) +{ + if (parent_stat->st_uid != 0 && parent_stat->st_uid != uid) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Private directory can only be created below a directory " + "belonging to root or to [%"SPRIuid"].\n", uid); + return EINVAL; + } + + if (parent_stat->st_uid == uid) { + if (!(parent_stat->st_mode & S_IXUSR)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Parent directory does not have the search bit set for " + "the owner.\n"); + return EINVAL; + } + } else { + if (!(parent_stat->st_mode & S_IXOTH)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Parent directory does not have the search bit set for " + "others.\n"); + return EINVAL; + } + } + + return EOK; +} + +static errno_t create_ccache_dir(const char *ccdirname, uid_t uid, gid_t gid) +{ + int ret = EFAULT; + struct stat parent_stat; + struct string_list *missing_parents = NULL; + struct string_list *li = NULL; + mode_t old_umask; + mode_t new_dir_mode; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_new failed.\n"); + return ENOMEM; + } + + if (*ccdirname != '/') { + DEBUG(SSSDBG_MINOR_FAILURE, + "Only absolute paths are allowed, not [%s] .\n", ccdirname); + ret = EINVAL; + goto done; + } + + ret = find_ccdir_parent_data(tmp_ctx, ccdirname, &parent_stat, + &missing_parents); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "find_ccdir_parent_data failed.\n"); + goto done; + } + + ret = check_parent_stat(&parent_stat, uid); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Check the ownership and permissions of krb5_ccachedir: [%s].\n", + ccdirname); + goto done; + } + + DLIST_FOR_EACH(li, missing_parents) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Creating directory [%s].\n", li->s); + new_dir_mode = 0700; + + old_umask = umask(0000); + ret = mkdir(li->s, new_dir_mode); + umask(old_umask); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "mkdir [%s] failed: [%d][%s].\n", li->s, ret, + strerror(ret)); + goto done; + } + ret = chown(li->s, uid, gid); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "chown failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_krb5_precreate_ccache(const char *ccname, uid_t uid, gid_t gid) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *filename; + char *ccdirname; + char *end; + errno_t ret; + + if (ccname[0] == '/') { + filename = ccname; + } else if (strncmp(ccname, "FILE:", 5) == 0) { + filename = ccname + 5; + } else if (strncmp(ccname, "DIR:", 4) == 0) { + filename = ccname + 4; + } else { + /* only FILE and DIR types need precreation so far, we ignore any + * other type */ + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + ccdirname = talloc_strdup(tmp_ctx, filename); + if (ccdirname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + /* We'll remove all trailing slashes from the back so that + * we only pass /some/path to find_ccdir_parent_data, not + * /some/path/ */ + do { + end = strrchr(ccdirname, '/'); + if (end == NULL || end == ccdirname) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find parent directory of [%s], " + "/ is not allowed.\n", ccdirname); + ret = EINVAL; + goto done; + } + *end = '\0'; + } while (*(end+1) == '\0'); + + ret = create_ccache_dir(ccdirname, uid, gid); +done: + talloc_free(tmp_ctx); + return ret; +} + +struct sss_krb5_ccache { + struct sss_creds *creds; + krb5_context context; + krb5_ccache ccache; +}; + +static int sss_free_krb5_ccache(void *mem) +{ + struct sss_krb5_ccache *cc = talloc_get_type(mem, struct sss_krb5_ccache); + + if (cc->ccache) { + krb5_cc_close(cc->context, cc->ccache); + } + krb5_free_context(cc->context); + restore_creds(cc->creds); + return 0; +} + +static errno_t sss_open_ccache_as_user(TALLOC_CTX *mem_ctx, + const char *ccname, + uid_t uid, gid_t gid, + struct sss_krb5_ccache **ccache) +{ + struct sss_krb5_ccache *cc; + krb5_error_code kerr; + errno_t ret; + + cc = talloc_zero(mem_ctx, struct sss_krb5_ccache); + if (!cc) { + return ENOMEM; + } + talloc_set_destructor((TALLOC_CTX *)cc, sss_free_krb5_ccache); + + ret = switch_creds(cc, uid, gid, 0, NULL, &cc->creds); + if (ret) { + goto done; + } + + kerr = sss_krb5_init_context(&cc->context); + if (kerr) { + ret = EIO; + goto done; + } + + kerr = krb5_cc_resolve(cc->context, ccname, &cc->ccache); + if (kerr == KRB5_FCC_NOFILE || cc->ccache == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "ccache %s is missing or empty\n", ccname); + ret = ERR_NOT_FOUND; + goto done; + } else if (kerr != 0) { + KRB5_DEBUG(SSSDBG_OP_FAILURE, cc->context, kerr); + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_resolve failed.\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = EOK; + +done: + if (ret) { + talloc_free(cc); + } else { + *ccache = cc; + } + return ret; +} + +static errno_t sss_destroy_ccache(struct sss_krb5_ccache *cc) +{ + krb5_error_code kerr; + errno_t ret; + + kerr = krb5_cc_destroy(cc->context, cc->ccache); + if (kerr) { + KRB5_DEBUG(SSSDBG_OP_FAILURE, cc->context, kerr); + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_destroy failed.\n"); + ret = EIO; + } else { + ret = EOK; + } + + /* krb5_cc_destroy frees cc->ccache in all events */ + cc->ccache = NULL; + + return ret; +} + +errno_t sss_krb5_cc_destroy(const char *ccname, uid_t uid, gid_t gid) +{ + struct sss_krb5_ccache *cc = NULL; + TALLOC_CTX *tmp_ctx; + errno_t ret; + + if (ccname == NULL) { + /* nothing to remove */ + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = sss_open_ccache_as_user(tmp_ctx, ccname, uid, gid, &cc); + if (ret) { + goto done; + } + + ret = sss_destroy_ccache(cc); + +done: + talloc_free(tmp_ctx); + return ret; +} + +/* This function is called only as a way to validate that we have the + * right cache */ +errno_t sss_krb5_check_ccache_princ(krb5_context kctx, + const char *ccname, + krb5_principal user_princ) +{ + krb5_ccache kcc = NULL; + krb5_principal ccprinc = NULL; + krb5_error_code kerr; + const char *cc_type; + errno_t ret; + + kerr = krb5_cc_resolve(kctx, ccname, &kcc); + if (kerr) { + ret = ERR_INTERNAL; + goto done; + } + + cc_type = krb5_cc_get_type(kctx, kcc); + + kerr = krb5_cc_get_principal(kctx, kcc, &ccprinc); + if (kerr != 0) { + KRB5_DEBUG(SSSDBG_OP_FAILURE, kctx, kerr); + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_get_principal failed.\n"); + } + + if (ccprinc) { + if (krb5_principal_compare(kctx, user_princ, ccprinc) == TRUE) { + /* found in the primary ccache */ + ret = EOK; + goto done; + } + } + +#ifdef HAVE_KRB5_CC_COLLECTION + + if (krb5_cc_support_switch(kctx, cc_type)) { + + krb5_cc_close(kctx, kcc); + kcc = NULL; + + kerr = krb5_cc_set_default_name(kctx, ccname); + if (kerr != 0) { + KRB5_DEBUG(SSSDBG_MINOR_FAILURE, kctx, kerr); + /* try to continue despite failure */ + } + + kerr = krb5_cc_cache_match(kctx, user_princ, &kcc); + if (kerr == 0) { + ret = EOK; + goto done; + } + KRB5_DEBUG(SSSDBG_TRACE_INTERNAL, kctx, kerr); + } + +#endif /* HAVE_KRB5_CC_COLLECTION */ + + ret = ERR_NOT_FOUND; + +done: + if (ccprinc) { + krb5_free_principal(kctx, ccprinc); + } + if (kcc) { + krb5_cc_close(kctx, kcc); + } + return ret; +} + +static errno_t sss_low_level_path_check(const char *ccname) +{ + const char *filename; + struct stat buf; + int ret; + + if (ccname[0] == '/') { + filename = ccname; + } else if (strncmp(ccname, "FILE:", 5) == 0) { + filename = ccname + 5; + } else if (strncmp(ccname, "DIR:", 4) == 0) { + filename = ccname + 4; + if (filename[0] == ':') filename += 1; + } else { + /* only FILE and DIR types need file checks so far, we ignore any + * other type */ + return EOK; + } + + ret = stat(filename, &buf); + if (ret == -1) return errno; + return EOK; +} + +errno_t sss_krb5_cc_verify_ccache(const char *ccname, uid_t uid, gid_t gid, + const char *realm, const char *principal) +{ + struct sss_krb5_ccache *cc = NULL; + TALLOC_CTX *tmp_ctx = NULL; + krb5_principal tgt_princ = NULL; + krb5_principal princ = NULL; + char *tgt_name; + krb5_creds mcred = { 0 }; + krb5_creds cred = { 0 }; + krb5_error_code kerr; + errno_t ret; + + /* first of all verify if the old ccache file/dir exists as we may be + * trying to verify if an old ccache exists at all. If no file/dir + * exists bail out immediately otherwise a following krb5_cc_resolve() + * call may actually create paths and files we do not want to have + * around */ + ret = sss_low_level_path_check(ccname); + if (ret) { + return ret; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = sss_open_ccache_as_user(tmp_ctx, ccname, uid, gid, &cc); + if (ret) { + goto done; + } + + tgt_name = talloc_asprintf(tmp_ctx, "krbtgt/%s@%s", realm, realm); + if (!tgt_name) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + kerr = krb5_parse_name(cc->context, tgt_name, &tgt_princ); + if (kerr) { + KRB5_DEBUG(SSSDBG_CRIT_FAILURE, cc->context, kerr); + if (kerr == KRB5_PARSE_MALFORMED) ret = EINVAL; + else ret = ERR_INTERNAL; + goto done; + } + + kerr = krb5_parse_name(cc->context, principal, &princ); + if (kerr) { + KRB5_DEBUG(SSSDBG_CRIT_FAILURE, cc->context, kerr); + if (kerr == KRB5_PARSE_MALFORMED) ret = EINVAL; + else ret = ERR_INTERNAL; + goto done; + } + + mcred.client = princ; + mcred.server = tgt_princ; + /* Type krb5_timestamp is a signed 32-bit integer, so we need to convert the + * 64-bit time_t value returned by time(). Just keeping the lower 32 bits + * should be enough as Kerberos seems to be planing on making this time + * unsigned to avoid the Y2K38 problem. + * Please check: + * https://web.mit.edu/kerberos/krb5-latest/doc/appdev/y2038.html + */ + mcred.times.endtime = time(NULL) & 0xFFFFFFFF; + + kerr = krb5_cc_retrieve_cred(cc->context, cc->ccache, + KRB5_TC_MATCH_TIMES, &mcred, &cred); + if (kerr) { + if (kerr == KRB5_CC_NOTFOUND || kerr == KRB5_FCC_NOFILE) { + DEBUG(SSSDBG_TRACE_INTERNAL, "TGT not found or expired.\n"); + ret = EINVAL; + } else { + KRB5_DEBUG(SSSDBG_CRIT_FAILURE, cc->context, kerr); + ret = ERR_INTERNAL; + } + } + krb5_free_cred_contents(cc->context, &cred); + +done: + if (tgt_princ) krb5_free_principal(cc->context, tgt_princ); + if (princ) krb5_free_principal(cc->context, princ); + talloc_free(tmp_ctx); + return ret; +} + +errno_t get_ccache_file_data(const char *ccache_file, const char *client_name, + struct tgt_times *tgtt) +{ + krb5_error_code kerr; + krb5_context ctx = NULL; + krb5_ccache cc = NULL; + krb5_principal client_princ = NULL; + krb5_principal server_princ = NULL; + char *server_name; + krb5_creds mcred; + krb5_creds cred; + const char *realm_name; + int realm_length; + + kerr = sss_krb5_init_context(&ctx); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_init_context failed.\n"); + goto done; + } + + kerr = krb5_parse_name(ctx, client_name, &client_princ); + if (kerr != 0) { + KRB5_DEBUG(SSSDBG_OP_FAILURE, ctx, kerr); + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_parse_name failed.\n"); + goto done; + } + + sss_krb5_princ_realm(ctx, client_princ, &realm_name, &realm_length); + if (realm_length == 0) { + kerr = KRB5KRB_ERR_GENERIC; + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_princ_realm failed.\n"); + goto done; + } + + server_name = talloc_asprintf(NULL, "krbtgt/%.*s@%.*s", + realm_length, realm_name, + realm_length, realm_name); + if (server_name == NULL) { + kerr = KRB5_CC_NOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + goto done; + } + + kerr = krb5_parse_name(ctx, server_name, &server_princ); + talloc_free(server_name); + if (kerr != 0) { + KRB5_DEBUG(SSSDBG_OP_FAILURE, ctx, kerr); + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_parse_name failed.\n"); + goto done; + } + + kerr = krb5_cc_resolve(ctx, ccache_file, &cc); + if (kerr != 0) { + KRB5_DEBUG(SSSDBG_OP_FAILURE, ctx, kerr); + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_resolve failed.\n"); + goto done; + } + + memset(&mcred, 0, sizeof(mcred)); + memset(&cred, 0, sizeof(mcred)); + + mcred.server = server_princ; + mcred.client = client_princ; + + kerr = krb5_cc_retrieve_cred(ctx, cc, 0, &mcred, &cred); + if (kerr != 0) { + KRB5_DEBUG(SSSDBG_OP_FAILURE, ctx, kerr); + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_retrieve_cred failed.\n"); + goto done; + } + + tgtt->authtime = cred.times.authtime; + tgtt->starttime = cred.times.starttime; + tgtt->endtime = cred.times.endtime; + tgtt->renew_till = cred.times.renew_till; + + krb5_free_cred_contents(ctx, &cred); + + kerr = krb5_cc_close(ctx, cc); + cc = NULL; + if (kerr != 0) { + KRB5_DEBUG(SSSDBG_OP_FAILURE, ctx, kerr); + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_close failed.\n"); + goto done; + } + + kerr = 0; + +done: + if (cc != NULL) { + krb5_cc_close(ctx, cc); + } + + if (client_princ != NULL) { + krb5_free_principal(ctx, client_princ); + } + + if (server_princ != NULL) { + krb5_free_principal(ctx, server_princ); + } + + if (ctx != NULL) { + krb5_free_context(ctx); + } + + if (kerr != 0) { + return EIO; + } + + return EOK; +} + +errno_t safe_remove_old_ccache_file(const char *old_ccache, + const char *new_ccache, + uid_t uid, gid_t gid) +{ + if ((old_ccache == new_ccache) + || (old_ccache && new_ccache + && (strcmp(old_ccache, new_ccache) == 0))) { + DEBUG(SSSDBG_TRACE_FUNC, "New and old ccache file are the same, " + "none will be deleted.\n"); + return EOK; + } + + return sss_krb5_cc_destroy(old_ccache, uid, gid); +} + +krb5_error_code copy_ccache_into_memory(TALLOC_CTX *mem_ctx, krb5_context kctx, + const char *ccache_file, + char **_mem_name) +{ + krb5_error_code kerr; + krb5_ccache ccache; + krb5_ccache mem_ccache = NULL; + char *ccache_name = NULL; + krb5_principal princ = NULL; + char *mem_name = NULL; + char *sep; + + kerr = krb5_cc_resolve(kctx, ccache_file, &ccache); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "error resolving ccache [%s].\n", + ccache_file); + return kerr; + } + + kerr = krb5_cc_get_full_name(kctx, ccache, &ccache_name); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to read name for ccache [%s].\n", + ccache_file); + goto done; + } + + sep = strchr(ccache_name, ':'); + if (sep == NULL || sep[1] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Ccache name [%s] does not have delimiter[:] .\n", ccache_name); + kerr = KRB5KRB_ERR_GENERIC; + goto done; + } + + if (strncmp(ccache_name, "MEMORY:", sizeof("MEMORY:") -1) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Ccache [%s] is already memory ccache.\n", + ccache_name); + *_mem_name = talloc_strdup(mem_ctx, ccache_name); + if(*_mem_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + kerr = KRB5KRB_ERR_GENERIC; + goto done; + } + kerr = 0; + goto done; + } + if (strncmp(ccache_name, "FILE:", sizeof("FILE:") -1) == 0) { + mem_name = talloc_asprintf(mem_ctx, "MEMORY:%s", sep + 1); + if (mem_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + kerr = KRB5KRB_ERR_GENERIC; + goto done; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected ccache type for ccache [%s], " \ + "currently only FILE is supported.\n", + ccache_name); + kerr = KRB5KRB_ERR_GENERIC; + goto done; + } + + kerr = krb5_cc_resolve(kctx, mem_name, &mem_ccache); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "error resolving ccache [%s].\n", mem_name); + goto done; + } + + kerr = krb5_cc_get_principal(kctx, ccache, &princ); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "error reading principal from ccache [%s].\n", ccache_name); + goto done; + } + + kerr = krb5_cc_initialize(kctx, mem_ccache, princ); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize ccache [%s].\n", mem_name); + goto done; + } + + kerr = krb5_cc_copy_creds(kctx, ccache, mem_ccache); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to copy ccache [%s] to [%s].\n", ccache_name, mem_name); + goto done; + } + + *_mem_name = mem_name; + kerr = 0; + +done: + if (kerr != 0) { + talloc_free(mem_name); + } + + krb5_free_string(kctx, ccache_name); + krb5_free_principal(kctx, princ); + + if (krb5_cc_close(kctx, ccache) != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_cc_close failed.\n"); + } + + if ((mem_ccache != NULL) && (krb5_cc_close(kctx, mem_ccache) != 0)) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_cc_close failed.\n"); + } + + return kerr; +} diff --git a/src/providers/krb5/krb5_ccache.h b/src/providers/krb5/krb5_ccache.h new file mode 100644 index 0000000..f3928e6 --- /dev/null +++ b/src/providers/krb5/krb5_ccache.h @@ -0,0 +1,73 @@ +/* + SSSD + + Kerberos 5 Backend Module -- ccache related utilities + + Authors: + Sumit Bose + Jakub Hrozek + + Copyright (C) 2014 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef __KRB5_CCACHE_H__ +#define __KRB5_CCACHE_H__ + +#include "util/util.h" + +struct tgt_times { + time_t authtime; + time_t starttime; + time_t endtime; + time_t renew_till; +}; + +errno_t sss_krb5_precreate_ccache(const char *ccname, uid_t uid, gid_t gid); + +errno_t sss_krb5_cc_destroy(const char *ccname, uid_t uid, gid_t gid); + +errno_t sss_krb5_check_ccache_princ(krb5_context kctx, + const char *ccname, + krb5_principal user_princ); + +errno_t sss_krb5_cc_verify_ccache(const char *ccname, uid_t uid, gid_t gid, + const char *realm, const char *principal); + +errno_t get_ccache_file_data(const char *ccache_file, const char *client_name, + struct tgt_times *tgtt); + +errno_t safe_remove_old_ccache_file(const char *old_ccache, + const char *new_ccache, + uid_t uid, gid_t gid); + +/** + * @brief Copy given ccache into a MEMORY ccache + * + * @param[in] mem_ctx Talloc memory context the new ccache name should be + * allocated on + * @param[in] kctx Kerberos context + * @param[in] ccache_file Name of existing ccache + * @param[out] _mem_name Name of the new MEMORY ccache + * + * In contrast to MEMORY keytabs MEMORY ccaches can and must be removed + * explicitly with krb5_cc_destroy() from the memory. Just calling + * krb5_cc_close() will keep the MEMORY ccache in memory even if there are no + * open handles for the given MEMORY ccache. + */ +krb5_error_code copy_ccache_into_memory(TALLOC_CTX *mem_ctx, krb5_context kctx, + const char *ccache_file, + char **_mem_name); +#endif /* __KRB5_CCACHE_H__ */ diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c new file mode 100644 index 0000000..704f650 --- /dev/null +++ b/src/providers/krb5/krb5_child.c @@ -0,0 +1,4247 @@ +/* + SSSD + + Kerberos 5 Backend Module -- tgt_req and changepw child + + Authors: + Sumit Bose + + Copyright (C) 2009-2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "util/util.h" +#include "util/sss_krb5.h" +#include "util/user_info_msg.h" +#include "util/child_common.h" +#include "util/find_uid.h" +#include "util/sss_chain_id.h" +#include "util/sss_ptr_hash.h" +#include "src/util/util_errors.h" +#include "providers/backend.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_utils.h" +#include "krb5_plugin/idp/idp.h" +#ifdef BUILD_PASSKEY +#include "responder/pam/pamsrv_passkey.h" +#include "krb5_plugin/passkey/passkey.h" +#endif /* BUILD_PASSKEY */ +#include "sss_cli.h" + +#define SSSD_KRB5_CHANGEPW_PRINCIPAL "kadmin/changepw" +#ifndef BUILD_PASSKEY +#define SSSD_PASSKEY_QUESTION "passkey" +#endif /* BUILD_PASSKEY */ + +typedef krb5_error_code +(*k5_init_creds_password_fn_t)(krb5_context context, krb5_creds *creds, + krb5_principal client, const char *password, + krb5_prompter_fct prompter, void *data, + krb5_deltat start_time, + const char *in_tkt_service, + krb5_get_init_creds_opt *k5_gic_options); + +enum k5c_fast_opt { + K5C_FAST_NEVER, + K5C_FAST_TRY, + K5C_FAST_DEMAND, +}; + +struct cli_opts { + char *realm; + char *lifetime; + char *rtime; + char *use_fast_str; + char *fast_principal; + uint32_t check_pac_flags; + bool canonicalize; + bool fast_use_anonymous_pkinit; +}; + +struct krb5_req { + krb5_context ctx; + krb5_principal princ; + krb5_principal princ_orig; + char* name; + krb5_creds *creds; + bool otp; + bool password_prompting; + bool pkinit_prompting; + char *otp_vendor; + char *otp_token_id; + char *otp_challenge; + krb5_get_init_creds_opt *options; + k5_init_creds_password_fn_t krb5_get_init_creds_password; + + struct pam_data *pd; + + char *realm; + char *ccname; + char *keytab; + bool validate; + bool posix_domain; + bool send_pac; + bool use_enterprise_princ; + char *fast_ccname; + + const char *upn; + uid_t uid; + gid_t gid; + + char *old_ccname; + bool old_cc_valid; + bool old_cc_active; + enum k5c_fast_opt fast_val; + + uid_t fast_uid; + gid_t fast_gid; + struct sss_creds *pcsc_saved_creds; + + struct cli_opts *cli_opts; +}; + +static krb5_context krb5_error_ctx; +#define KRB5_CHILD_DEBUG(level, error) KRB5_DEBUG(level, krb5_error_ctx, error) + +static errno_t k5c_attach_otp_info_msg(struct krb5_req *kr); +static errno_t k5c_attach_oauth2_info_msg(struct krb5_req *kr, struct sss_idp_oauth2 *data); +#ifdef BUILD_PASSKEY +static errno_t k5c_attach_passkey_msg(struct krb5_req *kr, struct sss_passkey_challenge *data); +#endif /* BUILD_PASSKEY */ +static errno_t k5c_attach_keep_alive_msg(struct krb5_req *kr); +static errno_t k5c_recv_data(struct krb5_req *kr, int fd, uint32_t *offline); +static errno_t k5c_send_data(struct krb5_req *kr, int fd, errno_t error); + +static errno_t k5c_become_user(uid_t uid, gid_t gid, bool is_posix) +{ + if (is_posix == false) { + DEBUG(SSSDBG_TRACE_FUNC, + "Will not drop privileges for a non-POSIX user\n"); + return EOK; + } + return become_user(uid, gid); +} + +static krb5_error_code set_lifetime_options(struct cli_opts *cli_opts, + krb5_get_init_creds_opt *options) +{ + krb5_error_code kerr; + krb5_deltat lifetime; + + if (cli_opts->rtime == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No specific renewable lifetime requested.\n"); + + /* Unset option flag to make sure defaults from krb5.conf are used. */ + options->flags &= ~(KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE); + } else { + kerr = krb5_string_to_deltat(cli_opts->rtime, &lifetime); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "krb5_string_to_deltat failed for [%s].\n", cli_opts->rtime); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + DEBUG(SSSDBG_CONF_SETTINGS, "Renewable lifetime is set to [%s]\n", + cli_opts->rtime); + krb5_get_init_creds_opt_set_renew_life(options, lifetime); + } + + if (cli_opts->lifetime == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No specific lifetime requested.\n"); + + /* Unset option flag to make sure defaults from krb5.conf are used. */ + options->flags &= ~(KRB5_GET_INIT_CREDS_OPT_TKT_LIFE); + } else { + kerr = krb5_string_to_deltat(cli_opts->lifetime, &lifetime); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "krb5_string_to_deltat failed for [%s].\n", + cli_opts->lifetime); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + DEBUG(SSSDBG_CONF_SETTINGS, "Lifetime is set to [%s]\n", + cli_opts->lifetime); + krb5_get_init_creds_opt_set_tkt_life(options, lifetime); + } + + return 0; +} + +static void set_canonicalize_option(struct cli_opts *cli_opts, + krb5_get_init_creds_opt *opts) +{ + int canonicalize = 0; + + canonicalize = cli_opts->canonicalize ? 1 : 0; + DEBUG(SSSDBG_CONF_SETTINGS, "Canonicalization is set to [%s]\n", + cli_opts->canonicalize ? "true" : "false"); + sss_krb5_get_init_creds_opt_set_canonicalize(opts, canonicalize); +} + +static void set_changepw_options(krb5_get_init_creds_opt *options) +{ + sss_krb5_get_init_creds_opt_set_canonicalize(options, 0); + krb5_get_init_creds_opt_set_forwardable(options, 0); + krb5_get_init_creds_opt_set_proxiable(options, 0); + krb5_get_init_creds_opt_set_renew_life(options, 0); + krb5_get_init_creds_opt_set_tkt_life(options, 5*60); +} + +static void revert_changepw_options(struct cli_opts *cli_opts, + krb5_get_init_creds_opt *options) +{ + krb5_error_code kerr; + + set_canonicalize_option(cli_opts, options); + + /* Currently we do not set forwardable and proxiable explicitly, the flags + * must be removed so that libkrb5 can take the defaults from krb5.conf */ + options->flags &= ~(KRB5_GET_INIT_CREDS_OPT_FORWARDABLE); + options->flags &= ~(KRB5_GET_INIT_CREDS_OPT_PROXIABLE); + + kerr = set_lifetime_options(cli_opts, options); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "set_lifetime_options failed.\n"); + } +} + + +static errno_t sss_send_pac(krb5_authdata **pac_authdata) +{ + struct sss_cli_req_data sss_data; + int ret; + int errnop; + + sss_data.len = pac_authdata[0]->length; + sss_data.data = pac_authdata[0]->contents; + + ret = sss_pac_make_request(SSS_PAC_ADD_PAC_USER, &sss_data, + NULL, NULL, &errnop); + DEBUG(SSSDBG_TRACE_ALL, + "NSS return code [%d], request return code [%d][%s].\n", ret, + errnop, sss_strerror(errnop)); + if (errnop == ERR_CHECK_PAC_FAILED) { + return ERR_CHECK_PAC_FAILED; + } + + if (ret == NSS_STATUS_UNAVAIL) { + DEBUG(SSSDBG_MINOR_FAILURE, "failed to contact PAC responder\n"); + return EIO; + } else if (ret != NSS_STATUS_SUCCESS || errnop != 0) { + DEBUG(SSSDBG_OP_FAILURE, "sss_pac_make_request failed [%d][%d].\n", + ret, errnop); + return EIO; + } + DEBUG(SSSDBG_TRACE_FUNC, + "PAC responder contacted. It might take a bit of time in case the " + "cache is not up to date.\n"); + + return EOK; +} + +static void sss_krb5_expire_callback_func(krb5_context context, void *data, + krb5_timestamp password_expiration, + krb5_timestamp account_expiration, + krb5_boolean is_last_req) +{ + int ret; + uint32_t *blob; + long exp_time; + struct krb5_req *kr = talloc_get_type(data, struct krb5_req); + + if (password_expiration == 0) { + return; + } + + exp_time = password_expiration - time(NULL); + if (exp_time < 0 || exp_time > UINT32_MAX) { + DEBUG(SSSDBG_CRIT_FAILURE, "Time to expire out of range.\n"); + return; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "exp_time: [%ld]\n", exp_time); + + blob = talloc_array(kr->pd, uint32_t, 2); + if (blob == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n"); + return; + } + + blob[0] = SSS_PAM_USER_INFO_EXPIRE_WARN; + blob[1] = (uint32_t) exp_time; + + ret = pam_add_response(kr->pd, SSS_PAM_USER_INFO, 2 * sizeof(uint32_t), + (uint8_t *) blob); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return; +} + +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER +/* + * TODO: These features generally would requires a significant refactoring + * of SSSD and MIT krb5 doesn't support them anyway. They are listed here + * simply as a reminder of things that might become future feature potential. + * + * 1. tokeninfo selection + * 2. challenge + * 3. discreet token/PIN prompting + * 4. interactive OTP format correction + * 5. nextOTP + * + */ +typedef int (*checker)(int c); + +static inline checker pick_checker(int format) +{ + switch (format) { + case KRB5_RESPONDER_OTP_FORMAT_DECIMAL: + return isdigit; + case KRB5_RESPONDER_OTP_FORMAT_HEXADECIMAL: + return isxdigit; + case KRB5_RESPONDER_OTP_FORMAT_ALPHANUMERIC: + return isalnum; + } + + return NULL; +} + +static int token_pin_destructor(char *mem) +{ + return sss_erase_talloc_mem_securely(mem); +} + +static krb5_error_code tokeninfo_matches_2fa(TALLOC_CTX *mem_ctx, + const krb5_responder_otp_tokeninfo *ti, + const char *fa1, size_t fa1_len, + const char *fa2, size_t fa2_len, + char **out_token, char **out_pin) +{ + char *token = NULL, *pin = NULL; + checker check = NULL; + int i; + + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_NEXTOTP) { + return ENOTSUP; + } + + if (ti->challenge != NULL) { + return ENOTSUP; + } + + /* This is a non-sensical value. */ + if (ti->length == 0) { + return EPROTO; + } + + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN) { + if (ti->length > 0 && ti->length != fa2_len) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected [%d] and given [%zu] token size " + "do not match.\n", ti->length, fa2_len); + return EMSGSIZE; + } + + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN) { + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_SEPARATE_PIN) { + + pin = talloc_strndup(mem_ctx, fa1, fa1_len); + if (pin == NULL) { + talloc_free(token); + return ENOMEM; + } + talloc_set_destructor(pin, token_pin_destructor); + + token = talloc_strndup(mem_ctx, fa2, fa2_len); + if (token == NULL) { + return ENOMEM; + } + talloc_set_destructor(token, token_pin_destructor); + + check = pick_checker(ti->format); + } + } else { + token = talloc_asprintf(mem_ctx, "%s%s", fa1, fa2); + if (token == NULL) { + return ENOMEM; + } + talloc_set_destructor(token, token_pin_destructor); + + check = pick_checker(ti->format); + } + } else { + /* Assuming PIN only required */ + pin = talloc_strndup(mem_ctx, fa1, fa1_len); + if (pin == NULL) { + return ENOMEM; + } + talloc_set_destructor(pin, token_pin_destructor); + } + + /* If check is set, we need to verify the contents of the token. */ + for (i = 0; check != NULL && token[i] != '\0'; i++) { + if (!check(token[i])) { + talloc_free(token); + talloc_free(pin); + return EBADMSG; + } + } + + *out_token = token; + *out_pin = pin; + return 0; +} + +static krb5_error_code tokeninfo_matches_pwd(TALLOC_CTX *mem_ctx, + const krb5_responder_otp_tokeninfo *ti, + const char *pwd, size_t len, + char **out_token, char **out_pin) +{ + char *token = NULL, *pin = NULL; + checker check = NULL; + int i; + + + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_NEXTOTP) { + return ENOTSUP; + } + + if (ti->challenge != NULL) { + return ENOTSUP; + } + + /* This is a non-sensical value. */ + if (ti->length == 0) { + return EPROTO; + } + + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN) { + /* ASSUMPTION: authtok has one of the following formats: + * 1. TokenValue + * 2. PIN+TokenValue + */ + token = talloc_strndup(mem_ctx, pwd, len); + if (token == NULL) { + return ENOMEM; + } + talloc_set_destructor(token, token_pin_destructor); + + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN) { + /* If the server desires a separate PIN, we will split it. + * ASSUMPTION: Format of authtok is PIN+TokenValue. */ + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_SEPARATE_PIN) { + if (ti->length < 1) { + talloc_free(token); + return ENOTSUP; + } + + if (ti->length >= len) { + talloc_free(token); + return EMSGSIZE; + } + + /* Copy the PIN from the front of the value. */ + pin = talloc_strndup(NULL, pwd, len - ti->length); + if (pin == NULL) { + talloc_free(token); + return ENOMEM; + } + talloc_set_destructor(pin, token_pin_destructor); + + /* Remove the PIN from the front of the token value. */ + memmove(token, token + len - ti->length, ti->length + 1); + + check = pick_checker(ti->format); + } else { + if (ti->length > 0 && ti->length > len) { + talloc_free(token); + return EMSGSIZE; + } + } + } else { + if (ti->length > 0 && ti->length != len) { + talloc_free(token); + return EMSGSIZE; + } + + check = pick_checker(ti->format); + } + } else { + pin = talloc_strndup(mem_ctx, pwd, len); + if (pin == NULL) { + return ENOMEM; + } + talloc_set_destructor(pin, token_pin_destructor); + } + + /* If check is set, we need to verify the contents of the token. */ + for (i = 0; check != NULL && token[i] != '\0'; i++) { + if (!check(token[i])) { + talloc_free(token); + talloc_free(pin); + return EBADMSG; + } + } + + *out_token = token; + *out_pin = pin; + return 0; +} + +static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx, + const krb5_responder_otp_tokeninfo *ti, + struct sss_auth_token *auth_tok, + char **out_token, char **out_pin) +{ + int ret; + const char *pwd; + size_t len; + const char *fa2; + size_t fa2_len; + + switch (sss_authtok_get_type(auth_tok)) { + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_get_password(auth_tok, &pwd, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_password failed.\n"); + return ret; + } + + return tokeninfo_matches_pwd(mem_ctx, ti, pwd, len, out_token, out_pin); + break; + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + ret = sss_authtok_get_2fa_single(auth_tok, &pwd, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_password failed.\n"); + return ret; + } + + return tokeninfo_matches_pwd(mem_ctx, ti, pwd, len, out_token, out_pin); + break; + case SSS_AUTHTOK_TYPE_2FA: + ret = sss_authtok_get_2fa(auth_tok, &pwd, &len, &fa2, &fa2_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_2fa failed.\n"); + return ret; + } + + return tokeninfo_matches_2fa(mem_ctx, ti, pwd, len, fa2, fa2_len, + out_token, out_pin); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported authtok type %d\n", sss_authtok_get_type(auth_tok)); + } + + return EINVAL; +} + +static krb5_error_code answer_otp(krb5_context ctx, + struct krb5_req *kr, + krb5_responder_context rctx) +{ + krb5_responder_otp_challenge *chl; + char *token = NULL, *pin = NULL; + krb5_error_code ret; + size_t i; + + ret = krb5_responder_otp_get_challenge(ctx, rctx, &chl); + if (ret != EOK || chl == NULL) { + /* Either an error, or nothing to do. */ + return ret; + } + + if (chl->tokeninfo == NULL || chl->tokeninfo[0] == NULL) { + /* No tokeninfos? Absurd! */ + ret = EINVAL; + goto done; + } + + kr->otp = true; + + if (kr->pd->cmd == SSS_PAM_PREAUTH) { + for (i = 0; chl->tokeninfo[i] != NULL; i++) { + DEBUG(SSSDBG_TRACE_ALL, "[%zu] Vendor [%s].\n", + i, chl->tokeninfo[i]->vendor); + DEBUG(SSSDBG_TRACE_ALL, "[%zu] Token-ID [%s].\n", + i, chl->tokeninfo[i]->token_id); + DEBUG(SSSDBG_TRACE_ALL, "[%zu] Challenge [%s].\n", + i, chl->tokeninfo[i]->challenge); + DEBUG(SSSDBG_TRACE_ALL, "[%zu] Flags [%d].\n", + i, chl->tokeninfo[i]->flags); + } + + if (chl->tokeninfo[0]->vendor != NULL) { + kr->otp_vendor = talloc_strdup(kr, chl->tokeninfo[0]->vendor); + } + if (chl->tokeninfo[0]->token_id != NULL) { + kr->otp_token_id = talloc_strdup(kr, chl->tokeninfo[0]->token_id); + } + if (chl->tokeninfo[0]->challenge != NULL) { + kr->otp_challenge = talloc_strdup(kr, chl->tokeninfo[0]->challenge); + } + /* Allocation errors are ignored on purpose */ + + DEBUG(SSSDBG_TRACE_INTERNAL, "Exit answer_otp during pre-auth.\n"); + return EAGAIN; + } + + /* Find the first supported tokeninfo which matches our authtoken. */ + for (i = 0; chl->tokeninfo[i] != NULL; i++) { + ret = tokeninfo_matches(kr, chl->tokeninfo[i], kr->pd->authtok, + &token, &pin); + if (ret == EOK) { + break; + } + + switch (ret) { + case EBADMSG: + case EMSGSIZE: + case ENOTSUP: + case EPROTO: + break; + default: + goto done; + } + } + if (chl->tokeninfo[i] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No tokeninfos found which match our credentials.\n"); + ret = EOK; + goto done; + } + + if (chl->tokeninfo[i]->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN) { + /* Don't let SSSD cache the OTP authtoken since it is single-use. */ + ret = pam_add_response(kr->pd, SSS_OTP, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + /* Respond with the appropriate answer. */ + ret = krb5_responder_otp_set_answer(ctx, rctx, i, token, pin); +done: + talloc_free(token); + talloc_free(pin); + krb5_responder_otp_challenge_free(ctx, rctx, chl); + return ret; +} + +static bool pkinit_identity_matches(const char *identity, + const char *token_name, + const char *module_name) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *str; + bool res = false; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return false; + } + + str = talloc_asprintf(tmp_ctx, "module_name=%s", module_name); + if (str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + goto done; + } + + if (strstr(identity, str) == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Identity [%s] does not contain [%s].\n", + identity, str); + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found [%s] in identity [%s].\n", str, identity); + + str = talloc_asprintf(tmp_ctx, "token=%s", token_name); + if (str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + goto done; + } + + if (strstr(identity, str) == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Identity [%s] does not contain [%s].\n", + identity, str); + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found [%s] in identity [%s].\n", str, identity); + + res = true; + +done: + talloc_free(tmp_ctx); + + return res; +} + +static krb5_error_code answer_pkinit(krb5_context ctx, + struct krb5_req *kr, + krb5_responder_context rctx) +{ + krb5_error_code kerr; + const char *pin = NULL; + const char *token_name = NULL; + const char *module_name = NULL; + krb5_responder_pkinit_challenge *chl = NULL; + size_t c; + + kerr = krb5_responder_pkinit_get_challenge(ctx, rctx, &chl); + if (kerr != EOK || chl == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_responder_pkinit_get_challenge failed.\n"); + return kerr; + } + if (chl->identities == NULL || chl->identities[0] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No identities for pkinit!\n"); + kerr = EINVAL; + goto done; + } + + for (c = 0; chl->identities[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "[%zu] Identity [%s] flags [%"PRId32"].\n", + c, chl->identities[c]->identity, + chl->identities[c]->token_flags); + } + + DEBUG(SSSDBG_TRACE_ALL, "Setting pkinit_prompting.\n"); + kr->pkinit_prompting = true; + + if (kr->pd->cmd == SSS_PAM_AUTHENTICATE + && (sss_authtok_get_type(kr->pd->authtok) + == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(kr->pd->authtok) + == SSS_AUTHTOK_TYPE_SC_KEYPAD)) { + kerr = sss_authtok_get_sc(kr->pd->authtok, &pin, NULL, + &token_name, NULL, + &module_name, NULL, + NULL, NULL, NULL, NULL); + if (kerr != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_authtok_get_sc failed.\n"); + goto done; + } + + for (c = 0; chl->identities[c] != NULL; c++) { + if (chl->identities[c]->identity != NULL + && pkinit_identity_matches(chl->identities[c]->identity, + token_name, module_name)) { + break; + } + } + + if (chl->identities[c] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No matching identity for [%s][%s] found in pkinit challenge.\n", + token_name, module_name); + kerr = EINVAL; + goto done; + } + + kerr = krb5_responder_pkinit_set_answer(ctx, rctx, + chl->identities[c]->identity, + pin); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_responder_set_answer failed.\n"); + } + + goto done; + } + + kerr = EOK; + +done: + krb5_responder_pkinit_challenge_free(ctx, rctx, chl); + + return kerr; +} + +static errno_t krb5_req_update(struct krb5_req *dest, struct krb5_req *src) +{ + /* Check request validity. This should never happen, but it is better to + * be little paranoid. */ + if (strcmp(dest->ccname, src->ccname) != 0) { + return EINVAL; + } + + if (strcmp(dest->upn, src->upn) != 0) { + return EINVAL; + } + + if (dest->uid != src->uid || dest->gid != src->gid) { + return EINVAL; + } + + /* Update PAM data. */ + talloc_free(dest->pd); + dest->pd = talloc_steal(dest, src->pd); + + return EOK; +} + +static krb5_error_code idp_oauth2_preauth(struct krb5_req *kr, + struct sss_idp_oauth2 *oauth2) +{ + struct krb5_req *tmpkr = NULL; + uint32_t offline; + errno_t ret; + + if (oauth2->verification_uri == NULL || oauth2->user_code == NULL) { + ret = EINVAL; + goto done; + } + + /* Challenge was presented. We need to continue the authentication + * with this exact child process in order to maintain internal Kerberos + * state so we are able to respond to this particular challenge. */ + + ret = k5c_attach_oauth2_info_msg(kr, oauth2); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "k5c_attach_oauth2_info_msg failed.\n"); + return ret; + } + + ret = k5c_attach_keep_alive_msg(kr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "k5c_attach_keep_alive_msg failed.\n"); + return ret; + } + + tmpkr = talloc_zero(NULL, struct krb5_req); + if (tmpkr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Send reply and wait for next step. */ + ret = k5c_send_data(kr, STDOUT_FILENO, ret); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to send reply\n"); + } + + ret = k5c_recv_data(tmpkr, STDIN_FILENO, &offline); + if (ret != EOK) { + goto done; + } + + ret = krb5_req_update(kr, tmpkr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to update krb request [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + talloc_free(tmpkr); + return ret; +} + +static krb5_error_code answer_idp_oauth2(krb5_context kctx, + struct krb5_req *kr, + krb5_responder_context rctx) +{ + enum sss_authtok_type type; + struct sss_idp_oauth2 *data; + const char *challenge; + const char *token; + size_t token_len; + krb5_error_code kerr; + + challenge = krb5_responder_get_challenge(kctx, rctx, + SSSD_IDP_OAUTH2_QUESTION); + if (challenge == NULL) { + return ENOENT; + } + + data = sss_idp_oauth2_decode_challenge(challenge); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to parse OAuth2 challenge\n"); + return EINVAL; + } + + if (kr->pd->cmd == SSS_PAM_PREAUTH) { + kerr = idp_oauth2_preauth(kr, data); + if (kerr != EOK) { + goto done; + } + } + + if (kr->pd->cmd != SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected command [%d]\n", kr->pd->cmd); + kerr = EINVAL; + goto done; + } + + type = sss_authtok_get_type(kr->pd->authtok); + if (type != SSS_AUTHTOK_TYPE_OAUTH2) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected authentication token type [%s]\n", + sss_authtok_type_to_str(type)); + kerr = EINVAL; + goto done; + } + + kerr = sss_authtok_get_oauth2(kr->pd->authtok, &token, &token_len); + if (kerr != EOK) { + goto done; + } + + if (strlen(data->user_code) != token_len && strcmp(data->user_code, token) != 0) { + DEBUG(SSSDBG_OP_FAILURE, "User code do not match!\n"); + kerr = EINVAL; + goto done; + } + + /* Don't let SSSD cache the authtoken since it is single-use. */ + kerr = pam_add_response(kr->pd, SSS_OTP, 0, NULL); + if (kerr != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + + /* The answer is arbitrary but we need to provide some since krb5 lib + * expects it. So we choose the pin. */ + kerr = krb5_responder_set_answer(kctx, rctx, SSSD_IDP_OAUTH2_QUESTION, + data->user_code); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to set IdP answer [%d]\n", kerr); + goto done; + } + + kerr = EOK; + +done: + sss_idp_oauth2_free(data); + + return kerr; +} + +#ifdef BUILD_PASSKEY +static errno_t k5c_attach_passkey_msg(struct krb5_req *kr, + struct sss_passkey_challenge *data) +{ + uint8_t *msg; + const char *user_verification; + int i; + size_t msg_len = 0; + size_t domain_len = 0; + size_t crypto_len = 0; + size_t num_creds = 0; + size_t cred_len = 0; + size_t verification_len = 0; + size_t idx = 0; + errno_t ret; + + if (data->domain == NULL || data->credential_id_list == NULL + || data->cryptographic_challenge == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Empty passkey domain, credential id list, or cryptographic " + "challenge\n"); + return EINVAL; + } + + user_verification = data->user_verification == 0 ? "false" : "true"; + verification_len = strlen(user_verification) + 1; + msg_len += verification_len; + + crypto_len = strlen(data->cryptographic_challenge) + 1; + msg_len += crypto_len; + + domain_len = strlen(data->domain) + 1; + msg_len += domain_len; + + /* credentials list size */ + msg_len += sizeof(uint32_t); + + for (i = 0; data->credential_id_list[i] != NULL; i++) { + msg_len += (strlen(data->credential_id_list[i]) + 1); + } + num_creds = i; + + msg = talloc_zero_size(kr, msg_len); + if (msg == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + /* To avoid sending extraneous data back and forth to pam_sss, + * (and reduce boilerplate memcpy code) only the user + * verification and cryptographic challenge are retrieved in pam_sss. + * + * The remaining passkey data (domain, creds list, num_creds) + * is sent to the PAM responder and stored in a hash table. The + * challenge is used as a unique key of the hash table. The pam_sss + * reply includes the challenge which is used to lookup the passkey + * data in the PAM responder, ensuring it matches the originating + * request */ + memcpy(msg + idx, user_verification, verification_len); + idx += verification_len; + + memcpy(msg + idx, data->cryptographic_challenge, crypto_len); + idx += crypto_len; + + memcpy(msg + idx, data->domain, domain_len); + idx += domain_len; + + SAFEALIGN_COPY_UINT32(msg + idx, &num_creds, &idx); + + for (i = 0; data->credential_id_list[i] != NULL; i++) { + cred_len = strlen(data->credential_id_list[i]) + 1; + memcpy(msg + idx, data->credential_id_list[i], cred_len); + idx += cred_len; + } + + ret = pam_add_response(kr->pd, SSS_PAM_PASSKEY_KRB_INFO, msg_len, msg); + talloc_zfree(msg); + + return ret; +} + +static krb5_error_code passkey_preauth(struct krb5_req *kr, + struct sss_passkey_challenge *passkey) +{ + struct krb5_req *tmpkr = NULL; + uint32_t offline; + errno_t ret; + + if (passkey->domain == NULL || passkey->credential_id_list == NULL + || passkey->cryptographic_challenge == NULL) { + ret = EINVAL; + goto done; + } + + ret = k5c_attach_passkey_msg(kr, passkey); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "k5c_attach_passkey_info_msg failed.\n"); + return ret; + } + + /* Challenge was presented. We need to continue the authentication + * with this exact child process in order to maintain internal Kerberos + * state so we are able to respond to this particular challenge. */ + ret = k5c_attach_keep_alive_msg(kr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "k5c_attach_keep_alive_msg failed.\n"); + return ret; + } + + tmpkr = talloc_zero(NULL, struct krb5_req); + if (tmpkr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Send reply and wait for next step. */ + ret = k5c_send_data(kr, STDOUT_FILENO, ret); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to send reply\n"); + } + + ret = k5c_recv_data(tmpkr, STDIN_FILENO, &offline); + if (ret != EOK) { + goto done; + } + + ret = krb5_req_update(kr, tmpkr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to update krb request [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + talloc_free(tmpkr); + return ret; +} +#endif /* BUILD_PASSKEY */ + +static krb5_error_code answer_passkey(krb5_context kctx, + struct krb5_req *kr, + krb5_responder_context rctx) +{ +#ifndef BUILD_PASSKEY + DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n"); + return EINVAL; +#else + enum sss_authtok_type type; + struct sss_passkey_message *msg; + struct sss_passkey_message *reply_msg = NULL; + const char *challenge; + const char *reply; + char *reply_str = NULL; + enum sss_passkey_phase phase; + const char *state; + size_t reply_len; + krb5_error_code kerr; + + challenge = krb5_responder_get_challenge(kctx, rctx, + SSSD_PASSKEY_QUESTION); + if (challenge == NULL) { + return ENOENT; + } + + msg = sss_passkey_message_decode(challenge); + if (msg == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to decode passkey message\n"); + return EINVAL; + } + + if (kr->pd->cmd == SSS_PAM_PREAUTH) { + kerr = passkey_preauth(kr, msg->data.challenge); + if (kerr != EOK) { + goto done; + } + } + + if (kr->pd->cmd != SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected command [%d]\n", kr->pd->cmd); + kerr = EINVAL; + goto done; + } + + type = sss_authtok_get_type(kr->pd->authtok); + if (type != SSS_AUTHTOK_TYPE_PASSKEY_REPLY) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected authentication token type [%s]\n", + sss_authtok_type_to_str(type)); + kerr = EINVAL; + goto done; + } + + kerr = sss_authtok_get_passkey_reply(kr->pd->authtok, &reply, &reply_len); + if (kerr != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected command [%d]\n", kr->pd->cmd); + goto done; + } + + phase = SSS_PASSKEY_PHASE_REPLY; + state = SSSD_PASSKEY_REPLY_STATE; + reply_msg = sss_passkey_message_from_reply_json(phase, state, reply); + if (reply_msg == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to prefix passkey message\n"); + kerr = EINVAL; + goto done; + } + + reply_str = sss_passkey_message_encode(reply_msg); + if (reply_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to encode passkey message\n"); + kerr = EINVAL; + goto done; + } + + /* Don't let SSSD cache the authtoken since it is single-use. */ + kerr = pam_add_response(kr->pd, SSS_OTP, 0, NULL); + if (kerr != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + + kerr = krb5_responder_set_answer(kctx, rctx, SSSD_PASSKEY_QUESTION, + reply_str); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to set passkey answer [%d]\n", kerr); + goto done; + } + + kerr = EOK; + +done: + if (reply_str != NULL) { + free(reply_str); + } + if (reply_msg != NULL) { + sss_passkey_message_free(reply_msg); + } + + return kerr; +#endif /* BUILD_PASSKEY */ +} + +static krb5_error_code sss_krb5_responder(krb5_context ctx, + void *data, + krb5_responder_context rctx) +{ + struct krb5_req *kr = talloc_get_type(data, struct krb5_req); + const char * const *question_list; + size_t c; + const char *pwd; + int ret; + krb5_error_code kerr; + + if (kr == NULL) { + return EINVAL; + } + + question_list = krb5_responder_list_questions(ctx, rctx); + + if (question_list != NULL) { + for (c = 0; question_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Got question [%s].\n", question_list[c]); + + if (strcmp(question_list[c], + KRB5_RESPONDER_QUESTION_PASSWORD) == 0) { + kr->password_prompting = true; + + if ((kr->pd->cmd == SSS_PAM_AUTHENTICATE + || kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM + || kr->pd->cmd == SSS_PAM_CHAUTHTOK) + && sss_authtok_get_type(kr->pd->authtok) + == SSS_AUTHTOK_TYPE_PASSWORD) { + ret = sss_authtok_get_password(kr->pd->authtok, &pwd, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_authtok_get_password failed.\n"); + return ret; + } + + kerr = krb5_responder_set_answer(ctx, rctx, + KRB5_RESPONDER_QUESTION_PASSWORD, + pwd); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_responder_set_answer failed.\n"); + } + + return kerr; + } + } else if (strcmp(question_list[c], + KRB5_RESPONDER_QUESTION_PKINIT) == 0 + && (sss_authtok_get_type(kr->pd->authtok) + == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(kr->pd->authtok) + == SSS_AUTHTOK_TYPE_SC_KEYPAD)) { + return answer_pkinit(ctx, kr, rctx); + } else if (strcmp(question_list[c], SSSD_IDP_OAUTH2_QUESTION) == 0) { + return answer_idp_oauth2(ctx, kr, rctx); + } else if (strcmp(question_list[c], SSSD_PASSKEY_QUESTION) == 0) { + return answer_passkey(ctx, kr, rctx); + } + } + } + + return answer_otp(ctx, kr, rctx); +} +#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER */ + +static char *password_or_responder(const char *password) +{ +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER + /* If the new responder interface is available, we will handle even simple + * passwords in the responder. */ + return NULL; +#else + return discard_const(password); +#endif +} + +static krb5_error_code sss_krb5_prompter(krb5_context context, void *data, + const char *name, const char *banner, + int num_prompts, krb5_prompt prompts[]) +{ + int ret; + size_t c; + struct krb5_req *kr = talloc_get_type(data, struct krb5_req); + + if (kr == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_ALL, + "sss_krb5_prompter name [%s] banner [%s] num_prompts [%d] EINVAL.\n", + name, banner, num_prompts); + + if (num_prompts != 0) { + for (c = 0; c < num_prompts; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Prompt [%zu][%s].\n", c, + prompts[c].prompt); + } + + DEBUG(SSSDBG_FUNC_DATA, "Prompter interface isn't used for password prompts by SSSD.\n"); + return KRB5_LIBOS_CANTREADPWD; + } + + if (banner == NULL || *banner == '\0') { + DEBUG(SSSDBG_FUNC_DATA, + "Prompter called with empty banner, nothing to do.\n"); + return EOK; + } + + DEBUG(SSSDBG_FUNC_DATA, "Prompter called with [%s].\n", banner); + + ret = pam_add_response(kr->pd, SSS_PAM_TEXT_MSG, strlen(banner)+1, + (const uint8_t *) banner); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return EOK; +} + + +static krb5_error_code create_empty_cred(krb5_context ctx, krb5_principal princ, + krb5_creds **_cred) +{ + krb5_error_code kerr; + krb5_creds *cred = NULL; + krb5_data *krb5_realm; + + cred = calloc(sizeof(krb5_creds), 1); + if (cred == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "calloc failed.\n"); + return ENOMEM; + } + + kerr = krb5_copy_principal(ctx, princ, &cred->client); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_copy_principal failed.\n"); + goto done; + } + + krb5_realm = krb5_princ_realm(ctx, princ); + + kerr = krb5_build_principal_ext(ctx, &cred->server, + krb5_realm->length, krb5_realm->data, + KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME, + krb5_realm->length, krb5_realm->data, 0); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_build_principal_ext failed.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Created empty krb5_creds.\n"); + +done: + if (kerr != 0) { + krb5_free_cred_contents(ctx, cred); + free(cred); + } else { + *_cred = cred; + } + + return kerr; +} + + +static errno_t handle_randomized(char *in) +{ + size_t ccname_len; + char *ccname = NULL; + int ret; + + /* We only treat the FILE type case in a special way due to the history + * of storing FILE type ccache in /tmp and associated security issues */ + if (in[0] == '/') { + ccname = in; + } else if (strncmp(in, "FILE:", 5) == 0) { + ccname = in + 5; + } else { + return EOK; + } + + ccname_len = strlen(ccname); + if (ccname_len >= 6 && strcmp(ccname + (ccname_len - 6), "XXXXXX") == 0) { + /* NOTE: this call is only used to create a unique name, as later + * krb5_cc_initialize() will unlink and recreate the file. + * This is ok because this part of the code is called with + * privileges already dropped when handling user ccache, or the ccache + * is stored in a private directory. So we do not have huge issues if + * something races, we mostly care only about not accidentally use + * an existing name and thus failing in the process of saving the + * cache. Malicious races can only be avoided by libkrb5 itself. */ + ret = sss_unique_filename(NULL, ccname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "mkstemp(\"%s\") failed [%d]: %s!\n", + ccname, ret, strerror(ret)); + return ret; + } + } + + return EOK; +} + +/* NOTE: callers rely on 'name' being *changed* if it needs to be randomized, + * as they will then send the name back to the new name via the return call + * k5c_attach_ccname_msg(). Callers will send in a copy of the name if they + * do not care for changes. */ +static krb5_error_code create_ccache(char *ccname, krb5_creds *creds) +{ + krb5_context kctx = NULL; + krb5_ccache kcc = NULL; + const char *type; + krb5_error_code kerr; +#ifdef HAVE_KRB5_CC_COLLECTION + krb5_ccache cckcc; + bool switch_to_cc = false; +#endif + + /* Set a restrictive umask, just in case we end up creating any file or a + * directory. */ + if (strncmp(ccname, "DIR:", 4) == 0) { + umask(SSS_DFL_X_UMASK); + } else { + umask(SSS_DFL_UMASK); + } + + /* we create a new context here as the main process one may have been + * opened as root and contain possibly references (even open handles?) + * to resources we do not have or do not want to have access to */ + kerr = krb5_init_context(&kctx); + if (kerr) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return ERR_INTERNAL; + } + + kerr = handle_randomized(ccname); + if (kerr) { + DEBUG(SSSDBG_CRIT_FAILURE, "handle_randomized failed: %d\n", kerr); + goto done; + } + + kerr = krb5_cc_resolve(kctx, ccname, &kcc); + if (kerr) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + + type = krb5_cc_get_type(kctx, kcc); + DEBUG(SSSDBG_TRACE_ALL, "Initializing ccache of type [%s]\n", type); + +#ifdef HAVE_KRB5_CC_COLLECTION + if (krb5_cc_support_switch(kctx, type)) { + DEBUG(SSSDBG_TRACE_ALL, "CC supports switch\n"); + kerr = krb5_cc_set_default_name(kctx, ccname); + if (kerr) { + DEBUG(SSSDBG_TRACE_ALL, "Cannot set default name!\n"); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + + kerr = krb5_cc_cache_match(kctx, creds->client, &cckcc); + if (kerr == KRB5_CC_NOTFOUND) { + DEBUG(SSSDBG_TRACE_ALL, "Match not found\n"); + kerr = krb5_cc_new_unique(kctx, type, NULL, &cckcc); + switch_to_cc = true; + } + if (kerr) { + DEBUG(SSSDBG_TRACE_ALL, "krb5_cc_cache_match failed\n"); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + krb5_cc_close(kctx, kcc); + kcc = cckcc; + } +#endif + + kerr = krb5_cc_initialize(kctx, kcc, creds->client); + if (kerr) { + DEBUG(SSSDBG_TRACE_ALL, "krb5_cc_initialize failed\n"); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + + kerr = krb5_cc_store_cred(kctx, kcc, creds); + if (kerr) { + DEBUG(SSSDBG_TRACE_ALL, "krb5_cc_store_cred failed\n"); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + +#ifdef HAVE_KRB5_CC_COLLECTION + if (switch_to_cc) { + DEBUG(SSSDBG_TRACE_ALL, "switch_to_cc\n"); + kerr = krb5_cc_switch(kctx, kcc); + if (kerr) { + DEBUG(SSSDBG_TRACE_ALL, "krb5_cc_switch\n"); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + } +#endif + + DEBUG(SSSDBG_TRACE_ALL, "returning: %d\n", kerr); +done: + if (kcc) { + /* FIXME: should we krb5_cc_destroy in case of error? */ + krb5_cc_close(kctx, kcc); + } + + krb5_free_context(kctx); + + return kerr; +} + +static errno_t pack_response_packet(TALLOC_CTX *mem_ctx, errno_t error, + struct response_data *resp_list, + uint8_t **_buf, size_t *_len) +{ + uint8_t *buf; + size_t size = 0; + size_t p = 0; + struct response_data *pdr; + + /* A buffer with the following structure must be created: + * int32_t status of the request (required) + * message (zero or more) + * + * A message consists of: + * int32_t type of the message + * int32_t length of the following data + * uint8_t[len] data + */ + + size = sizeof(int32_t); + + for (pdr = resp_list; pdr != NULL; pdr = pdr->next) { + size += 2*sizeof(int32_t) + pdr->len; + } + + buf = talloc_array(mem_ctx, uint8_t, size); + if (!buf) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed\n"); + return ENOMEM; + } + + SAFEALIGN_SET_INT32(&buf[p], error, &p); + + for (pdr = resp_list; pdr != NULL; pdr = pdr->next) { + SAFEALIGN_SET_INT32(&buf[p], pdr->type, &p); + SAFEALIGN_SET_INT32(&buf[p], pdr->len, &p); + safealign_memcpy(&buf[p], pdr->data, pdr->len, &p); + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "response packet size: [%zu]\n", p); + + *_buf = buf; + *_len = p; + return EOK; +} + +static errno_t k5c_attach_otp_info_msg(struct krb5_req *kr) +{ + uint8_t *msg = NULL; + size_t msg_len; + int ret; + size_t vendor_len = 0; + size_t token_id_len = 0; + size_t challenge_len = 0; + size_t idx = 0; + + msg_len = 3; + if (kr->otp_vendor != NULL) { + vendor_len = strlen(kr->otp_vendor); + msg_len += vendor_len; + } + + if (kr->otp_token_id != NULL) { + token_id_len = strlen(kr->otp_token_id); + msg_len += token_id_len; + } + + if (kr->otp_challenge != NULL) { + challenge_len = strlen(kr->otp_challenge); + msg_len += challenge_len; + } + + msg = talloc_zero_size(kr, msg_len); + if (msg == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + if (kr->otp_vendor != NULL) { + memcpy(msg, kr->otp_vendor, vendor_len); + } + idx += vendor_len +1; + + if (kr->otp_token_id != NULL) { + memcpy(msg + idx, kr->otp_token_id, token_id_len); + } + idx += token_id_len +1; + + if (kr->otp_challenge != NULL) { + memcpy(msg + idx, kr->otp_challenge, challenge_len); + } + + ret = pam_add_response(kr->pd, SSS_PAM_OTP_INFO, msg_len, msg); + talloc_zfree(msg); + + return ret; +} + +static errno_t k5c_attach_oauth2_info_msg(struct krb5_req *kr, + struct sss_idp_oauth2 *data) +{ + uint8_t *msg; + const char *curi; + size_t msg_len; + size_t uri_len = 0; + size_t curi_len = 0; + size_t user_code_len = 0; + size_t idx = 0; + errno_t ret; + + if (data->verification_uri == NULL || data->user_code == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Empty oauth2 verification_uri or user_code\n"); + return EINVAL; + } + + msg_len = 0; + + uri_len = strlen(data->verification_uri) + 1; + msg_len += uri_len; + + if (data->verification_uri_complete != NULL) { + curi = data->verification_uri_complete; + curi_len = strlen(curi) + 1; + } else { + curi = ""; + curi_len = 1; + } + msg_len += curi_len; + + user_code_len = strlen(data->user_code) + 1; + msg_len += user_code_len; + + msg = talloc_zero_size(NULL, msg_len); + if (msg == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + memcpy(msg, data->verification_uri, uri_len); + idx += uri_len; + + memcpy(msg + idx, curi, curi_len); + idx += curi_len; + + memcpy(msg + idx, data->user_code, user_code_len); + + ret = pam_add_response(kr->pd, SSS_PAM_OAUTH2_INFO, msg_len, msg); + talloc_zfree(msg); + + return ret; +} + + +static errno_t k5c_attach_keep_alive_msg(struct krb5_req *kr) +{ + uint8_t *msg; + pid_t pid; + int ret; + + pid = getpid(); + + msg = talloc_memdup(kr, &pid, sizeof(pid_t)); + if (msg == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + /* Indicate that the krb5 child must be kept alive to continue + * authentication with correct internal state of Kerberos API. + * + * Further communication must be done against the same child process */ + ret = pam_add_response(kr->pd, SSS_CHILD_KEEP_ALIVE, sizeof(pid_t), msg); + talloc_zfree(msg); + + return ret; +} + +static errno_t k5c_attach_ccname_msg(struct krb5_req *kr) +{ + char *msg = NULL; + int ret; + + if (kr->ccname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error obtaining ccname.\n"); + return ERR_INTERNAL; + } + + msg = talloc_asprintf(kr, "%s=%s",CCACHE_ENV_NAME, kr->ccname); + if (msg == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + ret = pam_add_response(kr->pd, SSS_PAM_ENV_ITEM, + strlen(msg) + 1, (uint8_t *)msg); + talloc_zfree(msg); + + return ret; +} + +static errno_t k5c_send_data(struct krb5_req *kr, int fd, errno_t error) +{ + ssize_t written; + uint8_t *buf; + size_t len; + int ret; + + DEBUG(SSSDBG_FUNC_DATA, "Received error code %d\n", error); + + ret = pack_response_packet(kr, error, kr->pd->resp_list, &buf, &len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pack_response_packet failed.\n"); + return ret; + } + + errno = 0; + written = sss_atomic_write_safe_s(fd, buf, len); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "write failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + if (written != len) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Write error, wrote [%zu] bytes, expected [%zu]\n", + written, len); + return EOK; + } + + DEBUG(SSSDBG_TRACE_ALL, "Response sent.\n"); + + return EOK; +} + +static errno_t get_pkinit_identity(TALLOC_CTX *mem_ctx, + struct sss_auth_token *authtok, + char **_identity) +{ + int ret; + char *identity; + const char *token_name; + const char *module_name; + const char *key_id; + const char *label; + + ret = sss_authtok_get_sc(authtok, NULL, NULL, + &token_name, NULL, + &module_name, NULL, + &key_id, NULL, &label, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); + return ret; + } + + DEBUG(SSSDBG_TRACE_ALL, "Got [%s][%s].\n", token_name, module_name); + + if (module_name == NULL || *module_name == '\0') { + module_name = "p11-kit-proxy.so"; + } + + identity = talloc_asprintf(mem_ctx, "PKCS11:module_name=%s", module_name); + if (identity == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + + if (token_name != NULL && *token_name != '\0') { + identity = talloc_asprintf_append(identity, ":token=%s", + token_name); + if (identity == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "talloc_asprintf_append failed.\n"); + return ENOMEM; + } + } + + if (key_id != NULL && *key_id != '\0') { + identity = talloc_asprintf_append(identity, ":certid=%s", key_id); + if (identity == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "talloc_asprintf_append failed.\n"); + return ENOMEM; + } + } + + if (label != NULL && *label != '\0') { + identity = talloc_asprintf_append(identity, ":certlabel=%s", label); + if (identity == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "talloc_asprintf_append failed.\n"); + return ENOMEM; + } + } + + *_identity = identity; + + DEBUG(SSSDBG_TRACE_ALL, "Using pkinit identity [%s].\n", identity); + + return EOK; +} + +static errno_t add_ticket_times_and_upn_to_response(struct krb5_req *kr) +{ + int ret; + int64_t t[4]; + krb5_error_code kerr; + char *upn = NULL; + unsigned int upn_len = 0; + + t[0] = (int64_t) kr->creds->times.authtime; + t[1] = (int64_t) kr->creds->times.starttime; + t[2] = (int64_t) kr->creds->times.endtime; + t[3] = (int64_t) kr->creds->times.renew_till; + + ret = pam_add_response(kr->pd, SSS_KRB5_INFO_TGT_LIFETIME, + 4*sizeof(int64_t), (uint8_t *) t); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + + kerr = krb5_unparse_name_ext(kr->ctx, kr->creds->client, &upn, &upn_len); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_unparse_name_ext failed.\n"); + goto done; + } + + ret = pam_add_response(kr->pd, SSS_KRB5_INFO_UPN, upn_len, + (uint8_t *) upn); + krb5_free_unparsed_name(kr->ctx, upn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + +done: + return ret; +} + +static krb5_error_code validate_tgt(struct krb5_req *kr) +{ + krb5_error_code kerr; + krb5_error_code kt_err; + char *principal = NULL; + krb5_keytab keytab; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + krb5_verify_init_creds_opt opt; + krb5_principal validation_princ = NULL; + bool realm_entry_found = false; + krb5_ccache validation_ccache = NULL; + krb5_authdata **pac_authdata = NULL; + + memset(&keytab, 0, sizeof(keytab)); + kerr = krb5_kt_resolve(kr->ctx, kr->keytab, &keytab); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "error resolving keytab [%s], " \ + "not verifying TGT.\n", kr->keytab); + return kerr; + } + + memset(&cursor, 0, sizeof(cursor)); + kerr = krb5_kt_start_seq_get(kr->ctx, keytab, &cursor); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "error reading keytab [%s], " \ + "not verifying TGT.\n", kr->keytab); + krb5_kt_close(kr->ctx, keytab); + return kerr; + } + + /* We look for the first entry from our realm or take the last one */ + memset(&entry, 0, sizeof(entry)); + while ((kt_err = krb5_kt_next_entry(kr->ctx, keytab, &entry, &cursor)) == 0) { + if (validation_princ != NULL) { + krb5_free_principal(kr->ctx, validation_princ); + validation_princ = NULL; + } + kerr = krb5_copy_principal(kr->ctx, entry.principal, + &validation_princ); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_copy_principal failed.\n"); + krb5_kt_end_seq_get(kr->ctx, keytab, &cursor); + goto done; + } + + kerr = sss_krb5_free_keytab_entry_contents(kr->ctx, &entry); + if (kerr != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to free keytab entry.\n"); + } + memset(&entry, 0, sizeof(entry)); + + if (krb5_realm_compare(kr->ctx, validation_princ, kr->creds->client)) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Found keytab entry with the realm of the credential.\n"); + realm_entry_found = true; + break; + } + } + + if (!realm_entry_found) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Keytab entry with the realm of the credential not found " + "in keytab. Using the last entry.\n"); + } + + /* Close the keytab here. Even though we're using cursors, the file + * handle is stored in the krb5_keytab structure, and it gets + * overwritten when the verify_init_creds() call below creates its own + * cursor, creating a leak. */ + kerr = krb5_kt_end_seq_get(kr->ctx, keytab, &cursor); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_kt_end_seq_get failed, " \ + "not verifying TGT.\n"); + goto done; + } + + /* check if we got any errors from krb5_kt_next_entry */ + if (kt_err != 0 && kt_err != KRB5_KT_END) { + DEBUG(SSSDBG_CRIT_FAILURE, "error reading keytab [%s], " \ + "not verifying TGT.\n", kr->keytab); + goto done; + } + + /* Get the principal to which the key belongs, for logging purposes. */ + principal = NULL; + kerr = krb5_unparse_name(kr->ctx, validation_princ, &principal); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "internal error parsing principal name, " + "not verifying TGT.\n"); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + + + krb5_verify_init_creds_opt_init(&opt); + krb5_verify_init_creds_opt_set_ap_req_nofail(&opt, TRUE); + kerr = krb5_verify_init_creds(kr->ctx, kr->creds, validation_princ, keytab, + &validation_ccache, &opt); + + if (kerr == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "TGT verified using key for [%s].\n", + principal); + } else { + DEBUG(SSSDBG_CRIT_FAILURE ,"TGT failed verification using key " \ + "for [%s].\n", principal); + goto done; + } + + /* Try to find and send the PAC to the PAC responder. + * Failures are not critical. */ + if (kr->send_pac || kr->cli_opts->check_pac_flags != 0) { + kerr = sss_extract_pac(kr->ctx, validation_ccache, validation_princ, + kr->creds->client, keytab, + kr->cli_opts->check_pac_flags, &pac_authdata); + if (kerr != 0) { + if (kerr == ERR_CHECK_PAC_FAILED) { + DEBUG(SSSDBG_CRIT_FAILURE, + "PAC check failed for principal [%s].\n", kr->name); + goto done; + } + DEBUG(SSSDBG_OP_FAILURE, "sss_extract_and_send_pac failed, group " \ + "membership for user with principal [%s] " \ + "might not be correct.\n", kr->name); + kerr = 0; + goto done; + } + } + + if (kr->send_pac) { + if(unsetenv("_SSS_LOOPS") != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to unset _SSS_LOOPS, " + "sss_pac_make_request will most certainly fail.\n"); + } + + kerr = sss_send_pac(pac_authdata); + + if(setenv("_SSS_LOOPS", "NO", 0) != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to set _SSS_LOOPS.\n"); + } + + if (kerr != 0) { + if (kerr == ERR_CHECK_PAC_FAILED) { + DEBUG(SSSDBG_CRIT_FAILURE, + "PAC for principal [%s] is not valid.\n", kr->name); + goto done; + } + if (kr->cli_opts->check_pac_flags != 0) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "pac_check is set but PAC responder is not running, " + "failed to properly validate PAC, ignored, " + "authentication for [%s] can proceed.\n", kr->name); + } + DEBUG(SSSDBG_OP_FAILURE, "sss_send_pac failed, group " \ + "membership for user with principal [%s] " \ + "might not be correct.\n", kr->name); + kerr = 0; + } + } + +done: + krb5_free_authdata(kr->ctx, pac_authdata); + if (validation_ccache != NULL) { + krb5_cc_destroy(kr->ctx, validation_ccache); + } + + if (krb5_kt_close(kr->ctx, keytab) != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "krb5_kt_close failed\n"); + } + if (validation_princ != NULL) { + krb5_free_principal(kr->ctx, validation_princ); + } + if (principal != NULL) { + sss_krb5_free_unparsed_name(kr->ctx, principal); + } + + return kerr; + +} + +static krb5_error_code get_and_save_tgt_with_keytab(krb5_context ctx, + struct cli_opts *cli_opts, + krb5_principal princ, + krb5_keytab keytab, + char *ccname) +{ + krb5_error_code kerr = 0; + krb5_creds creds; + krb5_get_init_creds_opt options; + + memset(&creds, 0, sizeof(creds)); + memset(&options, 0, sizeof(options)); + + krb5_get_init_creds_opt_set_address_list(&options, NULL); + krb5_get_init_creds_opt_set_forwardable(&options, 0); + krb5_get_init_creds_opt_set_proxiable(&options, 0); + set_canonicalize_option(cli_opts, &options); + + kerr = krb5_get_init_creds_keytab(ctx, &creds, princ, keytab, 0, NULL, + &options); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + + /* Use the updated principal in the creds in case canonicalized */ + kerr = create_ccache(ccname, &creds); + if (kerr != 0) { + goto done; + } + kerr = 0; + +done: + krb5_free_cred_contents(ctx, &creds); + + return kerr; + +} + +/* [MS-KILE]: Kerberos Protocol Extensions + * https://msdn.microsoft.com/en-us/library/cc233855.aspx + * http://download.microsoft.com/download/9/5/E/95EF66AF-9026-4BB0-A41D-A4F81802D92C/%5BMS-KILE%5D.pdf + * 2.2.1 KERB-EXT-ERROR + */ +bool have_ms_kile_ext_error(unsigned char *data, unsigned int length, + uint32_t *_ntstatus) +{ + /* [MS-KILE] 2.2.2 KERB-ERROR-DATA + * Kerberos V5 messages are defined using Abstract Syntax Notation One + * (ASN.1) + * KERB-ERROR-DATA ::= SEQUENCE { + * data-type [1] INTEGER, + * data-value [2] OCTET STRING OPTIONAL + * } + * We are interested in data-type 3 KERB_ERR_TYPE_EXTENDED + */ + uint8_t kile_asn1_begining[] = { + 0x30, 0x15, /* 0x30 is SEQUENCE, 0x15 length */ + 0xA1, 0x03, /* 0xA1 is 1st element of sequence, 0x03 length */ + 0x02, 0x01, 0x03, /* 0x02 is INTEGER, 0x01 length, 0x03 value */ + 0xA2, 0x0E, /* 0xA2 is 2nd element of sequence, 0x0E length */ + 0x04, 0x0C, /* 0x04 is OCTET STRING, 0x0C length (12 bytes) */ + }; + const size_t offset = sizeof(kile_asn1_begining); + uint32_t value; + + if (length != 23 || data == NULL) { + return false; + } + + if (memcmp(data, kile_asn1_begining, offset) != 0) { + return false; + } + + /* [MS-KILE] 2.2.1 KERB-EXT-ERROR + * typedef struct KERB_EXT_ERROR { + * unsigned long status; + * unsigned long reserved; + * unsigned long flags; + * } KERB_EXT_ERROR; + * Status: An NTSTATUS value. See [MS-ERREF] section 2.3. + */ + value = data[offset + 3] << 24 + | data[offset + 2] << 16 + | data[offset + 1] << 8 + | data[offset + 0]; + + *_ntstatus = value; + return true; +} + +/* Following NTSTATUS values are from: + * [MS-ERREF]: Windows Error Codes -> Section 2.3.1 + * https://msdn.microsoft.com/en-us/library/cc231196.aspx + * http://download.microsoft.com/download/9/5/E/95EF66AF-9026-4BB0-A41D-A4F81802D92C/%5BMS-ERREF%5D.pdf + */ +#define NT_STATUS_ACCOUNT_EXPIRED 0xC0000193 +#define NT_STATUS_ACCOUNT_DISABLED 0xC0000072 + +void check_ms_kile_ext_krb5err(krb5_context context, + krb5_init_creds_context init_cred_ctx, + krb5_error_code *_kerr) +{ + krb5_error_code err; + krb5_error *error = NULL; + uint32_t ntstatus; + + err = krb5_init_creds_get_error(context, init_cred_ctx, &error); + if (err != 0 || error == NULL) { + KRB5_CHILD_DEBUG(SSSDBG_TRACE_FUNC, err); + return; + } + + if (have_ms_kile_ext_error((unsigned char *)error->e_data.data, error->e_data.length, + &ntstatus)) { + switch (ntstatus) { + case NT_STATUS_ACCOUNT_EXPIRED: + *_kerr = KRB5KDC_ERR_NAME_EXP; + break; + case NT_STATUS_ACCOUNT_DISABLED: + *_kerr = KRB5KDC_ERR_CLIENT_REVOKED; + break; + } + } +} + +krb5_error_code +sss_krb5_get_init_creds_password(krb5_context context, krb5_creds *creds, + krb5_principal client, const char *password, + krb5_prompter_fct prompter, void *data, + krb5_deltat start_time, + const char *in_tkt_service, + krb5_get_init_creds_opt *k5_gic_options) +{ + krb5_error_code kerr; + krb5_init_creds_context init_cred_ctx = NULL; + + kerr = krb5_init_creds_init(context, client, prompter, data, + start_time, k5_gic_options, + &init_cred_ctx); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + + if (password != NULL) { + kerr = krb5_init_creds_set_password(context, init_cred_ctx, password); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + } + + if (in_tkt_service != NULL) { + kerr = krb5_init_creds_set_service(context, init_cred_ctx, + in_tkt_service); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + } + + kerr = krb5_init_creds_get(context, init_cred_ctx); + if (kerr == KRB5KDC_ERR_CLIENT_REVOKED) { + check_ms_kile_ext_krb5err(context, init_cred_ctx, &kerr); + } + + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + + kerr = krb5_init_creds_get_creds(context, init_cred_ctx, creds); + +done: + krb5_init_creds_free(context, init_cred_ctx); + return kerr; +} + +static krb5_error_code get_and_save_tgt(struct krb5_req *kr, + const char *password) +{ + const char *realm_name; + int realm_length; + krb5_error_code kerr; + char *cc_name; + int ret; + char *identity = NULL; + + kerr = sss_krb5_get_init_creds_opt_set_expire_callback(kr->ctx, kr->options, + sss_krb5_expire_callback_func, + kr); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set expire callback, continue without.\n"); + } + + sss_krb5_princ_realm(kr->ctx, kr->princ, &realm_name, &realm_length); + if (realm_length == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_princ_realm failed.\n"); + return KRB5KRB_ERR_GENERIC; + } + + if (sss_authtok_get_type(kr->pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(kr->pd->authtok) + == SSS_AUTHTOK_TYPE_SC_KEYPAD) { + DEBUG(SSSDBG_TRACE_ALL, + "Found Smartcard credentials, trying pkinit.\n"); + + ret = get_pkinit_identity(kr, kr->pd->authtok, &identity); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_pkinit_identity failed.\n"); + return ret; + } + + kerr = krb5_get_init_creds_opt_set_pa(kr->ctx, kr->options, + "X509_user_identity", identity); + talloc_free(identity); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "krb5_get_init_creds_opt_set_pa failed.\n"); + return kerr; + } + + /* TODO: Maybe X509_anchors should be added here as well */ + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Attempting kinit for realm [%s]\n",realm_name); + kerr = kr->krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ, + password_or_responder(password), + sss_krb5_prompter, kr, 0, NULL, + kr->options); + if (kr->pd->cmd == SSS_PAM_PREAUTH && kerr != KRB5KDC_ERR_KEY_EXP) { + /* Any errors except KRB5KDC_ERR_KEY_EXP are ignored during pre-auth, + * only data is collected to be send back to the client. + * KRB5KDC_ERR_KEY_EXP must be handled separately to figure out the + * possible authentication methods to update the password. */ + DEBUG(SSSDBG_TRACE_FUNC, + "krb5_get_init_creds_password returned [%d] during pre-auth.\n", + kerr); + return 0; + } else { + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + + /* Special case for IPA password migration */ + if (kr->pd->cmd == SSS_PAM_AUTHENTICATE + && kerr == KRB5_PREAUTH_FAILED + && kr->pkinit_prompting == false + && kr->password_prompting == false + && kr->otp == false + && sss_authtok_get_type(kr->pd->authtok) + == SSS_AUTHTOK_TYPE_PASSWORD) { + return ERR_CREDS_INVALID; + } + + /* If during authentication either the MIT Kerberos pkinit + * pre-auth module is missing or no Smartcard is inserted and only + * pkinit is available KRB5_PREAUTH_FAILED is returned. + * ERR_NO_AUTH_METHOD_AVAILABLE is used to indicate to the + * frontend that local authentication might be tried. + * Same is true if Smartcard credentials are given but only other + * authentication methods are available. */ + if (kr->pd->cmd == SSS_PAM_AUTHENTICATE + && kerr == KRB5_PREAUTH_FAILED + && kr->pkinit_prompting == false + && (( kr->password_prompting == false + && kr->otp == false) + || ((kr->otp == true + || kr->password_prompting == true) + && IS_SC_AUTHTOK(kr->pd->authtok))) ) { + return ERR_NO_AUTH_METHOD_AVAILABLE; + } + return kerr; + } + } + + if (kr->validate) { + kerr = validate_tgt(kr); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "TGT validation is disabled.\n"); + } + + /* In a non-POSIX environment, we only care about the return code from + * krb5_child, so let's not even attempt to create the ccache + */ + if (kr->posix_domain == false) { + DEBUG(SSSDBG_TRACE_LIBS, + "Finished authentication in a non-POSIX domain\n"); + goto done; + } + + kerr = restore_creds(kr->pcsc_saved_creds); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "restore_creds failed.\n"); + } + /* Make sure ccache is created and written as the user */ + if (geteuid() != kr->uid || getegid() != kr->gid) { + kerr = k5c_become_user(kr->uid, kr->gid, kr->posix_domain); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "become_user failed.\n"); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Running as [%"SPRIuid"][%"SPRIgid"].\n", geteuid(), getegid()); + + /* If kr->ccname is cache collection (DIR:/...), we want to work + * directly with file ccache (DIR::/...), but cache collection + * should be returned back to back end. + */ + cc_name = sss_get_ccache_name_for_principal(kr->pd, kr->ctx, + kr->creds->client, + kr->ccname); + if (cc_name == NULL) { + cc_name = kr->ccname; + } + + /* Use the updated principal in the creds in case canonicalized */ + kerr = create_ccache(cc_name, kr->creds); + if (kerr != 0) { + goto done; + } + + /* Successful authentication! Check if ccache contains the + * right principal... + */ + kerr = sss_krb5_check_ccache_princ(kr->ctx, kr->ccname, kr->creds->client); + if (kerr) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No ccache for %s in %s?\n", kr->upn, kr->ccname); + goto done; + } + + kerr = safe_remove_old_ccache_file(kr->old_ccname, kr->ccname, + kr->uid, kr->gid); + if (kerr != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to remove old ccache file [%s], " + "please remove it manually.\n", kr->old_ccname); + } + + kerr = add_ticket_times_and_upn_to_response(kr); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "add_ticket_times_and_upn_to_response failed.\n"); + } + + kerr = 0; + +done: + krb5_free_cred_contents(kr->ctx, kr->creds); + + return kerr; + +} + +static errno_t map_krb5_error(krb5_error_code kerr) +{ + /* just pass SSSD's internal error codes */ + if (kerr > 0 && IS_SSSD_ERROR(kerr)) { + DEBUG(SSSDBG_CRIT_FAILURE, "[%d][%s].\n", kerr, sss_strerror(kerr)); + return kerr; + } + + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + } + + switch (kerr) { + case 0: + return ERR_OK; + + case KRB5_LIBOS_CANTREADPWD: + return ERR_NO_CREDS; + + case KRB5_KDCREP_SKEW: + case KRB5KRB_AP_ERR_SKEW: + case KRB5KRB_AP_ERR_TKT_EXPIRED: + case KRB5KRB_AP_ERR_TKT_NYV: + case KRB5_KDC_UNREACH: + case KRB5_REALM_CANT_RESOLVE: + case KRB5_REALM_UNKNOWN: + return ERR_NETWORK_IO; + + case KRB5KDC_ERR_CLIENT_REVOKED: + return ERR_ACCOUNT_LOCKED; + + case KRB5KDC_ERR_NAME_EXP: + return ERR_ACCOUNT_EXPIRED; + + case KRB5KDC_ERR_KEY_EXP: + return ERR_CREDS_EXPIRED; + + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + return ERR_AUTH_FAILED; + + /* ERR_CREDS_INVALID is used to indicate to the IPA provider that trying + * password migration would make sense. All Kerberos error codes which can + * be seen while migrating LDAP users to IPA should be added here. */ + case KRB5_PROG_ETYPE_NOSUPP: + case KRB5_PREAUTH_FAILED: + case KRB5KDC_ERR_PREAUTH_FAILED: + return ERR_CREDS_INVALID; + + /* Please do not remove KRB5KRB_ERR_GENERIC here, it is a _generic_ error + * code and we cannot make any assumptions about the reason for the error. + * As a consequence we cannot return a different error code than a generic + * one which unfortunately might result in a unspecific system error + * message to the user. + * + * If there are cases where libkrb5 calls return KRB5KRB_ERR_GENERIC where + * SSSD should behave differently this has to be detected by different + * means, e.g. by evaluation error messages, and then the error code + * should be changed to a more suitable KRB5* error code or immediately to + * an SSSD ERR_* error code to avoid the default handling here. */ + case KRB5KRB_ERR_GENERIC: + default: + return ERR_INTERNAL; + } +} + +static errno_t changepw_child(struct krb5_req *kr, bool prelim) +{ + int ret; + krb5_error_code kerr = 0; + const char *password = NULL; + const char *newpassword = NULL; + int result_code = -1; + krb5_data result_code_string; + krb5_data result_string; + char *user_error_message = NULL; + size_t user_resp_len; + uint8_t *user_resp; + krb5_prompter_fct prompter = NULL; + const char *realm_name; + int realm_length; + size_t msg_len; + uint8_t *msg; + uint32_t user_info_type; + + DEBUG(SSSDBG_TRACE_LIBS, "Password change operation\n"); + + if (sss_authtok_get_type(kr->pd->authtok) == SSS_AUTHTOK_TYPE_PASSWORD) { + ret = sss_authtok_get_password(kr->pd->authtok, &password, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to fetch current password [%d] %s.\n", + ret, strerror(ret)); + return ERR_NO_CREDS; + } + } + + if (!prelim) { + /* We do not need a password expiration warning here. */ + prompter = sss_krb5_prompter; + } + + set_changepw_options(kr->options); + sss_krb5_princ_realm(kr->ctx, kr->princ, &realm_name, &realm_length); + if (realm_length == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_princ_realm failed.\n"); + return ERR_INTERNAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Attempting kinit for realm [%s]\n",realm_name); + kerr = kr->krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ, + password_or_responder(password), + prompter, kr, 0, + SSSD_KRB5_CHANGEPW_PRINCIPAL, + kr->options); + DEBUG(SSSDBG_TRACE_INTERNAL, + "chpass is%s using OTP\n", kr->otp ? "" : " not"); + if (kerr != 0) { + ret = pack_user_info_chpass_error(kr->pd, "Old password not accepted.", + &msg_len, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pack_user_info_chpass_error failed [%d]\n", ret); + } else { + ret = pam_add_response(kr->pd, SSS_PAM_USER_INFO, msg_len, + msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + return kerr; + } + + sss_authtok_set_empty(kr->pd->authtok); + + if (prelim) { + DEBUG(SSSDBG_TRACE_LIBS, + "Initial authentication for change password operation " + "successful.\n"); + krb5_free_cred_contents(kr->ctx, kr->creds); + return EOK; + } + + ret = sss_authtok_get_password(kr->pd->newauthtok, &newpassword, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to fetch new password [%d] %s.\n", + ret, strerror(ret)); + return ERR_NO_CREDS; + } + + memset(&result_code_string, 0, sizeof(krb5_data)); + memset(&result_string, 0, sizeof(krb5_data)); + kerr = krb5_change_password(kr->ctx, kr->creds, + discard_const(newpassword), &result_code, + &result_code_string, &result_string); + + if (kerr == KRB5_KDC_UNREACH) { + return ERR_NETWORK_IO; + } + + if (kerr != 0 || result_code != 0) { + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + } + + if (result_code_string.length > 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "krb5_change_password failed [%d][%.*s].\n", result_code, + result_code_string.length, result_code_string.data); + user_error_message = talloc_strndup(kr->pd, result_code_string.data, + result_code_string.length); + if (user_error_message == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + } + } + + if (result_string.length > 0 && result_string.data[0] != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "krb5_change_password failed [%d][%.*s].\n", result_code, + result_string.length, result_string.data); + talloc_free(user_error_message); + user_error_message = talloc_strndup(kr->pd, result_string.data, + result_string.length); + if (user_error_message == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + } + } else if (result_code == KRB5_KPASSWD_SOFTERROR) { + user_error_message = talloc_strdup(kr->pd, "Please make sure the " + "password meets the complexity constraints."); + if (user_error_message == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + } + } + + if (user_error_message != NULL) { + ret = pack_user_info_chpass_error(kr->pd, user_error_message, + &user_resp_len, &user_resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pack_user_info_chpass_error failed [%d]\n", ret); + } else { + ret = pam_add_response(kr->pd, SSS_PAM_USER_INFO, user_resp_len, + user_resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + } + + return ERR_CHPASS_FAILED; + } + + krb5_free_cred_contents(kr->ctx, kr->creds); + + if (kr->otp == true) { + user_info_type = SSS_PAM_USER_INFO_OTP_CHPASS; + ret = pam_add_response(kr->pd, SSS_PAM_USER_INFO, sizeof(uint32_t), + (const uint8_t *) &user_info_type); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + /* Not fatal */ + } + + sss_authtok_set_empty(kr->pd->newauthtok); + return map_krb5_error(kerr); + } + + /* We changed some of the GIC options for the password change, now we have + * to change them back to get a fresh TGT. */ + revert_changepw_options(kr->cli_opts, kr->options); + + ret = sss_authtok_set_password(kr->pd->authtok, newpassword, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set password for fresh TGT.\n"); + return ret; + } + + kerr = get_and_save_tgt(kr, newpassword); + + sss_authtok_set_empty(kr->pd->authtok); + sss_authtok_set_empty(kr->pd->newauthtok); + + if (kerr == 0) { + kerr = k5c_attach_ccname_msg(kr); + } + return map_krb5_error(kerr); +} + +static errno_t pam_add_prompting(struct krb5_req *kr) +{ + int ret; + + /* add OTP tokeninfo message if available */ + if (kr->otp) { + ret = k5c_attach_otp_info_msg(kr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "k5c_attach_otp_info_msg failed.\n"); + return ret; + } + } + + if (kr->password_prompting) { + ret = pam_add_response(kr->pd, SSS_PASSWORD_PROMPTING, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + return ret; + } + } + + if (kr->pkinit_prompting) { + ret = pam_add_response(kr->pd, SSS_CERT_AUTH_PROMPTING, 0, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + return ret; + } + } + + return EOK; +} + +static errno_t tgt_req_child(struct krb5_req *kr) +{ + const char *password = NULL; + krb5_error_code kerr; + int ret; + + DEBUG(SSSDBG_TRACE_LIBS, "Attempting to get a TGT\n"); + + /* No password is needed for pre-auth or if we have 2FA or SC */ + if (kr->pd->cmd != SSS_PAM_PREAUTH + && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_2FA + && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_2FA_SINGLE + && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_SC_PIN + && sss_authtok_get_type(kr->pd->authtok) + != SSS_AUTHTOK_TYPE_SC_KEYPAD) { + ret = sss_authtok_get_password(kr->pd->authtok, &password, NULL); + switch (ret) { + case EOK: + break; + + case EACCES: + DEBUG(SSSDBG_OP_FAILURE, "Invalid authtok type\n"); + return ERR_INVALID_CRED_TYPE; + break; + + default: + DEBUG(SSSDBG_OP_FAILURE, "No credentials available\n"); + return ERR_NO_CREDS; + break; + } + } + + kerr = get_and_save_tgt(kr, password); + + if (kerr != KRB5KDC_ERR_KEY_EXP) { + if (kr->pd->cmd == SSS_PAM_PREAUTH) { + ret = pam_add_prompting(kr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_prompting failed.\n"); + goto done; + } + } else { + if (kerr == 0) { + kerr = k5c_attach_ccname_msg(kr); + } + } + ret = map_krb5_error(kerr); + goto done; + } + + /* If the password is expired, the KDC will always return + KRB5KDC_ERR_KEY_EXP regardless if the supplied password is correct or + not. In general the password can still be used to get a changepw ticket. + So we validate the password by trying to get a changepw ticket. */ + DEBUG(SSSDBG_TRACE_LIBS, "Password was expired\n"); + kerr = sss_krb5_get_init_creds_opt_set_expire_callback(kr->ctx, + kr->options, + NULL, NULL); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to unset expire callback, continue ...\n"); + } + + set_changepw_options(kr->options); + kerr = kr->krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ_orig, + password_or_responder(password), + sss_krb5_prompter, kr, 0, + SSSD_KRB5_CHANGEPW_PRINCIPAL, + kr->options); + + krb5_free_cred_contents(kr->ctx, kr->creds); + + if (kr->pd->cmd == SSS_PAM_PREAUTH) { + /* Any errors are ignored during pre-auth, only data is collected to + * be send back to the client. Even if the password is expired we + * should now know which authentication methods are available to + * update the password. */ + DEBUG(SSSDBG_TRACE_FUNC, + "krb5_get_init_creds_password returned [%d] during pre-auth, " + "ignored.\n", kerr); + ret = pam_add_prompting(kr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_prompting failed.\n"); + goto done; + } + goto done; + } + + if (kerr == 0) { + ret = ERR_CREDS_EXPIRED; + + /* If the password is expired, we can safely remove the ccache from the + * cache and disk if it is not actively used anymore. This will allow + * to create a new random ccache if sshd with privilege separation is + * used. */ + if (kr->old_cc_active == false && kr->old_ccname) { + ret = safe_remove_old_ccache_file(kr->old_ccname, NULL, + kr->uid, kr->gid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to remove old ccache file [%s], " + "please remove it manually.\n", kr->old_ccname); + } + ret = ERR_CREDS_EXPIRED_CCACHE; + } + } else { + ret = map_krb5_error(kerr); + } + +done: + sss_authtok_set_empty(kr->pd->authtok); + return ret; +} + +static errno_t kuserok_child(struct krb5_req *kr) +{ + krb5_boolean access_allowed; + krb5_error_code kerr; + + DEBUG(SSSDBG_TRACE_LIBS, "Verifying if principal can log in as user\n"); + + /* krb5_kuserok tries to verify that kr->pd->user is a locally known + * account, so we have to unset _SSS_LOOPS to make getpwnam() work. */ + if (unsetenv("_SSS_LOOPS") != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to unset _SSS_LOOPS, " + "krb5_kuserok will most certainly fail.\n"); + } + + kerr = krb5_set_default_realm(kr->ctx, kr->realm); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_set_default_realm failed, " + "krb5_kuserok may fail.\n"); + } + + access_allowed = krb5_kuserok(kr->ctx, kr->princ, kr->pd->user); + DEBUG(SSSDBG_TRACE_LIBS, + "Access was %s\n", access_allowed ? "allowed" : "denied"); + + if (access_allowed) { + return EOK; + } + + return ERR_AUTH_DENIED; +} + +static errno_t renew_tgt_child(struct krb5_req *kr) +{ + const char *ccname; + krb5_ccache ccache = NULL; + krb5_error_code kerr; + int ret; + + DEBUG(SSSDBG_TRACE_LIBS, "Renewing a ticket\n"); + + ret = sss_authtok_get_ccfile(kr->pd->authtok, &ccname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unsupported authtok type for TGT renewal [%d].\n", + sss_authtok_get_type(kr->pd->authtok)); + return ERR_INVALID_CRED_TYPE; + } + + kerr = krb5_cc_resolve(kr->ctx, ccname, &ccache); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + + kerr = krb5_get_renewed_creds(kr->ctx, kr->creds, kr->princ, ccache, NULL); + if (kerr != 0) { + goto done; + } + + if (kr->validate) { + kerr = validate_tgt(kr); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "TGT validation is disabled.\n"); + } + + kerr = krb5_cc_initialize(kr->ctx, ccache, kr->princ); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + + kerr = krb5_cc_store_cred(kr->ctx, ccache, kr->creds); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + goto done; + } + + kerr = add_ticket_times_and_upn_to_response(kr); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "add_ticket_times_and_upn_to_response failed.\n"); + } + + kerr = k5c_attach_ccname_msg(kr); + +done: + krb5_free_cred_contents(kr->ctx, kr->creds); + + if (ccache != NULL) { + krb5_cc_close(kr->ctx, ccache); + } + + if (kerr == KRB5KRB_AP_ERR_TKT_EXPIRED) { + DEBUG(SSSDBG_TRACE_LIBS, + "Attempted to renew an expired TGT, changing the error code " + "to expired creds internally\n"); + /* map_krb5_error() won't touch the SSSD-internal code */ + kerr = ERR_CREDS_EXPIRED; + } + + return map_krb5_error(kerr); +} + +static errno_t create_empty_ccache(struct krb5_req *kr) +{ + krb5_creds *creds = NULL; + krb5_error_code kerr; + + if (kr->old_cc_valid == false) { + DEBUG(SSSDBG_TRACE_LIBS, "Creating empty ccache\n"); + kerr = create_empty_cred(kr->ctx, kr->princ, &creds); + if (kerr == 0) { + kerr = create_ccache(kr->ccname, creds); + } + } else { + DEBUG(SSSDBG_TRACE_LIBS, "Existing ccache still valid, reusing\n"); + kerr = 0; + } + + if (kerr == 0) { + kerr = k5c_attach_ccname_msg(kr); + } + + krb5_free_creds(kr->ctx, creds); + + return map_krb5_error(kerr); +} + +static errno_t unpack_authtok(struct sss_auth_token *tok, + uint8_t *buf, size_t size, size_t *p) +{ + uint32_t auth_token_type; + uint32_t auth_token_length; + errno_t ret = EOK; + + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, buf + *p, size, p); + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_length, buf + *p, size, p); + if (auth_token_length > (size - *p)) { + return EINVAL; + } + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_set_password(tok, (char *)(buf + *p), 0); + break; + case SSS_AUTHTOK_TYPE_CCFILE: + ret = sss_authtok_set_ccfile(tok, (char *)(buf + *p), 0); + break; + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + ret = sss_authtok_set_2fa_single(tok, (char *)(buf + *p), 0); + break; + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + ret = sss_authtok_set(tok, auth_token_type, (buf + *p), + auth_token_length); + break; + default: + return EINVAL; + } + + if (ret == EOK) { + *p += auth_token_length; + } + return ret; +} + +static const char *krb5_child_command_to_str(int cmd) +{ + switch (cmd) { + case SSS_PAM_AUTHENTICATE: + return "auth"; + case SSS_PAM_CHAUTHTOK: + return "password change"; + case SSS_PAM_CHAUTHTOK_PRELIM: + return "password change checks"; + case SSS_PAM_ACCT_MGMT: + return "account management"; + case SSS_CMD_RENEW: + return "ticket renewal"; + case SSS_PAM_PREAUTH: + return "pre-auth"; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected command %d\n", cmd); + return "-unexpected-"; +} + +static errno_t unpack_buffer(uint8_t *buf, size_t size, + struct krb5_req *kr, uint32_t *offline) +{ + size_t p = 0; + uint32_t len; + uint32_t validate; + uint32_t posix_domain; + uint32_t send_pac; + uint32_t use_enterprise_princ; + struct pam_data *pd; + errno_t ret; + + DEBUG(SSSDBG_TRACE_LIBS, "total buffer size: [%zu]\n", size); + + if (!offline || !kr) return EINVAL; + + pd = create_pam_data(kr); + if (pd == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "create_pam_data failed.\n"); + return ENOMEM; + } + kr->pd = pd; + + SAFEALIGN_COPY_UINT32_CHECK(&pd->cmd, buf + p, size, &p); + SAFEALIGN_COPY_UINT32_CHECK(&kr->uid, buf + p, size, &p); + SAFEALIGN_COPY_UINT32_CHECK(&kr->gid, buf + p, size, &p); + SAFEALIGN_COPY_UINT32_CHECK(&validate, buf + p, size, &p); + kr->validate = (validate == 0) ? false : true; + SAFEALIGN_COPY_UINT32_CHECK(&posix_domain, buf + p, size, &p); + kr->posix_domain = (posix_domain == 0) ? false : true; + SAFEALIGN_COPY_UINT32_CHECK(offline, buf + p, size, &p); + SAFEALIGN_COPY_UINT32_CHECK(&send_pac, buf + p, size, &p); + kr->send_pac = (send_pac == 0) ? false : true; + SAFEALIGN_COPY_UINT32_CHECK(&use_enterprise_princ, buf + p, size, &p); + kr->use_enterprise_princ = (use_enterprise_princ == 0) ? false : true; + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + if (len > size - p) return EINVAL; + kr->upn = talloc_strndup(kr, (char *)(buf + p), len); + if (kr->upn == NULL) return ENOMEM; + p += len; + + DEBUG(SSSDBG_CONF_SETTINGS, + "cmd [%d (%s)] uid [%llu] gid [%llu] validate [%s] " + "enterprise principal [%s] offline [%s] UPN [%s]\n", + pd->cmd, krb5_child_command_to_str(pd->cmd), + (unsigned long long) kr->uid, (unsigned long long) kr->gid, + kr->validate ? "true" : "false", + kr->use_enterprise_princ ? "true" : "false", + *offline ? "true" : "false", kr->upn ? kr->upn : "none"); + + if (pd->cmd == SSS_PAM_AUTHENTICATE || + pd->cmd == SSS_PAM_PREAUTH || + pd->cmd == SSS_CMD_RENEW || + pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM || pd->cmd == SSS_PAM_CHAUTHTOK) { + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + if (len > size - p) return EINVAL; + kr->ccname = talloc_strndup(kr, (char *)(buf + p), len); + if (kr->ccname == NULL) return ENOMEM; + p += len; + + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + if (len > size - p) return EINVAL; + + if (len > 0) { + kr->old_ccname = talloc_strndup(kr, (char *)(buf + p), len); + if (kr->old_ccname == NULL) return ENOMEM; + p += len; + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, "No old ccache\n"); + } + + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + if (len > size - p) return EINVAL; + + if (len > 0) { + kr->keytab = talloc_strndup(kr, (char *)(buf + p), len); + p += len; + } else { + kr->keytab = NULL; + } + + ret = unpack_authtok(pd->authtok, buf, size, &p); + if (ret) { + return ret; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "ccname: [%s] old_ccname: [%s] keytab: [%s]\n", + kr->ccname, + kr->old_ccname ? kr->old_ccname : "not set", + kr->keytab ? kr->keytab : "not set"); + } else { + kr->ccname = NULL; + kr->old_ccname = NULL; + kr->keytab = NULL; + sss_authtok_set_empty(pd->authtok); + } + + if (pd->cmd == SSS_PAM_CHAUTHTOK) { + ret = unpack_authtok(pd->newauthtok, buf, size, &p); + if (ret) { + return ret; + } + } else { + sss_authtok_set_empty(pd->newauthtok); + } + + if (pd->cmd == SSS_PAM_ACCT_MGMT) { + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + if (len > size - p) return EINVAL; + pd->user = talloc_strndup(pd, (char *)(buf + p), len); + if (pd->user == NULL) return ENOMEM; + p += len; + DEBUG(SSSDBG_CONF_SETTINGS, "user: [%s]\n", pd->user); + } else { + pd->user = NULL; + } + + return EOK; +} + +static int krb5_cleanup(struct krb5_req *kr) +{ + if (kr == NULL) return EOK; + + if (kr->options != NULL) { + sss_krb5_get_init_creds_opt_free(kr->ctx, kr->options); + } + + if (kr->creds != NULL) { + krb5_free_cred_contents(kr->ctx, kr->creds); + krb5_free_creds(kr->ctx, kr->creds); + } + if (kr->name != NULL) + sss_krb5_free_unparsed_name(kr->ctx, kr->name); + if (kr->princ != NULL) + krb5_free_principal(kr->ctx, kr->princ); + if (kr->princ_orig != NULL) + krb5_free_principal(kr->ctx, kr->princ_orig); + if (kr->ctx != NULL) + krb5_free_context(kr->ctx); + + memset(kr, 0, sizeof(struct krb5_req)); + + return EOK; +} + +static krb5_error_code get_tgt_times(krb5_context ctx, const char *ccname, + krb5_principal server_principal, + krb5_principal client_principal, + sss_krb5_ticket_times *tgtt) +{ + krb5_error_code krberr; + krb5_ccache ccache = NULL; + krb5_creds mcred; + krb5_creds cred; + + krberr = krb5_cc_resolve(ctx, ccname, &ccache); + if (krberr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_resolve failed.\n"); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, krberr); + goto done; + } + + memset(&mcred, 0, sizeof(mcred)); + memset(&cred, 0, sizeof(mcred)); + + mcred.server = server_principal; + mcred.client = client_principal; + + krberr = krb5_cc_retrieve_cred(ctx, ccache, 0, &mcred, &cred); + if (krberr == KRB5_FCC_NOFILE) { + DEBUG(SSSDBG_TRACE_LIBS, "FAST ccache must be recreated\n"); + } else if (krberr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_retrieve_cred failed\n"); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, krberr); + krberr = 0; + goto done; + } + + tgtt->authtime = cred.times.authtime; + tgtt->starttime = cred.times.starttime; + tgtt->endtime = cred.times.endtime; + tgtt->renew_till = cred.times.renew_till; + + krb5_free_cred_contents(ctx, &cred); + + krberr = 0; + +done: + if (ccache != NULL) { + krb5_cc_close(ctx, ccache); + } + + return krberr; +} + +static krb5_error_code get_fast_ccache_with_anonymous_pkinit(krb5_context ctx, + uid_t fast_uid, + gid_t fast_gid, + bool posix_domain, + struct cli_opts *cli_opts, + krb5_keytab keytab, + krb5_principal client_princ, + char *ccname, + const char *realm) +{ + krb5_error_code kerr; + krb5_get_init_creds_opt *options; + struct sss_creds *saved_creds = NULL; + krb5_preauthtype pkinit = KRB5_PADATA_PK_AS_REQ; + krb5_creds creds = { 0 }; + + kerr = sss_krb5_get_init_creds_opt_alloc(ctx, &options); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + + krb5_get_init_creds_opt_set_tkt_life(options, 10 * 60); + krb5_get_init_creds_opt_set_renew_life(options, 0); + krb5_get_init_creds_opt_set_forwardable(options, 0); + krb5_get_init_creds_opt_set_proxiable(options, 0); + krb5_get_init_creds_opt_set_canonicalize(options, 1); + krb5_get_init_creds_opt_set_preauth_list(options, &pkinit, 1); + + kerr = krb5_build_principal(ctx, &creds.server, strlen(realm), realm, + KRB5_TGS_NAME, realm, NULL); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create principal.\n"); + goto done; + } + + creds.client = client_princ; + + kerr = krb5_get_init_creds_password(ctx, &creds, client_princ, NULL, + sss_krb5_prompter, NULL, 0, NULL, + options); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get FAST credential with anonymous PKINIT.\n"); + goto done; + } + + kerr = switch_creds(NULL, fast_uid, fast_gid, 0, NULL, &saved_creds); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to switch credentials to store FAST ccache with " + "expected permissions.\n"); + goto done; + } + + kerr = create_ccache(ccname, &creds); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store FAST ccache.\n"); + goto done; + } + + kerr = restore_creds(saved_creds); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to restore credentials, krb5_child might run with wrong " + "permissions, aborting.\n"); + goto done; + } + +done: + sss_krb5_get_init_creds_opt_free(ctx, options); + talloc_free(saved_creds); + + return kerr; +} + +static krb5_error_code get_fast_ccache_with_keytab(krb5_context ctx, + uid_t fast_uid, + gid_t fast_gid, + bool posix_domain, + struct cli_opts *cli_opts, + krb5_keytab keytab, + krb5_principal client_princ, + char *ccname) +{ + krb5_error_code kerr; + pid_t fchild_pid; + int status; + + fchild_pid = fork(); + switch (fchild_pid) { + case -1: + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed\n"); + return EIO; + case 0: + /* Child */ + debug_prg_name = talloc_asprintf(NULL, "krb5_child[%d]", getpid()); + if (debug_prg_name == NULL) { + debug_prg_name = "krb5_child"; + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + /* Try to carry on */ + } + + kerr = k5c_become_user(fast_uid, fast_gid, posix_domain); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "become_user failed: %d\n", kerr); + exit(1); + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "Running as [%"SPRIuid"][%"SPRIgid"].\n", geteuid(), getegid()); + + kerr = get_and_save_tgt_with_keytab(ctx, cli_opts, client_princ, + keytab, ccname); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "get_and_save_tgt_with_keytab failed: %d\n", kerr); + exit(2); + } + exit(0); + default: + /* Parent */ + do { + errno = 0; + kerr = waitpid(fchild_pid, &status, 0); + } while (kerr == -1 && errno == EINTR); + + if (kerr > 0) { + if (WIFEXITED(status)) { + kerr = WEXITSTATUS(status); + /* Don't blindly fail if the child fails, but check + * the ccache again */ + if (kerr != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Creating FAST ccache failed, krb5_child will " + "likely fail!\n"); + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "krb5_child subprocess %d terminated unexpectedly\n", + fchild_pid); + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to wait for child %d\n", fchild_pid); + /* Let the code re-check the TGT times and fail if we + * can't find the updated principal */ + } + } + + return 0; +} + +static krb5_error_code check_fast_ccache(TALLOC_CTX *mem_ctx, + krb5_context ctx, + uid_t fast_uid, + gid_t fast_gid, + bool posix_domain, + struct cli_opts *cli_opts, + const char *primary, + const char *realm, + const char *keytab_name, + char **fast_ccname) +{ + TALLOC_CTX *tmp_ctx = NULL; + krb5_error_code kerr; + char *ccname; + char *server_name; + sss_krb5_ticket_times tgtt; + krb5_keytab keytab = NULL; + krb5_principal client_princ = NULL; + krb5_principal server_princ = NULL; + krb5_principal client_search_princ = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ccname = talloc_asprintf(tmp_ctx, "FILE:%s/fast_ccache_%s", DB_PATH, realm); + if (ccname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + kerr = ENOMEM; + goto done; + } + + if (cli_opts->fast_use_anonymous_pkinit) { + kerr = krb5_build_principal(ctx, &client_princ, strlen(realm), realm, + KRB5_WELLKNOWN_NAMESTR, + KRB5_ANONYMOUS_PRINCSTR, NULL); + if (kerr != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to create anonymous PKINIT principal.\n"); + goto done; + } + + /* Anonymous pkinit is using the canonical principal + * WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS so we need an additional + * client_search_princ to find it in the ccache to determine the + * lifetime. */ + kerr = krb5_build_principal(ctx, &client_search_princ, + strlen(KRB5_ANONYMOUS_REALMSTR), + KRB5_ANONYMOUS_REALMSTR, + KRB5_WELLKNOWN_NAMESTR, + KRB5_ANONYMOUS_PRINCSTR, NULL); + if (kerr != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to create anonymous PKINIT principal.\n"); + goto done; + } + } else { + if (keytab_name != NULL) { + kerr = krb5_kt_resolve(ctx, keytab_name, &keytab); + } else { + kerr = krb5_kt_default(ctx, &keytab); + } + if (kerr) { + const char *__err_msg = sss_krb5_get_error_message(ctx, kerr); + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to read keytab file [%s]: %s\n", + sss_printable_keytab_name(ctx, keytab_name), + __err_msg); + sss_krb5_free_error_message(ctx, __err_msg); + goto done; + } + + kerr = find_principal_in_keytab(ctx, keytab, primary, realm, &client_princ); + if (kerr != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "find_principal_in_keytab failed for principal %s@%s.\n", + primary, realm); + goto done; + } + } + + server_name = talloc_asprintf(tmp_ctx, "krbtgt/%s@%s", realm, realm); + if (server_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + kerr = ENOMEM; + goto done; + } + + kerr = krb5_parse_name(ctx, server_name, &server_princ); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_parse_name failed.\n"); + goto done; + } + + memset(&tgtt, 0, sizeof(tgtt)); + kerr = get_tgt_times(ctx, ccname, server_princ, + client_search_princ != NULL ? client_search_princ + : client_princ, + &tgtt); + if (kerr == 0) { + if (tgtt.endtime > time(NULL)) { + DEBUG(SSSDBG_FUNC_DATA, "FAST TGT is still valid.\n"); + goto done; + } + } + + /* Need to recreate the FAST ccache */ + if (cli_opts->fast_use_anonymous_pkinit) { + kerr = get_fast_ccache_with_anonymous_pkinit(ctx, fast_uid, fast_gid, + posix_domain, cli_opts, + keytab, client_princ, + ccname, realm); + if (kerr != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "Creating FAST ccache with anonymous " + "PKINIT failed, krb5_child will " + "likely fail!\n"); + } + } else { + kerr = get_fast_ccache_with_keytab(ctx, fast_uid, fast_gid, posix_domain, + cli_opts, keytab, client_princ, ccname); + if (kerr != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "Creating FAST ccache with keytab failed, " + "krb5_child will likely fail!\n"); + } + } + + /* Check the ccache times again. Should be updated ... */ + memset(&tgtt, 0, sizeof(tgtt)); + kerr = get_tgt_times(ctx, ccname, server_princ, + client_search_princ != NULL ? client_search_princ + : client_princ, + &tgtt); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "get_tgt_times() failed\n"); + goto done; + } + + if (tgtt.endtime < time(NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "FAST TGT was renewed but is already expired, please check that " + "time is synchronized with server.\n"); + kerr = ERR_CREDS_EXPIRED; + goto done; + } + DEBUG(SSSDBG_FUNC_DATA, "FAST TGT was successfully recreated!\n"); + +done: + if (client_princ != NULL) { + krb5_free_principal(ctx, client_princ); + } + if (client_search_princ != NULL) { + krb5_free_principal(ctx, client_search_princ); + } + if (server_princ != NULL) { + krb5_free_principal(ctx, server_princ); + } + + if (kerr == 0) { + *fast_ccname = talloc_steal(mem_ctx, ccname); + } + talloc_free(tmp_ctx); + + if (keytab != NULL) { + krb5_kt_close(ctx, keytab); + } + + return kerr; +} + +static errno_t k5c_recv_data(struct krb5_req *kr, int fd, uint32_t *offline) +{ + uint8_t buf[IN_BUF_SIZE]; + ssize_t len; + errno_t ret; + + errno = 0; + len = sss_atomic_read_safe_s(fd, buf, IN_BUF_SIZE, NULL); + if (len == -1) { + ret = errno; + ret = (ret == 0) ? EINVAL: ret; + DEBUG(SSSDBG_CRIT_FAILURE, + "read failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + ret = unpack_buffer(buf, len, kr, offline); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "unpack_buffer failed.\n"); + } + + return ret; +} + +static int k5c_setup_fast(struct krb5_req *kr, bool demand) +{ + krb5_principal fast_princ_struct; + krb5_data *realm_data; + char *fast_principal_realm; + char *fast_principal; + krb5_error_code kerr; + char *tmp_str = NULL; + char *new_ccname; + + if (kr->cli_opts->fast_principal) { + DEBUG(SSSDBG_CONF_SETTINGS, "Fast principal is set to [%s]\n", + kr->cli_opts->fast_principal); + kerr = krb5_parse_name(kr->ctx, kr->cli_opts->fast_principal, + &fast_princ_struct); + if (kerr) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_parse_name failed.\n"); + return kerr; + } + kerr = sss_krb5_unparse_name_flags(kr->ctx, fast_princ_struct, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, + &tmp_str); + if (kerr) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_unparse_name_flags failed.\n"); + return kerr; + } + fast_principal = talloc_strdup(kr, tmp_str); + if (!fast_principal) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + return KRB5KRB_ERR_GENERIC; + } + free(tmp_str); + realm_data = krb5_princ_realm(kr->ctx, fast_princ_struct); + fast_principal_realm = talloc_asprintf(kr, "%.*s", realm_data->length, + realm_data->data); + if (!fast_principal_realm) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + } else { + fast_principal_realm = kr->realm; + fast_principal = NULL; + } + + kerr = check_fast_ccache(kr, kr->ctx, kr->fast_uid, kr->fast_gid, + kr->posix_domain, kr->cli_opts, + fast_principal, fast_principal_realm, + kr->keytab, &kr->fast_ccname); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "check_fast_ccache failed.\n"); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + + kerr = copy_ccache_into_memory(kr, kr->ctx, kr->fast_ccname, &new_ccname); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "copy_ccache_into_memory failed.\n"); + return kerr; + } + + talloc_free(kr->fast_ccname); + kr->fast_ccname = new_ccname; + + kerr = sss_krb5_get_init_creds_opt_set_fast_ccache_name(kr->ctx, + kr->options, + kr->fast_ccname); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_krb5_get_init_creds_opt_set_fast_ccache_name " + "failed.\n"); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + + if (demand) { + kerr = sss_krb5_get_init_creds_opt_set_fast_flags(kr->ctx, + kr->options, + SSS_KRB5_FAST_REQUIRED); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_krb5_get_init_creds_opt_set_fast_flags " + "failed.\n"); + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + } + + return EOK; +} + +static errno_t check_use_fast(const char *use_fast_str, + enum k5c_fast_opt *_fast_val) +{ + enum k5c_fast_opt fast_val; + + if (use_fast_str == NULL || strcasecmp(use_fast_str, "never") == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "Not using FAST.\n"); + fast_val = K5C_FAST_NEVER; + } else if (strcasecmp(use_fast_str, "try") == 0) { + fast_val = K5C_FAST_TRY; + } else if (strcasecmp(use_fast_str, "demand") == 0) { + fast_val = K5C_FAST_DEMAND; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported value [%s] for krb5_use_fast.\n", + use_fast_str); + return EINVAL; + } + + *_fast_val = fast_val; + return EOK; +} + +static errno_t old_ccache_valid(struct krb5_req *kr, bool *_valid) +{ + errno_t ret; + bool valid; + + valid = false; + + ret = sss_krb5_cc_verify_ccache(kr->old_ccname, + kr->uid, kr->gid, + kr->realm, kr->upn); + switch (ret) { + case ERR_NOT_FOUND: + case ENOENT: + DEBUG(SSSDBG_TRACE_FUNC, + "Saved ccache %s doesn't exist, ignoring\n", kr->old_ccname); + break; + case EINVAL: + /* cache found but no TGT or expired */ + case EOK: + valid = true; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "Cannot check if saved ccache %s is valid\n", + kr->old_ccname); + return ret; + } + + *_valid = valid; + return EOK; +} + +static int k5c_check_old_ccache(struct krb5_req *kr) +{ + errno_t ret; + + if (kr->old_ccname) { + ret = old_ccache_valid(kr, &kr->old_cc_valid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "old_ccache_valid failed.\n"); + return ret; + } + + ret = check_if_uid_is_active(kr->uid, &kr->old_cc_active); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "check_if_uid_is_active failed.\n"); + return ret; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Ccache_file is [%s] and is %s active and TGT is %s valid.\n", + kr->old_ccname ? kr->old_ccname : "not set", + kr->old_cc_active ? "" : "not", + kr->old_cc_valid ? "" : "not"); + } + + return EOK; +} + +static int k5c_precreate_ccache(struct krb5_req *kr, uint32_t offline) +{ + errno_t ret; + + /* The ccache file should be (re)created if one of the following conditions + * is true: + * - it doesn't exist (kr->old_ccname == NULL) + * - the backend is online and the current ccache file is not used, i.e + * the related user is currently not logged in and it is not a renewal + * request + * (offline && !kr->old_cc_active && kr->pd->cmd != SSS_CMD_RENEW) + * - the backend is offline and the current cache file not used and + * it does not contain a valid TGT + * (offline && !kr->old_cc_active && !kr->valid_tgt) + */ + if (kr->old_ccname == NULL || + (offline && !kr->old_cc_active && !kr->old_cc_valid) || + (!offline && !kr->old_cc_active && kr->pd->cmd != SSS_CMD_RENEW)) { + DEBUG(SSSDBG_TRACE_ALL, "Recreating ccache\n"); + + ret = sss_krb5_precreate_ccache(kr->ccname, kr->uid, kr->gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ccache creation failed.\n"); + return ret; + } + } else { + /* We can reuse the old ccache */ + kr->ccname = kr->old_ccname; + } + + return EOK; +} + +static int k5c_ccache_setup(struct krb5_req *kr, uint32_t offline) +{ + errno_t ret; + + if (kr->pd->cmd == SSS_PAM_ACCT_MGMT) { + return EOK; + } + + ret = k5c_check_old_ccache(kr); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot check old ccache [%s]: [%d][%s]. " \ + "Assuming old cache is invalid " \ + "and not used.\n", + kr->old_ccname, ret, sss_strerror(ret)); + } + + /* Pre-creating the ccache must be done as root, otherwise we can't mkdir + * some of the DIR: cache components. One example is /run/user/$UID because + * logind doesn't create the directory until the session phase, whereas + * we need the directory during the auth phase already + */ + ret = k5c_precreate_ccache(kr, offline); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot precreate ccache\n"); + return ret; + } + + return EOK; +} + +static int k5c_setup(struct krb5_req *kr, uint32_t offline) +{ + krb5_error_code kerr; + int parse_flags; + + /* Set the global error context */ + krb5_error_ctx = kr->ctx; + + if (debug_level & SSSDBG_TRACE_ALL) { + kerr = sss_child_set_krb5_tracing(kr->ctx); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_MINOR_FAILURE, kerr); + return EIO; + } + } + + /* Enterprise principals require that a default realm is available. To + * make SSSD more robust in the case that the default realm option is + * missing in krb5.conf or to allow SSSD to work with multiple unconnected + * realms (e.g. AD domains without trust between them) the default realm + * will be set explicitly. */ + if (kr->use_enterprise_princ) { + kerr = krb5_set_default_realm(kr->ctx, kr->realm); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_set_default_realm failed.\n"); + } + } + + parse_flags = kr->use_enterprise_princ ? KRB5_PRINCIPAL_PARSE_ENTERPRISE : 0; + kerr = sss_krb5_parse_name_flags(kr->ctx, kr->upn, parse_flags, &kr->princ); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + + kerr = krb5_parse_name(kr->ctx, kr->upn, &kr->princ_orig); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + + kerr = krb5_unparse_name(kr->ctx, kr->princ, &kr->name); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + + kr->creds = calloc(1, sizeof(krb5_creds)); + if (kr->creds == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "calloc failed.\n"); + return ENOMEM; + } + +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER + kerr = krb5_get_init_creds_opt_set_responder(kr->ctx, kr->options, + sss_krb5_responder, kr); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } +#endif + +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_CHANGE_PASSWORD_PROMPT + /* A prompter is used to catch messages about when a password will + * expire. The library shall not use the prompter to ask for a new password + * but shall return KRB5KDC_ERR_KEY_EXP. */ + krb5_get_init_creds_opt_set_change_password_prompt(kr->options, 0); +#endif + + kerr = set_lifetime_options(kr->cli_opts, kr->options); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "set_lifetime_options failed.\n"); + return kerr; + } + + if (!offline) { + set_canonicalize_option(kr->cli_opts, kr->options); + } + +/* TODO: set options, e.g. + * krb5_get_init_creds_opt_set_forwardable + * krb5_get_init_creds_opt_set_proxiable + * krb5_get_init_creds_opt_set_etype_list + * krb5_get_init_creds_opt_set_address_list + * krb5_get_init_creds_opt_set_preauth_list + * krb5_get_init_creds_opt_set_salt + * krb5_get_init_creds_opt_set_change_password_prompt + * krb5_get_init_creds_opt_set_pa + */ + + return kerr; +} + +static krb5_error_code check_keytab_name(struct krb5_req *kr) +{ + krb5_error_code kerr; + char krb5_conf_keytab[MAX_KEYTAB_NAME_LEN]; + char *path_start = NULL; + + if (kr->keytab == NULL && ( + kr->pd->cmd == SSS_PAM_AUTHENTICATE || + kr->pd->cmd == SSS_PAM_PREAUTH || + kr->pd->cmd == SSS_CMD_RENEW || + kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM || + kr->pd->cmd == SSS_PAM_CHAUTHTOK)) { + + DEBUG(SSSDBG_TRACE_FUNC, + "Missing krb5_keytab option for domain, looking for default one\n"); + + kerr = krb5_kt_default_name(kr->ctx, krb5_conf_keytab, sizeof(krb5_conf_keytab)); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to get default keytab location from krb.conf\n"); + return kerr; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "krb5_kt_default_name() returned: %s\n", + krb5_conf_keytab); + + /* krb5_kt_default_name() can return file path with "FILE:" prefix, + it need to be removed */ + if (0 == strncmp(krb5_conf_keytab, "FILE:", strlen("FILE:"))) { + path_start = krb5_conf_keytab + strlen("FILE:"); + } else { + path_start = krb5_conf_keytab; + } + + kr->keytab = talloc_strndup(kr->pd, path_start, strlen(path_start)); + + DEBUG(SSSDBG_TRACE_FUNC, "krb5_child will default to: %s\n", path_start); + } + + return 0; +} + +static krb5_error_code privileged_krb5_setup(struct krb5_req *kr, + uint32_t offline) +{ + krb5_error_code kerr; + int ret; + char *mem_keytab; + + kr->realm = kr->cli_opts->realm; + if (kr->realm == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Realm not available.\n"); + } + + kerr = krb5_init_context(&kr->ctx); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + + kerr = check_keytab_name(kr); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + + kerr = sss_krb5_get_init_creds_opt_alloc(kr->ctx, &kr->options); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } + + ret = check_use_fast(kr->cli_opts->use_fast_str, &kr->fast_val); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "check_use_fast failed.\n"); + return ret; + } + + /* For ccache types FILE: and DIR: we might need to create some directory + * components as root. Cache files are not needed during preauth. */ + if (kr->pd->cmd != SSS_PAM_PREAUTH) { + ret = k5c_ccache_setup(kr, offline); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "k5c_ccache_setup failed.\n"); + return ret; + } + } + + if (!(offline || + (kr->fast_val == K5C_FAST_NEVER && kr->validate == false))) { + /* A Keytab is not used if fast with anonymous pkinit is used (and validate is false)*/ + if (!(kr->cli_opts->fast_use_anonymous_pkinit == true && kr->validate == false)) { + kerr = copy_keytab_into_memory(kr, kr->ctx, kr->keytab, &mem_keytab, + NULL); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "copy_keytab_into_memory failed.\n"); + return kerr; + } + + talloc_free(kr->keytab); + kr->keytab = mem_keytab; + } + + if (kr->fast_val != K5C_FAST_NEVER) { + kerr = k5c_setup_fast(kr, kr->fast_val == K5C_FAST_DEMAND); + if (kerr != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set up FAST\n"); + return kerr; + } + } + } + + if (kr->send_pac) { + ret = sss_pac_check_and_open(); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot open the PAC responder socket\n"); + /* Not fatal */ + } + } + + return 0; +} + +static void try_open_krb5_conf(void) +{ + int fd; + int ret; + + fd = open("/etc/krb5.conf", O_RDONLY); + if (fd != -1) { + close(fd); + } else { + ret = errno; + if (ret == EACCES || ret == EPERM) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User with uid:%"SPRIuid" gid:%"SPRIgid" cannot read " + "/etc/krb5.conf. It might cause problems\n", + geteuid(), getegid()); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot open /etc/krb5.conf [%d]: %s\n", + ret, strerror(ret)); + } + } +} + +int main(int argc, const char *argv[]) +{ + struct krb5_req *kr = NULL; + uint32_t offline; + int opt; + poptContext pc; + int dumpable = 1; + int debug_fd = -1; + const char *opt_logger = NULL; + errno_t ret; + krb5_error_code kerr; + uid_t fast_uid = 0; + gid_t fast_gid = 0; + long chain_id = 0; + struct cli_opts cli_opts = { 0 }; + int sss_creds_password = 0; + long dummy_long = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, + _("Allow core dumps"), NULL }, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + SSSD_LOGGER_OPTS + {CHILD_OPT_FAST_CCACHE_UID, 0, POPT_ARG_INT, &fast_uid, 0, + _("The user to create FAST ccache as"), NULL}, + {CHILD_OPT_FAST_CCACHE_GID, 0, POPT_ARG_INT, &fast_gid, 0, + _("The group to create FAST ccache as"), NULL}, + {CHILD_OPT_FAST_USE_ANONYMOUS_PKINIT, 0, POPT_ARG_NONE, NULL, 'A', + _("Use anonymous PKINIT to request FAST armor ticket"), NULL}, + {CHILD_OPT_REALM, 0, POPT_ARG_STRING, &cli_opts.realm, 0, + _("Kerberos realm to use"), NULL}, + {CHILD_OPT_LIFETIME, 0, POPT_ARG_STRING, &cli_opts.lifetime, 0, + _("Requested lifetime of the ticket"), NULL}, + {CHILD_OPT_RENEWABLE_LIFETIME, 0, POPT_ARG_STRING, &cli_opts.rtime, 0, + _("Requested renewable lifetime of the ticket"), NULL}, + {CHILD_OPT_USE_FAST, 0, POPT_ARG_STRING, &cli_opts.use_fast_str, 0, + _("FAST options ('never', 'try', 'demand')"), NULL}, + {CHILD_OPT_FAST_PRINCIPAL, 0, POPT_ARG_STRING, + &cli_opts.fast_principal, 0, + _("Specifies the server principal to use for FAST"), NULL}, + {CHILD_OPT_CANONICALIZE, 0, POPT_ARG_NONE, NULL, 'C', + _("Requests canonicalization of the principal name"), NULL}, + {CHILD_OPT_SSS_CREDS_PASSWORD, 0, POPT_ARG_NONE, &sss_creds_password, + 0, _("Use custom version of krb5_get_init_creds_password"), NULL}, + {CHILD_OPT_CHAIN_ID, 0, POPT_ARG_LONG, &chain_id, + 0, _("Tevent chain ID used for logging purposes"), NULL}, + {CHILD_OPT_CHECK_PAC, 0, POPT_ARG_LONG, &dummy_long, 0, + _("Check PAC flags"), NULL}, + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + cli_opts.canonicalize = false; + cli_opts.fast_use_anonymous_pkinit = false; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + case 'A': + cli_opts.fast_use_anonymous_pkinit = true; + break; + case 'C': + cli_opts.canonicalize = true; + break; + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + } + + cli_opts.check_pac_flags = 0; + if (dummy_long >= 0 && dummy_long <= UINT32_MAX) { + cli_opts.check_pac_flags = (uint32_t) dummy_long; + } else { + fprintf(stderr, "\nInvalid value [%ld] of check-pac option\n\n", + dummy_long); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + + poptFreeContext(pc); + + prctl(PR_SET_DUMPABLE, (dumpable == 0) ? 0 : 1); + + debug_prg_name = talloc_asprintf(NULL, "krb5_child[%d]", getpid()); + if (!debug_prg_name) { + debug_prg_name = "krb5_child"; + ERROR("talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + if (debug_fd != -1) { + opt_logger = sss_logger_str[FILES_LOGGER]; + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + opt_logger = sss_logger_str[STDERR_LOGGER]; + ERROR("set_debug_file_from_fd failed.\n"); + } + } + + sss_chain_id_set_format(DEBUG_CHAIN_ID_FMT_RID); + sss_chain_id_set((uint64_t)chain_id); + + DEBUG_INIT(debug_level, opt_logger); + + DEBUG(SSSDBG_TRACE_FUNC, "krb5_child started.\n"); + + kr = talloc_zero(NULL, struct krb5_req); + if (kr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + talloc_steal(kr, debug_prg_name); + + kr->fast_uid = fast_uid; + kr->fast_gid = fast_gid; + kr->cli_opts = &cli_opts; + if (sss_creds_password != 0) { + kr->krb5_get_init_creds_password = sss_krb5_get_init_creds_password; + } else { + kr->krb5_get_init_creds_password = krb5_get_init_creds_password; + } + + ret = k5c_recv_data(kr, STDIN_FILENO, &offline); + if (ret != EOK) { + goto done; + } + + if (cli_opts.check_pac_flags != 0 && !kr->validate) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "PAC check is requested but krb5_validate is set to false. " + "PAC checks will be skipped.\n"); + } + + kerr = privileged_krb5_setup(kr, offline); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "privileged_krb5_setup failed.\n"); + ret = EFAULT; + goto done; + } + + /* For PKINIT we might need access to the pcscd socket which by default + * is only allowed for authenticated users. Since PKINIT is part of + * the authentication and the user is not authenticated yet, we have + * to use different privileges and can only drop it only after the TGT is + * received. The fast_uid and fast_gid are the IDs the backend is running + * with. This can be either root or the 'sssd' user. Root is allowed by + * default and the 'sssd' user is allowed with the help of the + * sssd-pcsc.rules policy-kit rule. So those IDs are a suitable choice. We + * can only call switch_creds() because after the TGT is returned we have + * to switch to the IDs of the user to store the TGT. + * If we are offline we have to switch to the user's credentials directly + * to make sure the empty ccache is created with the expected + * ownership. */ + if (IS_SC_AUTHTOK(kr->pd->authtok) && !offline) { + kerr = switch_creds(kr, kr->fast_uid, kr->fast_gid, 0, NULL, + &kr->pcsc_saved_creds); + } else { + kerr = k5c_become_user(kr->uid, kr->gid, kr->posix_domain); + } + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "become_user failed.\n"); + ret = EFAULT; + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Running as [%"SPRIuid"][%"SPRIgid"].\n", geteuid(), getegid()); + + try_open_krb5_conf(); + + ret = k5c_setup(kr, offline); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "k5c_setup failed.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Will perform %s\n", krb5_child_command_to_str(kr->pd->cmd)); + switch(kr->pd->cmd) { + case SSS_PAM_AUTHENTICATE: + /* If we are offline, we need to create an empty ccache file */ + if (offline) { + DEBUG(SSSDBG_TRACE_FUNC, "Will perform offline auth\n"); + ret = create_empty_ccache(kr); + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Will perform online auth\n"); + ret = tgt_req_child(kr); + } + break; + case SSS_PAM_CHAUTHTOK: + ret = changepw_child(kr, false); + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + ret = changepw_child(kr, true); + break; + case SSS_PAM_ACCT_MGMT: + ret = kuserok_child(kr); + break; + case SSS_CMD_RENEW: + if (offline) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot renew TGT while offline\n"); + ret = KRB5_KDC_UNREACH; + goto done; + } + ret = renew_tgt_child(kr); + break; + case SSS_PAM_PREAUTH: + ret = tgt_req_child(kr); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "PAM command [%d] not supported.\n", kr->pd->cmd); + ret = EINVAL; + goto done; + } + + ret = k5c_send_data(kr, STDOUT_FILENO, ret); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to send reply\n"); + } + +done: + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "krb5_child completed successfully\n"); + ret = 0; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_child failed!\n"); + ret = -1; + } + krb5_cleanup(kr); + talloc_free(kr); + exit(ret); +} diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c new file mode 100644 index 0000000..54088e4 --- /dev/null +++ b/src/providers/krb5/krb5_child_handler.c @@ -0,0 +1,1022 @@ +/* + SSSD + + Kerberos 5 Backend Module - Manage krb5_child + + Authors: + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "util/util.h" +#include "util/child_common.h" +#include "util/sss_chain_id.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_auth.h" +#include "src/providers/krb5/krb5_utils.h" +#include "util/sss_ptr_hash.h" + +#ifndef KRB5_CHILD_DIR +#ifndef SSSD_LIBEXEC_PATH +#error "SSSD_LIBEXEC_PATH not defined" +#endif /* SSSD_LIBEXEC_PATH */ + +#define KRB5_CHILD_DIR SSSD_LIBEXEC_PATH +#endif /* KRB5_CHILD_DIR */ + +#define KRB5_CHILD KRB5_CHILD_DIR"/krb5_child" + +#define TIME_T_MAX LONG_MAX +#define int64_to_time_t(val) ((time_t)((val) < TIME_T_MAX ? val : TIME_T_MAX)) + +struct handle_child_state { + struct tevent_context *ev; + struct krb5child_req *kr; + uint8_t *buf; + ssize_t len; + + struct tevent_timer *timeout_handler; + pid_t child_pid; + + struct child_io_fds *io; +}; + +static errno_t pack_authtok(struct io_buffer *buf, size_t *rp, + struct sss_auth_token *tok) +{ + uint32_t auth_token_type; + uint32_t auth_token_length = 0; + const char *data; + size_t len; + errno_t ret = EOK; + + auth_token_type = sss_authtok_get_type(tok); + + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + auth_token_length = 0; + data = ""; + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_get_password(tok, &data, &len); + auth_token_length = len + 1; + break; + case SSS_AUTHTOK_TYPE_CCFILE: + ret = sss_authtok_get_ccfile(tok, &data, &len); + auth_token_length = len + 1; + break; + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + ret = sss_authtok_get_2fa_single(tok, &data, &len); + auth_token_length = len + 1; + break; + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + data = (char *) sss_authtok_get_data(tok); + auth_token_length = sss_authtok_get_size(tok); + break; + default: + ret = EINVAL; + } + + if (ret == EOK) { + SAFEALIGN_COPY_UINT32(&buf->data[*rp], &auth_token_type, rp); + SAFEALIGN_COPY_UINT32(&buf->data[*rp], &auth_token_length, rp); + if (data != NULL) { + safealign_memcpy(&buf->data[*rp], data, auth_token_length, rp); + } + } + + return ret; +} + +static errno_t create_send_buffer(struct krb5child_req *kr, + struct io_buffer **io_buf) +{ + struct io_buffer *buf; + size_t rp; + const char *keytab; + uint32_t validate; + uint32_t send_pac; + uint32_t use_enterprise_principal; + uint32_t posix_domain = 0; + size_t username_len = 0; + errno_t ret; + + keytab = dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_KEYTAB); + if (keytab == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "krb5_keytab not set for domain in sssd.conf\n"); + keytab = ""; + } + + validate = dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) ? 1 : 0; + + /* Always send PAC except for local IPA users and IPA server mode */ + switch (kr->krb5_ctx->config_type) { + case K5C_IPA_CLIENT: + send_pac = kr->upn_from_different_realm ? 1 : 0; + break; + case K5C_IPA_SERVER: + send_pac = 0; + break; + default: + send_pac = 1; + break; + } + + /* Renewals from KCM do not initialize kr->dom */ + if (kr->pd->cmd == SSS_CMD_RENEW || kr->dom->type == DOM_TYPE_POSIX) { + posix_domain = 1; + } else if (kr->dom->type != DOM_TYPE_APPLICATION) { + return EINVAL; + } + + if (kr->pd->cmd == SSS_CMD_RENEW || kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM + || kr->pd->cmd == SSS_PAM_CHAUTHTOK || kr->is_offline) { + use_enterprise_principal = false; + } else { + use_enterprise_principal = dp_opt_get_bool(kr->krb5_ctx->opts, + KRB5_USE_ENTERPRISE_PRINCIPAL) ? 1 : 0; + } + + buf = talloc(kr, struct io_buffer); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + buf->size = 9*sizeof(uint32_t) + strlen(kr->upn); + + if (kr->pd->cmd == SSS_PAM_AUTHENTICATE || + kr->pd->cmd == SSS_PAM_PREAUTH || + kr->pd->cmd == SSS_CMD_RENEW || + kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM || + kr->pd->cmd == SSS_PAM_CHAUTHTOK) { + buf->size += 4*sizeof(uint32_t) + strlen(kr->ccname) + strlen(keytab) + + sss_authtok_get_size(kr->pd->authtok); + + buf->size += sizeof(uint32_t); + if (kr->old_ccname) { + buf->size += strlen(kr->old_ccname); + } + } + + if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) { + buf->size += 2*sizeof(uint32_t) + + sss_authtok_get_size(kr->pd->newauthtok); + } + + if (kr->pd->cmd == SSS_PAM_ACCT_MGMT) { + username_len = strlen(kr->kuserok_user); + buf->size += sizeof(uint32_t) + username_len; + } + + buf->data = talloc_size(kr, buf->size); + if (buf->data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + talloc_free(buf); + return ENOMEM; + } + + rp = 0; + SAFEALIGN_COPY_UINT32(&buf->data[rp], &kr->pd->cmd, &rp); + SAFEALIGN_COPY_UINT32(&buf->data[rp], &kr->uid, &rp); + SAFEALIGN_COPY_UINT32(&buf->data[rp], &kr->gid, &rp); + SAFEALIGN_COPY_UINT32(&buf->data[rp], &validate, &rp); + SAFEALIGN_COPY_UINT32(&buf->data[rp], &posix_domain, &rp); + SAFEALIGN_COPY_UINT32(&buf->data[rp], &kr->is_offline, &rp); + SAFEALIGN_COPY_UINT32(&buf->data[rp], &send_pac, &rp); + SAFEALIGN_COPY_UINT32(&buf->data[rp], &use_enterprise_principal, &rp); + + SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(kr->upn), &rp); + safealign_memcpy(&buf->data[rp], kr->upn, strlen(kr->upn), &rp); + + if (kr->pd->cmd == SSS_PAM_AUTHENTICATE || + kr->pd->cmd == SSS_PAM_PREAUTH || + kr->pd->cmd == SSS_CMD_RENEW || + kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM || + kr->pd->cmd == SSS_PAM_CHAUTHTOK) { + SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(kr->ccname), &rp); + safealign_memcpy(&buf->data[rp], kr->ccname, strlen(kr->ccname), &rp); + + if (kr->old_ccname) { + SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(kr->old_ccname), &rp); + safealign_memcpy(&buf->data[rp], kr->old_ccname, + strlen(kr->old_ccname), &rp); + } else { + SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp); + } + + SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(keytab), &rp); + safealign_memcpy(&buf->data[rp], keytab, strlen(keytab), &rp); + + ret = pack_authtok(buf, &rp, kr->pd->authtok); + if (ret) { + return ret; + } + } + + if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) { + ret = pack_authtok(buf, &rp, kr->pd->newauthtok); + if (ret) { + return ret; + } + } + + if (kr->pd->cmd == SSS_PAM_ACCT_MGMT) { + SAFEALIGN_SET_UINT32(&buf->data[rp], username_len, &rp); + safealign_memcpy(&buf->data[rp], kr->kuserok_user, username_len, &rp); + } + + *io_buf = buf; + + return EOK; +} + +static void krb5_child_terminate(pid_t pid) +{ + int ret; + + if (pid == 0) { + return; + } + + ret = kill(pid, SIGKILL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "kill failed [%d]: %s\n", + ret, sss_strerror(ret)); + } +} + +static void krb5_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct handle_child_state *state = tevent_req_data(req, + struct handle_child_state); + + if (state->timeout_handler == NULL) { + return; + } + + /* No I/O expected anymore, make sure sockets are closed properly */ + state->io->in_use = false; + + DEBUG(SSSDBG_IMPORTANT_INFO, + "Timeout for child [%d] reached. In case KDC is distant or network " + "is slow you may consider increasing value of krb5_auth_timeout.\n", + state->child_pid); + + krb5_child_terminate(state->child_pid); + + tevent_req_error(req, ETIMEDOUT); +} + +static errno_t activate_child_timeout_handler(struct tevent_req *req, + struct tevent_context *ev, + const uint32_t timeout_seconds) +{ + struct timeval tv; + struct handle_child_state *state = tevent_req_data(req, + struct handle_child_state); + + tv = tevent_timeval_current(); + tv = tevent_timeval_add(&tv, timeout_seconds, 0); + state->timeout_handler = tevent_add_timer(ev, state, tv, + krb5_child_timeout, req); + if (state->timeout_handler == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + return ENOMEM; + } + + return EOK; +} + +errno_t set_extra_args(TALLOC_CTX *mem_ctx, struct krb5_ctx *krb5_ctx, + struct sss_domain_info *domain, + const char ***krb5_child_extra_args) +{ + const char **extra_args; + const char *krb5_realm; + uint64_t chain_id; + size_t c = 0; + int ret; + + if (krb5_ctx == NULL || krb5_child_extra_args == NULL) { + return EINVAL; + } + + extra_args = talloc_zero_array(mem_ctx, const char *, 12); + if (extra_args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + return ENOMEM; + } + + extra_args[c] = talloc_asprintf(extra_args, + "--"CHILD_OPT_FAST_CCACHE_UID"=%"SPRIuid, + getuid()); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + + extra_args[c] = talloc_asprintf(extra_args, + "--"CHILD_OPT_FAST_CCACHE_GID"=%"SPRIgid, + getgid()); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + + krb5_realm = krb5_ctx->realm; + if (domain != NULL && IS_SUBDOMAIN(domain) && dp_opt_get_bool(krb5_ctx->opts, KRB5_USE_SUBDOMAIN_REALM)) { + DEBUG(SSSDBG_CONF_SETTINGS, "Use subdomain realm %s.\n", domain->realm); + krb5_realm = domain->realm; + } + + if (krb5_ctx->realm != NULL) { + extra_args[c] = talloc_asprintf(extra_args, "--"CHILD_OPT_REALM"=%s", + krb5_realm); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + } + + if (krb5_ctx->lifetime_str != NULL) { + extra_args[c] = talloc_asprintf(extra_args, "--"CHILD_OPT_LIFETIME"=%s", + krb5_ctx->lifetime_str); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + } + + if (krb5_ctx->rlife_str != NULL) { + extra_args[c] = talloc_asprintf(extra_args, + "--"CHILD_OPT_RENEWABLE_LIFETIME"=%s", + krb5_ctx->rlife_str); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + } + + if (krb5_ctx->use_fast_str != NULL) { + extra_args[c] = talloc_asprintf(extra_args, "--"CHILD_OPT_USE_FAST"=%s", + krb5_ctx->use_fast_str); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + + if (krb5_ctx->fast_principal != NULL) { + extra_args[c] = talloc_asprintf(extra_args, + "--"CHILD_OPT_FAST_PRINCIPAL"=%s", + krb5_ctx->fast_principal); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + } + + if (krb5_ctx->fast_use_anonymous_pkinit) { + extra_args[c] = talloc_strdup(extra_args, + "--" CHILD_OPT_FAST_USE_ANONYMOUS_PKINIT); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + } + } + + if (krb5_ctx->check_pac_flags != 0) { + extra_args[c] = talloc_asprintf(extra_args, + "--"CHILD_OPT_CHECK_PAC"=%"PRIu32, + krb5_ctx->check_pac_flags); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + } + + if (krb5_ctx->canonicalize) { + extra_args[c] = talloc_strdup(extra_args, + "--" CHILD_OPT_CANONICALIZE); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + } + + if (krb5_ctx->sss_creds_password) { + extra_args[c] = talloc_strdup(extra_args, + "--" CHILD_OPT_SSS_CREDS_PASSWORD); + if (extra_args[c] == NULL) { + ret = ENOMEM; + goto done; + } + c++; + } + + chain_id = sss_chain_id_get(); + extra_args[c] = talloc_asprintf(extra_args, + "--"CHILD_OPT_CHAIN_ID"=%lu", + chain_id); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + + extra_args[c] = NULL; + + *krb5_child_extra_args = extra_args; + + ret = EOK; + +done: + + if (ret != EOK) { + talloc_free(extra_args); + } + + return ret; +} + +static void child_exited(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct child_io_fds *io = talloc_get_type(pvt, struct child_io_fds); + + /* Do not free it if we still need to read some data. Just mark that the + * child has exited so we know we need to free it later. */ + if (io->in_use) { + io->child_exited = true; + return; + } + + /* The child has finished and we don't need to use the file descriptors + * any more. This will close them and remove them from io hash table. */ + talloc_free(io); +} + +static void child_keep_alive_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *pvt) +{ + struct child_io_fds *io = talloc_get_type(pvt, struct child_io_fds); + + DEBUG(SSSDBG_IMPORTANT_INFO, "Keep alive timeout for child [%d] reached.\n", + io->pid); + + /* No I/O expected anymore, make sure sockets are closed properly */ + io->in_use = false; + + krb5_child_terminate(io->pid); +} + +static errno_t fork_child(struct tevent_context *ev, + struct krb5child_req *kr, + pid_t *_child_pid, + struct child_io_fds **_io) +{ + TALLOC_CTX *tmp_ctx; + int pipefd_to_child[2] = PIPE_INIT; + int pipefd_from_child[2] = PIPE_INIT; + const char **krb5_child_extra_args; + struct child_io_fds *io; + struct tevent_timer *te; + struct timeval tv; + char *io_key; + pid_t pid = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = set_extra_args(tmp_ctx, kr->krb5_ctx, kr->dom, &krb5_child_extra_args); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "set_extra_args failed.\n"); + goto done; + } + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe (from) failed [%d][%s].\n", errno, strerror(errno)); + goto done; + } + + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe (to) failed [%d][%s].\n", errno, strerror(errno)); + goto done; + } + + pid = fork(); + + if (pid == 0) { /* child */ + exec_child_ex(tmp_ctx, + pipefd_to_child, pipefd_from_child, + KRB5_CHILD, KRB5_CHILD_LOG_FILE, + krb5_child_extra_args, false, + STDIN_FILENO, STDOUT_FILENO); + + /* We should never get here */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec KRB5 child\n"); + ret = ERR_INTERNAL; + goto done; + } else if (pid < 0) { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + /* parent */ + + io = talloc_zero(tmp_ctx, struct child_io_fds); + if (io == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + talloc_set_destructor((void*)io, child_io_destructor); + + io->pid = pid; + + /* Set file descriptors. */ + io->read_from_child_fd = pipefd_from_child[0]; + io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(io->read_from_child_fd); + sss_fd_nonblocking(io->write_to_child_fd); + + /* Add io to pid:io hash table. */ + io_key = talloc_asprintf(tmp_ctx, "%d", pid); + if (io_key == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_ptr_hash_add(kr->krb5_ctx->io_table, io_key, io, + struct child_io_fds); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to add child io to hash table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Setup child's keep alive timeout for open file descriptors. This timeout + * is quite big to allow additional user interactions when the child is kept + * alive for further communication. */ + tv = tevent_timeval_current_ofs(300, 0); + te = tevent_add_timer(ev, io, tv, child_keep_alive_timeout, io); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup child timeout\n"); + ret = ENOMEM; + goto done; + } + + /* Setup the child handler. It will free io and remove it from the hash + * table when it exits. */ + ret = child_handler_setup(ev, pid, child_exited, io, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not set up child signal handler " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Steal the io pair so it can outlive this request if needed. */ + talloc_steal(kr->krb5_ctx->io_table, io); + + *_child_pid = pid; + *_io = io; + + ret = EOK; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + krb5_child_terminate(pid); + } + + talloc_free(tmp_ctx); + return ret; +} + +static void handle_child_step(struct tevent_req *subreq); +static void handle_child_done(struct tevent_req *subreq); + +struct tevent_req *handle_child_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct krb5child_req *kr) +{ + struct tevent_req *req, *subreq; + struct handle_child_state *state; + char *io_key; + int ret; + struct io_buffer *buf = NULL; + + req = tevent_req_create(mem_ctx, &state, struct handle_child_state); + if (req == NULL) { + return NULL; + } + + if (kr->krb5_ctx->io_table == NULL) { + /* Create IO/pipe table if it does not exist. */ + kr->krb5_ctx->io_table = sss_ptr_hash_create(kr->krb5_ctx, NULL, NULL); + if (kr->krb5_ctx->io_table == NULL) { + ret = ENOMEM; + goto fail; + } + } + + state->ev = ev; + state->kr = kr; + state->buf = NULL; + state->len = 0; + state->child_pid = -1; + state->timeout_handler = NULL; + + ret = create_send_buffer(kr, &buf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "create_send_buffer failed.\n"); + goto fail; + } + + if (kr->pd->child_pid == 0) { + /* Create new child. */ + ret = fork_child(ev, kr, &state->child_pid, &state->io); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "fork_child failed.\n"); + goto fail; + } + + /* Setup timeout. If failed, terminate the child process. */ + ret = activate_child_timeout_handler(req, ev, + dp_opt_get_int(kr->krb5_ctx->opts, KRB5_AUTH_TIMEOUT)); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup child timeout " + "[%d]: %s\n", ret, sss_strerror(ret)); + krb5_child_terminate(state->child_pid); + goto fail; + } + } else { + /* Continue talking to an existing child. */ + io_key = talloc_asprintf(state, "%d", kr->pd->child_pid); + if (io_key == NULL) { + ret = ENOMEM; + goto fail; + } + + state->io = sss_ptr_hash_lookup(kr->krb5_ctx->io_table, io_key, + struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to locate pipe for child pid=%s\n", + io_key); + ret = ENOENT; + goto fail; + } + } + + state->io->in_use = true; + subreq = write_pipe_safe_send(state, ev, buf->data, buf->size, + state->io->write_to_child_fd); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, handle_child_step, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void handle_child_step(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct handle_child_state *state = tevent_req_data(req, + struct handle_child_state); + int ret; + + ret = write_pipe_safe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + subreq = read_pipe_safe_send(state, state->ev, + state->io->read_from_child_fd); + if (!subreq) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, handle_child_done, req); + +done: + if (ret != EOK) { + state->io->in_use = false; + if (state->io->child_exited) { + talloc_free(state->io); + } + + tevent_req_error(req, ret); + } +} + +static void handle_child_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct handle_child_state *state = tevent_req_data(req, + struct handle_child_state); + int ret; + + talloc_zfree(state->timeout_handler); + + ret = read_pipe_safe_recv(subreq, state, &state->buf, &state->len); + state->io->in_use = false; + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + +done: + state->io->in_use = false; + if (state->io->child_exited) { + talloc_free(state->io); + } + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int handle_child_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint8_t **buf, ssize_t *len) +{ + struct handle_child_state *state = tevent_req_data(req, + struct handle_child_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *buf = talloc_move(mem_ctx, &state->buf); + *len = state->len; + + return EOK; +} + +static const char *krb5_child_response_type_to_str(int32_t type) +{ + switch (type) { + case SSS_PAM_ENV_ITEM: + return "Env variable to be set with pam_putenv(3)"; + case SSS_PAM_USER_INFO: + return "Message to be displayed to the user"; + case SSS_OTP: + return "Authtok was a OTP"; + case SSS_PAM_TEXT_MSG: + return "Plain text message to be displayed to the user"; + case SSS_PAM_OTP_INFO: + return "OTP info"; + case SSS_PASSWORD_PROMPTING: + return "Password prompting is possible"; + case SSS_CERT_AUTH_PROMPTING: + return "Certificate based authentication is available"; + case SSS_KRB5_INFO_TGT_LIFETIME: + return "TGT lifetime info"; + case SSS_KRB5_INFO_UPN: + return "UPN info"; + case SSS_CHILD_KEEP_ALIVE: + return "Keep alive"; + case SSS_PAM_OAUTH2_INFO: + return "OAuth2 info"; + case SSS_PAM_PASSKEY_INFO: + return "Passkey info"; + case SSS_PAM_PASSKEY_KRB_INFO: + return "Passkey kerberos info"; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected response type %d\n", type); + return "-unexpected-"; +} + +errno_t +parse_krb5_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf, ssize_t len, + struct pam_data *pd, int pwd_exp_warning, + struct krb5_child_response **_res) +{ + ssize_t pref_len; + size_t p; + errno_t ret; + bool skip; + char *ccname = NULL; + size_t ccname_len = 0; + int32_t msg_status; + int32_t msg_type; + int32_t msg_len; + int64_t time_data; + struct tgt_times tgtt; + uint32_t expiration; + uint32_t msg_subtype; + struct krb5_child_response *res; + const char *upn = NULL; + size_t upn_len = 0; + bool otp = false; + + if ((size_t) len < sizeof(int32_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, "message too short.\n"); + return EINVAL; + } + + memset(&tgtt, 0, sizeof(struct tgt_times)); + + if (pwd_exp_warning < 0) { + pwd_exp_warning = KERBEROS_PWEXPIRE_WARNING_TIME; + } + + /* A buffer with the following structure is expected. + * int32_t status of the request (required) + * message (zero or more) + * + * A message consists of: + * int32_t type of the message + * int32_t length of the following data + * uint8_t[len] data + */ + + p=0; + SAFEALIGN_COPY_INT32(&msg_status, buf+p, &p); + + while (p < len) { + skip = false; + SAFEALIGN_COPY_INT32(&msg_type, buf+p, &p); + SAFEALIGN_COPY_INT32(&msg_len, buf+p, &p); + + DEBUG(SSSDBG_TRACE_LIBS, "child response: " + "status code: %d (%s), msg type: %d (%s), len: %d\n", + msg_status, sss_strerror(msg_status), + msg_type, krb5_child_response_type_to_str(msg_type), + msg_len); + + if (msg_len > len - p) { + DEBUG(SSSDBG_CRIT_FAILURE, "message format error [%d] > [%zu].\n", + msg_len, len - p); + return EINVAL; + } + + /* We need to save the name of the credential cache file. To find it + * we check if the data part of a message starts with + * CCACHE_ENV_NAME"=". pref_len also counts the trailing '=' because + * sizeof() counts the trailing '\0' of a string. */ + pref_len = sizeof(CCACHE_ENV_NAME); + if ((msg_type == SSS_PAM_ENV_ITEM) && + (msg_len > pref_len) && + (strncmp((const char *) &buf[p], CCACHE_ENV_NAME"=", pref_len) == 0)) { + ccname = (char *) &buf[p+pref_len]; + ccname_len = msg_len-pref_len; + } + + if (msg_type == SSS_KRB5_INFO_TGT_LIFETIME && + msg_len == 4*sizeof(int64_t)) { + SAFEALIGN_COPY_INT64(&time_data, buf+p, NULL); + tgtt.authtime = int64_to_time_t(time_data); + SAFEALIGN_COPY_INT64(&time_data, buf+p+sizeof(int64_t), NULL); + tgtt.starttime = int64_to_time_t(time_data); + SAFEALIGN_COPY_INT64(&time_data, buf+p+2*sizeof(int64_t), NULL); + tgtt.endtime = int64_to_time_t(time_data); + SAFEALIGN_COPY_INT64(&time_data, buf+p+3*sizeof(int64_t), NULL); + tgtt.renew_till = int64_to_time_t(time_data); + DEBUG(SSSDBG_TRACE_LIBS, + "TGT times are [%"SPRItime"][%"SPRItime"][%"SPRItime"][%"SPRItime"].\n", + tgtt.authtime, tgtt.starttime, tgtt.endtime, tgtt.renew_till); + } + + if (msg_type == SSS_KRB5_INFO_UPN) { + upn = (char *) buf + p; + upn_len = msg_len; + } + + if (msg_type == SSS_PAM_USER_INFO) { + SAFEALIGN_COPY_UINT32(&msg_subtype, buf + p, NULL); + if (msg_subtype == SSS_PAM_USER_INFO_EXPIRE_WARN) { + SAFEALIGN_COPY_UINT32(&expiration, + buf + p + sizeof(uint32_t), NULL); + if (pwd_exp_warning > 0 && + difftime(pwd_exp_warning, expiration) < 0.0) { + skip = true; + } + } + } + + if (msg_type == SSS_OTP) { + otp = true; + skip = true; + } + + if (!skip) { + ret = pam_add_response(pd, msg_type, msg_len, &buf[p]); + if (ret != EOK) { + /* This is not a fatal error */ + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + + p += msg_len; + + if ((p < len) && (p + 2*sizeof(int32_t) > len)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "The remainder of the message is too short.\n"); + return EINVAL; + } + } + + res = talloc_zero(mem_ctx, struct krb5_child_response); + if (!res) return ENOMEM; + + res->otp = otp; + res->msg_status = msg_status; + memcpy(&res->tgtt, &tgtt, sizeof(tgtt)); + + if (ccname) { + res->ccname = talloc_strndup(res, ccname, ccname_len); + if (res->ccname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + talloc_free(res); + return ENOMEM; + } + } + + if (upn != NULL) { + res->correct_upn = talloc_strndup(res, upn, upn_len); + if (res->correct_upn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + talloc_free(res); + return ENOMEM; + } + } + + *_res = res; + return EOK; +} diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c new file mode 100644 index 0000000..6498258 --- /dev/null +++ b/src/providers/krb5/krb5_common.c @@ -0,0 +1,1261 @@ +/* + SSSD + + Kerberos Provider Common Functions + + Authors: + Sumit Bose + + Copyright (C) 2008-2009 Red Hat + + 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 3 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; 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, see . +*/ +#include +#include +#include +#include +#include +#include + +#include "providers/backend.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_opts.h" +#include "providers/krb5/krb5_utils.h" +#include "providers/fail_over.h" + +#ifdef HAVE_KRB5_CC_COLLECTION +/* krb5 profile functions */ +#include +#endif + +static errno_t check_lifetime(TALLOC_CTX *mem_ctx, struct dp_option *opts, + const int opt_id, char **lifetime_str) +{ + int ret; + char *str = NULL; + krb5_deltat lifetime; + + str = dp_opt_get_string(opts, opt_id); + if (str == NULL || *str == '\0') { + DEBUG(SSSDBG_FUNC_DATA, "No lifetime configured.\n"); + *lifetime_str = NULL; + return EOK; + } + + if (isdigit(str[strlen(str)-1])) { + str = talloc_asprintf(mem_ctx, "%ss", str); + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(opts, opt_id, str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + } else { + str = talloc_strdup(mem_ctx, str); + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + + ret = krb5_string_to_deltat(str, &lifetime); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid value [%s] for a lifetime.\n", str); + ret = EINVAL; + goto done; + } + + *lifetime_str = str; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(str); + } + + return ret; +} + +#ifdef HAVE_KRB5_CC_COLLECTION +/* source default_ccache_name from krb5.conf */ +static errno_t sss_get_system_ccname_template(TALLOC_CTX *mem_ctx, + char **ccname) +{ + krb5_context ctx; + profile_t p; + char *value = NULL; + long ret; + + *ccname = NULL; + + ret = sss_krb5_init_context(&ctx); + if (ret) return ret; + + ret = krb5_get_profile(ctx, &p); + if (ret) goto done; + + ret = profile_get_string(p, "libdefaults", "default_ccache_name", + NULL, NULL, &value); + profile_release(p); + if (ret) goto done; + + if (!value) { + ret = ERR_NOT_FOUND; + goto done; + } + + *ccname = talloc_strdup(mem_ctx, value); + if (*ccname == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + krb5_free_context(ctx); + free(value); + return ret; +} +#else +static errno_t sss_get_system_ccname_template(TALLOC_CTX *mem_ctx, + char **ccname) +{ + DEBUG(SSSDBG_CONF_SETTINGS, + "Your kerberos library does not support the default_ccache_name " + "option or the profile library. Please use krb5_ccname_template " + "in sssd.conf if you want to change the default\n"); + *ccname = NULL; + return ERR_NOT_FOUND; +} +#endif + +static void sss_check_cc_template(const char *cc_template) +{ + size_t template_len; + + template_len = strlen(cc_template); + if (template_len >= 6 && + strcmp(cc_template + (template_len - 6), "XXXXXX") != 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "ccache file name template [%s] doesn't " + "contain randomizing characters (XXXXXX), file might not " + "be rewritable\n", cc_template); + } +} + +errno_t sss_krb5_check_options(struct dp_option *opts, + struct sss_domain_info *dom, + struct krb5_ctx *krb5_ctx) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret; + const char *realm; + const char *dummy; + char *ccname; + + if (opts == NULL || dom == NULL || krb5_ctx == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + realm = dp_opt_get_cstring(opts, KRB5_REALM); + if (realm == NULL) { + ret = dp_opt_set_string(opts, KRB5_REALM, dom->name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + realm = dom->name; + } + + krb5_ctx->realm = talloc_strdup(krb5_ctx, realm); + if (krb5_ctx->realm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to set realm, krb5_child might not work as expected.\n"); + } + + ret = check_lifetime(krb5_ctx, opts, KRB5_RENEWABLE_LIFETIME, + &krb5_ctx->rlife_str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to check value of krb5_renewable_lifetime. [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + ret = check_lifetime(krb5_ctx, opts, KRB5_LIFETIME, + &krb5_ctx->lifetime_str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to check value of krb5_lifetime. [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + krb5_ctx->use_fast_str = dp_opt_get_cstring(opts, KRB5_USE_FAST); + if (krb5_ctx->use_fast_str != NULL) { + ret = check_fast(krb5_ctx->use_fast_str, &krb5_ctx->use_fast); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "check_fast failed.\n"); + goto done; + } + + if (krb5_ctx->use_fast) { + krb5_ctx->fast_principal = dp_opt_get_cstring(opts, + KRB5_FAST_PRINCIPAL); + krb5_ctx->fast_use_anonymous_pkinit = dp_opt_get_bool(opts, + KRB5_FAST_USE_ANONYMOUS_PKINIT); + } + } + + /* In contrast to MIT KDCs AD does not automatically canonicalize the + * enterprise principal in an AS request but requires the canonicalize + * flags to be set. To be on the safe side we always enable + * canonicalization if enterprise principals are used. */ + krb5_ctx->canonicalize = false; + if (dp_opt_get_bool(opts, KRB5_CANONICALIZE) + || dp_opt_get_bool(opts, KRB5_USE_ENTERPRISE_PRINCIPAL)) { + krb5_ctx->canonicalize = true; + } + + dummy = dp_opt_get_cstring(opts, KRB5_KDC); + if (dummy == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No KDC explicitly configured, using defaults.\n"); + } + + dummy = dp_opt_get_cstring(opts, KRB5_KPASSWD); + if (dummy == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No kpasswd server explicitly configured, " + "using the KDC or defaults.\n"); + } + + ccname = dp_opt_get_string(opts, KRB5_CCNAME_TMPL); + if (ccname != NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "The credential ccache name template has been explicitly set " + "in sssd.conf, it is recommended to set default_ccache_name " + "in krb5.conf instead so that a system default is used\n"); + ccname = talloc_strdup(tmp_ctx, ccname); + if (!ccname) { + ret = ENOMEM; + goto done; + } + } else { + ret = sss_get_system_ccname_template(tmp_ctx, &ccname); + if (ret && ret != ERR_NOT_FOUND) { + goto done; + } + if (ret == ERR_NOT_FOUND) { + /* Use fallback default */ + ccname = talloc_strdup(tmp_ctx, DEFAULT_CCNAME_TEMPLATE); + if (!ccname) { + ret = ENOMEM; + goto done; + } + } + + /* set back in opts */ + ret = dp_opt_set_string(opts, KRB5_CCNAME_TMPL, ccname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + } + + if ((ccname[0] == '/') || (strncmp(ccname, "FILE:", 5) == 0)) { + DEBUG(SSSDBG_CONF_SETTINGS, "ccache is of type FILE\n"); + /* warn if the file type (which is usually created in a sticky bit + * laden directory) does not have randomizing characters */ + sss_check_cc_template(ccname); + + if (ccname[0] == '/') { + /* /path/to/cc prepend FILE: */ + DEBUG(SSSDBG_CONF_SETTINGS, "The ccname template was " + "missing an explicit type, but is an absolute " + "path specifier. Assuming FILE:\n"); + + ccname = talloc_asprintf(tmp_ctx, "FILE:%s", ccname); + if (!ccname) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(opts, KRB5_CCNAME_TMPL, ccname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t krb5_try_kdcip(struct confdb_ctx *cdb, const char *conf_path, + struct dp_option *opts, int opt_id) +{ + char *krb5_servers = NULL; + errno_t ret; + + krb5_servers = dp_opt_get_string(opts, opt_id); + if (krb5_servers == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No KDC found in configuration, trying legacy option\n"); + ret = confdb_get_string(cdb, NULL, conf_path, + "krb5_kdcip", NULL, &krb5_servers); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "confdb_get_string failed.\n"); + return ret; + } + + if (krb5_servers != NULL) + { + ret = dp_opt_set_string(opts, opt_id, krb5_servers); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + talloc_free(krb5_servers); + return ret; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Set krb5 server [%s] based on legacy krb5_kdcip option\n", + krb5_servers); + DEBUG(SSSDBG_FATAL_FAILURE, + "Your configuration uses the deprecated option " + "'krb5_kdcip' to specify the KDC. Please change the " + "configuration to use the 'krb5_server' option " + "instead.\n"); + talloc_free(krb5_servers); + } + } + + return EOK; +} + +errno_t sss_krb5_get_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb, + const char *conf_path, struct dp_option **_opts) +{ + int ret; + struct dp_option *opts; + + ret = dp_get_options(memctx, cdb, conf_path, default_krb5_opts, + KRB5_OPTS, &opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_get_options failed.\n"); + goto done; + } + + /* If there is no KDC, try the deprecated krb5_kdcip option, too */ + /* FIXME - this can be removed in a future version */ + ret = krb5_try_kdcip(cdb, conf_path, opts, KRB5_KDC); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_try_kdcip failed.\n"); + goto done; + } + + *_opts = opts; + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(opts); + } + + return ret; +} + +void sss_krb5_parse_lookahead(const char *param, size_t *primary, size_t *backup) +{ + int ret; + + if (primary == NULL || backup == NULL) { + return; + } + + *primary = SSS_KRB5_LOOKAHEAD_PRIMARY_DEFAULT; + *backup = SSS_KRB5_LOOKAHEAD_BACKUP_DEFAULT; + + if (param == NULL) { + return; + } + + if (strchr(param, ':')) { + ret = sscanf(param, "%zu:%zu", primary, backup); + if (ret != 2) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not parse krb5_kdcinfo_lookahead!\n"); + } + } else { + ret = sscanf(param, "%zu", primary); + if (ret != 1) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not parse krb5_kdcinfo_lookahead!\n"); + } + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Option krb5_kdcinfo_lookahead set to %zu:%zu", + *primary, *backup); +} + + +static int remove_info_files_destructor(void *p) +{ + int ret; + struct remove_info_files_ctx *ctx = talloc_get_type(p, + struct remove_info_files_ctx); + + ret = remove_krb5_info_files(ctx, ctx->realm); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "remove_krb5_info_files failed.\n"); + } + ctx->krb5_service->removal_callback_available = false; + + return 0; +} + +static errno_t +krb5_add_krb5info_offline_callback(struct krb5_service *krb5_service) +{ + int ret; + struct remove_info_files_ctx *ctx = NULL; + + if (krb5_service == NULL || krb5_service->name == NULL + || krb5_service->realm == NULL + || krb5_service->be_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing KDC service name or realm!\n"); + return EINVAL; + } + + if (krb5_service->removal_callback_available) { + DEBUG(SSSDBG_TRACE_ALL, + "Removal callback already available for service [%s].\n", + krb5_service->name); + return EOK; + } + + ctx = talloc_zero(krb5_service->be_ctx, struct remove_info_files_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zfree failed.\n"); + return ENOMEM; + } + + ctx->realm = talloc_strdup(ctx, krb5_service->realm); + if (ctx->realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed!\n"); + ret = ENOMEM; + goto done; + } + + ctx->be_ctx = krb5_service->be_ctx; + ctx->krb5_service = krb5_service; + ctx->kdc_service_name = talloc_strdup(ctx, krb5_service->name); + if (ctx->kdc_service_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed!\n"); + ret = ENOMEM; + goto done; + } + + ret = be_add_offline_cb(ctx, krb5_service->be_ctx, + remove_krb5_info_files_callback, ctx, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_add_offline_cb failed.\n"); + goto done; + } + + talloc_set_destructor((TALLOC_CTX *) ctx, remove_info_files_destructor); + krb5_service->removal_callback_available = true; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(ctx); + } + + return ret; +} + +static errno_t write_krb5info_file_contents(struct krb5_service *krb5_service, + const char *contents, + const char *service) +{ + int ret; + int fd = -1; + char *tmp_name = NULL; + char *krb5info_name = NULL; + TALLOC_CTX *tmp_ctx = NULL; + const char *name_tmpl = NULL; + size_t server_len; + ssize_t written; + + if (krb5_service == NULL || krb5_service->realm == NULL + || *krb5_service->realm == '\0' + || contents == NULL || *contents == '\0' + || service == NULL || *service == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing or empty realm, server or service.\n"); + return EINVAL; + } + + if (sss_krb5_realm_has_proxy(krb5_service->realm)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "KDC Proxy available for realm [%s], no kdcinfo file created.\n", + krb5_service->realm); + return EOK; + } + + if (strcmp(service, SSS_KRB5KDC_FO_SRV) == 0) { + name_tmpl = KDCINFO_TMPL; + } else if (strcmp(service, SSS_KRB5KPASSWD_FO_SRV) == 0) { + name_tmpl = KPASSWDINFO_TMPL; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported service [%s].\n", service); + return EINVAL; + } + + server_len = strlen(contents); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + tmp_name = talloc_asprintf(tmp_ctx, PUBCONF_PATH"/.krb5info_dummy_XXXXXX"); + if (tmp_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + krb5info_name = talloc_asprintf(tmp_ctx, name_tmpl, krb5_service->realm); + if (krb5info_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + fd = sss_unique_file(tmp_ctx, tmp_name, &ret); + if (fd == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_unique_file failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + errno = 0; + written = sss_atomic_write_s(fd, discard_const(contents), server_len); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "write failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + if (written != server_len) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Write error, wrote [%zd] bytes, expected [%zu]\n", + written, server_len); + ret = EIO; + goto done; + } + + ret = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fchmod failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + ret = close(fd); + fd = -1; + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "close failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + ret = rename(tmp_name, krb5info_name); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "rename failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + ret = krb5_add_krb5info_offline_callback(krb5_service); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add offline callback, krb5info " + "file might not be removed properly.\n"); + } + + ret = EOK; +done: + if (fd != -1) { + close(fd); + } + + talloc_free(tmp_ctx); + return ret; +} + +errno_t write_krb5info_file(struct krb5_service *krb5_service, + const char **server_list, + const char *service) +{ + int i; + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + char *contents = NULL; + + if (krb5_service == NULL || server_list == NULL || service == NULL) { + return EINVAL; + } + + if (server_list[0] == NULL) { + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + contents = talloc_strdup(tmp_ctx, ""); + if (contents == NULL) { + ret = ENOMEM; + goto done; + } + + i = 0; + do { + contents = talloc_asprintf_append(contents, "%s\n", server_list[i]); + if (contents == NULL) { + ret = ENOMEM; + goto done; + } + i++; + } while (server_list[i] != NULL); + + ret = write_krb5info_file_contents(krb5_service, contents, service); +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char* fo_server_address_or_name(TALLOC_CTX *tmp_ctx, struct fo_server *server) +{ + struct resolv_hostent *srvaddr; + char *address; + + if (!server) return NULL; + + srvaddr = fo_get_server_hostent(server); + if (srvaddr) { + address = resolv_get_string_address(tmp_ctx, srvaddr); + if (address) { + return sss_escape_ip_address(tmp_ctx, + srvaddr->family, + address); + } + } + + address = discard_const(fo_get_server_name(server)); + if (address != NULL && fo_get_use_search_list(server) == false) { + if (address[strlen(address)-1] != '.') { + address = talloc_asprintf(tmp_ctx, "%s.", address); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_asprintf failed.\n"); + } + } + } + return address; +} + +errno_t write_krb5info_file_from_fo_server(struct krb5_service *krb5_service, + struct fo_server *server, + bool force_default_port, + const char *service, + bool (*filter)(struct fo_server *)) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char **server_list; + size_t server_idx; + struct fo_server *item; + int primary; + int port; + const char *address; + errno_t ret; + size_t n_lookahead_primary; + size_t n_lookahead_backup; + + if (krb5_service == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "The krb5_service must not be NULL!\n"); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed\n"); + return ENOMEM; + } + + n_lookahead_primary = krb5_service->lookahead_primary; + n_lookahead_backup = krb5_service->lookahead_backup; + + server_idx = 0; + server_list = talloc_zero_array(tmp_ctx, + const char *, + fo_server_count(server) + 1); + if (server_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array failed\n"); + talloc_free(tmp_ctx); + return ENOMEM; + } + + if (filter == NULL || filter(server) == false) { + address = fo_server_address_or_name(tmp_ctx, server); + if (address) { + if (!force_default_port) { + port = fo_get_server_port(server); + if (port != 0) { + address = talloc_asprintf(tmp_ctx, "%s:%d", address, port); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + talloc_free(tmp_ctx); + return ENOMEM; + } + } + } + + server_list[server_idx++] = address; + if (fo_is_server_primary(server)) { + if (n_lookahead_primary > 0) { + n_lookahead_primary--; + } + } else { + if (n_lookahead_backup > 0) { + n_lookahead_backup--; + } + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Server without name and address found in list.\n"); + } + } + + for (primary = 1; primary >= 0; --primary) { + for (item = fo_server_next(server) ? fo_server_next(server) : fo_server_first(server); + item != server; + item = fo_server_next(item) ? fo_server_next(item) : fo_server_first(item)) { + + if (primary && n_lookahead_primary == 0) break; + if (!primary && n_lookahead_backup == 0) break; + if (primary && !fo_is_server_primary(item)) continue; + if (!primary && fo_is_server_primary(item)) continue; + if (filter != NULL && filter(item)) continue; + + address = fo_server_address_or_name(tmp_ctx, item); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Server without name and address found in list.\n"); + continue; + } + + if (!force_default_port) { + port = fo_get_server_port(item); + if (port != 0) { + address = talloc_asprintf(tmp_ctx, "%s:%d", address, port); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + talloc_free(tmp_ctx); + return ENOMEM; + } + } + } + + server_list[server_idx++] = address; + if (primary) { + n_lookahead_primary--; + } else { + n_lookahead_backup--; + } + } + } + if (server_list[0] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "There is no server that can be written into kdc info file.\n"); + ret = EINVAL; + } else { + ret = write_krb5info_file(krb5_service, + server_list, + service); + } + talloc_free(tmp_ctx); + return ret; +} + + +static void krb5_resolve_callback(void *private_data, struct fo_server *server) +{ + struct krb5_service *krb5_service; + int ret; + + krb5_service = talloc_get_type(private_data, struct krb5_service); + if (!krb5_service) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bad private_data\n"); + return; + } + + if (krb5_service->write_kdcinfo) { + ret = write_krb5info_file_from_fo_server(krb5_service, + server, + false, + krb5_service->name, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "write to %s/kdcinfo.%s failed, authentication might fail.\n", + PUBCONF_PATH, krb5_service->realm); + } + } +} + +static errno_t _krb5_servers_init(struct be_ctx *ctx, + struct krb5_service *service, + const char *service_name, + const char *servers, + bool primary) +{ + TALLOC_CTX *tmp_ctx; + char **list = NULL; + errno_t ret = 0; + int i; + char *port_str; + long port; + char *server_spec; + char *endptr; + struct servent *servent; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = split_on_separator(tmp_ctx, servers, ',', true, true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse server list!\n"); + goto done; + } + + for (i = 0; list[i]; i++) { + talloc_steal(service, list[i]); + server_spec = talloc_strdup(service, list[i]); + if (!server_spec) { + ret = ENOMEM; + goto done; + } + + if (be_fo_is_srv_identifier(server_spec)) { + if (!primary) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add server [%s] to failover service: " + "SRV resolution only allowed for primary servers!\n", + list[i]); + continue; + } + + ret = be_fo_add_srv_server(ctx, service_name, service_name, NULL, + BE_FO_PROTO_UDP, true, NULL); + if (ret) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added service lookup\n"); + continue; + } + + /* Do not try to get port number if last character is ']' */ + if (server_spec[strlen(server_spec) - 1] != ']') { + port_str = strrchr(server_spec, ':'); + } else { + port_str = NULL; + } + + if (port_str == NULL) { + port = 0; + } else { + *port_str = '\0'; + ++port_str; + if (isdigit(*port_str)) { + errno = 0; + port = strtol(port_str, &endptr, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "strtol failed on [%s]: [%d][%s].\n", port_str, + ret, strerror(ret)); + goto done; + } + if (*endptr != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Found additional characters [%s] in port number " + "[%s].\n", endptr, port_str); + ret = EINVAL; + goto done; + } + + if (port < 1 || port > 65535) { + DEBUG(SSSDBG_CRIT_FAILURE, "Illegal port number [%ld].\n", port); + ret = EINVAL; + goto done; + } + } else if (isalpha(*port_str)) { + servent = getservbyname(port_str, NULL); + if (servent == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "getservbyname cannot find service [%s].\n", + port_str); + ret = EINVAL; + goto done; + } + + port = servent->s_port; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported port specifier in [%s].\n", list[i]); + ret = EINVAL; + goto done; + } + } + + /* It could be ipv6 address in square brackets. Remove + * the brackets if needed. */ + ret = remove_ipv6_brackets(server_spec); + if (ret != EOK) { + goto done; + } + + ret = be_fo_add_server(ctx, service_name, server_spec, (int) port, + list[i], primary); + if (ret && ret != EEXIST) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added Server %s\n", list[i]); + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static inline errno_t +krb5_primary_servers_init(struct be_ctx *ctx, struct krb5_service *service, + const char *service_name, const char *servers) +{ + return _krb5_servers_init(ctx, service, service_name, servers, true); +} + +static inline errno_t +krb5_backup_servers_init(struct be_ctx *ctx, struct krb5_service *service, + const char *service_name, const char *servers) +{ + return _krb5_servers_init(ctx, service, service_name, servers, false); +} + +static int krb5_user_data_cmp(void *ud1, void *ud2) +{ + return strcasecmp((char*) ud1, (char*) ud2); +} + +struct krb5_service *krb5_service_new(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + const char *service_name, + const char *realm, + bool use_kdcinfo, + size_t n_lookahead_primary, + size_t n_lookahead_backup) +{ + struct krb5_service *service; + + service = talloc_zero(mem_ctx, struct krb5_service); + if (service == NULL) { + return NULL; + } + + service->name = talloc_strdup(service, service_name); + if (service->name == NULL) { + talloc_free(service); + return NULL; + } + + service->realm = talloc_strdup(service, realm); + if (service->realm == NULL) { + talloc_free(service); + return NULL; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "write_kdcinfo for realm %s set to %s\n", + realm, + use_kdcinfo ? "true" : "false"); + service->write_kdcinfo = use_kdcinfo; + service->lookahead_primary = n_lookahead_primary; + service->lookahead_backup = n_lookahead_backup; + + service->be_ctx = be_ctx; + return service; +} + +int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *service_name, + const char *primary_servers, + const char *backup_servers, + const char *realm, + bool use_kdcinfo, + size_t n_lookahead_primary, + size_t n_lookahead_backup, + struct krb5_service **_service) +{ + TALLOC_CTX *tmp_ctx; + struct krb5_service *service; + int ret; + + tmp_ctx = talloc_new(memctx); + if (!tmp_ctx) { + return ENOMEM; + } + + service = krb5_service_new(tmp_ctx, ctx, service_name, realm, use_kdcinfo, + n_lookahead_primary, n_lookahead_backup); + if (!service) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_service(ctx, service_name, krb5_user_data_cmp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create failover service!\n"); + goto done; + } + + if (!primary_servers) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No primary servers defined, using service discovery\n"); + primary_servers = BE_SRV_IDENTIFIER; + } + + ret = krb5_primary_servers_init(ctx, service, service_name, primary_servers); + if (ret != EOK) { + goto done; + } + + if (backup_servers) { + ret = krb5_backup_servers_init(ctx, service, service_name, + backup_servers); + if (ret != EOK) { + goto done; + } + } + + ret = be_fo_service_add_callback(memctx, ctx, service_name, + krb5_resolve_callback, service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add failover callback!\n"); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *_service = talloc_steal(memctx, service); + } + talloc_zfree(tmp_ctx); + return ret; +} + + +errno_t remove_krb5_info_files(TALLOC_CTX *mem_ctx, const char *realm) +{ + int ret; + errno_t err; + char *file; + + file = talloc_asprintf(mem_ctx, KDCINFO_TMPL, realm); + if(file == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + errno = 0; + ret = unlink(file); + if (ret == -1) { + err = errno; + DEBUG(SSSDBG_FUNC_DATA, "Could not remove [%s], [%d][%s]\n", file, + err, strerror(err)); + } + + file = talloc_asprintf(mem_ctx, KPASSWDINFO_TMPL, realm); + if(file == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + errno = 0; + ret = unlink(file); + if (ret == -1) { + err = errno; + DEBUG(SSSDBG_FUNC_DATA, "Could not remove [%s], [%d][%s]\n", file, + err, strerror(err)); + } + + return EOK; +} + +void remove_krb5_info_files_callback(void *pvt) +{ + int ret; + struct remove_info_files_ctx *ctx = talloc_get_type(pvt, + struct remove_info_files_ctx); + + ret = be_fo_run_callbacks_at_next_request(ctx->be_ctx, + ctx->kdc_service_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "be_fo_run_callbacks_at_next_request(kdc_service_name) failed, " + "krb5 info files will not be removed, because " + "it is unclear if they will be recreated properly.\n"); + return; + } + if (ctx->kpasswd_service_name != NULL) { + ret = be_fo_run_callbacks_at_next_request(ctx->be_ctx, + ctx->kpasswd_service_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "be_fo_run_callbacks_at_next_request(kpasswd_service_name) failed, " + "krb5 info files will not be removed, because " + "it is unclear if they will be recreated properly.\n"); + return; + } + } + + /* Freeing the remove_info_files_ctx will remove the related krb5info + * file. Additionally the callback from the list of callbacks is removed, + * it will be added again when a new krb5info file is created. */ + talloc_free(ctx); +} + +errno_t krb5_get_simple_upn(TALLOC_CTX *mem_ctx, struct krb5_ctx *krb5_ctx, + struct sss_domain_info *dom, const char *username, + const char *user_dom, char **_upn) +{ + const char *realm = NULL; + char *uc_dom = NULL; + char *upn; + char *name; + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (user_dom != NULL && dom->name != NULL && + strcasecmp(dom->name, user_dom) != 0) { + uc_dom = get_uppercase_realm(tmp_ctx, user_dom); + if (uc_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "get_uppercase_realm failed.\n"); + ret = ENOMEM; + goto done; + } + } else { + realm = dp_opt_get_cstring(krb5_ctx->opts, KRB5_REALM); + if (realm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing Kerberos realm.\n"); + ret = ENOMEM; + goto done; + } + } + + /* The internal username is qualified, but we are only interested in + * the name part + */ + ret = sss_parse_internal_fqname(tmp_ctx, username, &name, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not parse [%s] into name and " + "domain components, login might fail\n", username); + upn = talloc_strdup(tmp_ctx, username); + } else { + /* NOTE: this is a hack, works only in some environments */ + upn = talloc_asprintf(tmp_ctx, "%s@%s", + name, realm != NULL ? realm : uc_dom); + } + + if (upn == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Using simple UPN [%s].\n", upn); + *_upn = talloc_steal(mem_ctx, upn); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t compare_principal_realm(const char *upn, const char *realm, + bool *different_realm) +{ + char *at_sign; + + if (upn == NULL || realm == NULL || different_realm == NULL || + *upn == '\0' || *realm == '\0') { + return EINVAL; + } + + at_sign = strchr(upn, '@'); + + if (at_sign == NULL) { + return EINVAL; + } + + if (strcmp(realm, at_sign + 1) == 0) { + *different_realm = false; + } else { + *different_realm = true; + } + + return EOK; +} diff --git a/src/providers/krb5/krb5_common.h b/src/providers/krb5/krb5_common.h new file mode 100644 index 0000000..80552ab --- /dev/null +++ b/src/providers/krb5/krb5_common.h @@ -0,0 +1,249 @@ +/* + SSSD + + Kerberos Backend, common header file + + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + + + 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 3 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; 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, see . +*/ + +#ifndef __KRB5_COMMON_H__ +#define __KRB5_COMMON_H__ + +#include "config.h" +#include + +#include "providers/backend.h" +#include "util/util.h" +#include "util/sss_krb5.h" + +#define KDCINFO_TMPL PUBCONF_PATH"/kdcinfo.%s" +#define KPASSWDINFO_TMPL PUBCONF_PATH"/kpasswdinfo.%s" + +#define SSS_KRB5KDC_FO_SRV "KERBEROS" +#define SSS_KRB5KPASSWD_FO_SRV "KPASSWD" +#define SSS_KRB5_LOOKAHEAD_PRIMARY_DEFAULT 3 +#define SSS_KRB5_LOOKAHEAD_BACKUP_DEFAULT 1 + +enum krb5_opts { + KRB5_KDC = 0, + KRB5_BACKUP_KDC, + KRB5_REALM, + KRB5_CCACHEDIR, + KRB5_CCNAME_TMPL, + KRB5_AUTH_TIMEOUT, + KRB5_KEYTAB, + KRB5_VALIDATE, + KRB5_KPASSWD, + KRB5_BACKUP_KPASSWD, + KRB5_STORE_PASSWORD_IF_OFFLINE, + KRB5_RENEWABLE_LIFETIME, + KRB5_LIFETIME, + KRB5_RENEW_INTERVAL, + KRB5_USE_FAST, + KRB5_FAST_PRINCIPAL, + KRB5_FAST_USE_ANONYMOUS_PKINIT, + KRB5_CANONICALIZE, + KRB5_USE_ENTERPRISE_PRINCIPAL, + KRB5_USE_KDCINFO, + KRB5_KDCINFO_LOOKAHEAD, + KRB5_MAP_USER, + KRB5_USE_SUBDOMAIN_REALM, + + KRB5_OPTS +}; + +typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type; + +struct krb5_service { + struct be_ctx *be_ctx; + char *name; + char *realm; + bool write_kdcinfo; + size_t lookahead_primary; + size_t lookahead_backup; + bool removal_callback_available; +}; + +struct fo_service; +struct deferred_auth_ctx; +struct renew_tgt_ctx; + +enum krb5_config_type { + K5C_GENERIC, + K5C_IPA_CLIENT, + K5C_IPA_SERVER +}; + +struct map_id_name_to_krb_primary { + const char *id_name; + const char* krb_primary; +}; + +struct krb5_ctx { + /* opts taken from kinit */ + /* in seconds */ + krb5_deltat starttime; + krb5_deltat lifetime; + char *lifetime_str; + krb5_deltat rlife; + char *rlife_str; + + int forwardable; + int proxiable; + int addresses; + + int not_forwardable; + int not_proxiable; + int no_addresses; + + int verbose; + + char* principal_name; + char* service_name; + char* keytab_name; + char* k5_cache_name; + char* k4_cache_name; + + action_type action; + + struct dp_option *opts; + struct krb5_service *service; + struct krb5_service *kpasswd_service; + + sss_regexp_t *illegal_path_re; + + struct deferred_auth_ctx *deferred_auth_ctx; + struct renew_tgt_ctx *renew_tgt_ctx; + struct kcm_renew_tgt_ctx *kcm_renew_tgt_ctx; + bool use_fast; + bool sss_creds_password; + + hash_table_t *wait_queue_hash; + hash_table_t *io_table; + + enum krb5_config_type config_type; + + struct map_id_name_to_krb_primary *name_to_primary; + + char *realm; + + const char *use_fast_str; + const char *fast_principal; + bool fast_use_anonymous_pkinit; + uint32_t check_pac_flags; + + bool canonicalize; +}; + +struct remove_info_files_ctx { + char *realm; + struct be_ctx *be_ctx; + const char *kdc_service_name; + const char *kpasswd_service_name; + struct krb5_service *krb5_service; +}; + +errno_t sss_krb5_check_options(struct dp_option *opts, + struct sss_domain_info *dom, + struct krb5_ctx *krb5_ctx); + +errno_t krb5_try_kdcip(struct confdb_ctx *cdb, const char *conf_path, + struct dp_option *opts, int opt_id); + +errno_t sss_krb5_get_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb, + const char *conf_path, struct dp_option **_opts); + +void sss_krb5_parse_lookahead(const char *param, size_t *primary, size_t *backup); + +errno_t write_krb5info_file(struct krb5_service *krb5_service, + const char **server_list, + const char *service); + +errno_t write_krb5info_file_from_fo_server(struct krb5_service *krb5_service, + struct fo_server *server, + bool force_default_port, + const char *service, + bool (*filter)(struct fo_server *)); + +struct krb5_service *krb5_service_new(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + const char *service_name, + const char *realm, + bool use_kdcinfo, + size_t n_lookahead_primary, + size_t n_lookahead_backup); + +int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *service_name, + const char *primary_servers, + const char *backup_servers, + const char *realm, + bool use_kdcinfo, + size_t n_lookahead_primary, + size_t n_lookahead_backup, + struct krb5_service **_service); + +void remove_krb5_info_files_callback(void *pvt); + +errno_t remove_krb5_info_files(TALLOC_CTX *mem_ctx, const char *realm); + +errno_t krb5_get_simple_upn(TALLOC_CTX *mem_ctx, struct krb5_ctx *krb5_ctx, + struct sss_domain_info *dom, const char *username, + const char *user_dom, char **_upn); + +errno_t compare_principal_realm(const char *upn, const char *realm, + bool *different_realm); + +/* from krb5_keytab.c */ + +/** + * @brief Copy given keytab into a MEMORY keytab + * + * @param[in] mem_ctx Talloc memory context the new keytab name should be + * allocated on + * @param[in] kctx Kerberos context + * @param[in] inp_keytab_file Existing keytab, if set to NULL the default + * keytab will be used + * @param[out] _mem_name Name of the new MEMORY keytab + * @param[out] _mem_keytab Krb5 keytab handle for the new MEMORY keytab, NULL + * may be passed here if the caller has no use for the + * handle + * + * The memory for the MEMORY keytab is handled by libkrb5 internally and + * a reference counter is used. If the reference counter of the specific + * MEMORY keytab reaches 0, i.e. no open ones are left, the memory is free. + * This means we cannot call krb5_kt_close() for the new MEMORY keytab in + * copy_keytab_into_memory() because this would destroy it immediately. Hence + * we have to return the handle so that the caller can safely remove the + * MEMORY keytab if the is not needed anymore. Since libkrb5 frees the + * internal memory when the library is unloaded short running processes can + * safely pass NULL as the 5th argument because on exit all memory is freed. + * Long running processes which need more control over the memory consumption + * should close the handle for free the memory at runtime. + */ +krb5_error_code copy_keytab_into_memory(TALLOC_CTX *mem_ctx, krb5_context kctx, + const char *inp_keytab_file, + char **_mem_name, + krb5_keytab *_mem_keytab); + +errno_t set_extra_args(TALLOC_CTX *mem_ctx, struct krb5_ctx *krb5_ctx, + struct sss_domain_info *domain, + const char ***krb5_child_extra_args); +#endif /* __KRB5_COMMON_H__ */ diff --git a/src/providers/krb5/krb5_delayed_online_authentication.c b/src/providers/krb5/krb5_delayed_online_authentication.c new file mode 100644 index 0000000..f88d8ab --- /dev/null +++ b/src/providers/krb5/krb5_delayed_online_authentication.c @@ -0,0 +1,386 @@ +/* + SSSD + + Kerberos 5 Backend Module -- Request a TGT when the system gets online + + Authors: + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#ifdef USE_KEYRING +#include +#include +#endif +#include + +#include "providers/krb5/krb5_auth.h" +#include "util/util.h" +#include "util/find_uid.h" + +struct deferred_auth_ctx { + hash_table_t *user_table; + struct be_ctx *be_ctx; + struct tevent_context *ev; + struct krb5_ctx *krb5_ctx; +}; + +struct auth_data { + struct be_ctx *be_ctx; + struct krb5_ctx *krb5_ctx; + struct pam_data *pd; +}; + +static void *hash_talloc(const size_t size, void *pvt) +{ + return talloc_size(pvt, size); +} + +static void hash_talloc_free(void *ptr, void *pvt) +{ + talloc_free(ptr); +} + +static void authenticate_user_done(struct tevent_req *req); +static void authenticate_user(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct auth_data *auth_data = talloc_get_type(private_data, + struct auth_data); + struct pam_data *pd = auth_data->pd; + struct tevent_req *req; + + DEBUG_PAM_DATA(SSSDBG_TRACE_ALL, pd); + +#ifdef USE_KEYRING + char *password; + long keysize; + long keyrevoke; + errno_t ret; + + keysize = keyctl_read_alloc(pd->key_serial, (void **)&password); + if (keysize == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "keyctl_read failed [%d][%s].\n", ret, strerror(ret)); + return; + } + + ret = sss_authtok_set_password(pd->authtok, password, keysize); + sss_erase_mem_securely(password, keysize); + free(password); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "failed to set password in auth token [%d][%s].\n", + ret, strerror(ret)); + return; + } + + keyrevoke = keyctl_revoke(pd->key_serial); + if (keyrevoke == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "keyctl_revoke failed [%d][%s].\n", ret, strerror(ret)); + } +#endif + + req = krb5_auth_queue_send(auth_data, ev, auth_data->be_ctx, + auth_data->pd, auth_data->krb5_ctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n"); + talloc_free(auth_data); + return; + } + + tevent_req_set_callback(req, authenticate_user_done, auth_data); +} + +static void authenticate_user_done(struct tevent_req *req) +{ + struct auth_data *auth_data = tevent_req_callback_data(req, + struct auth_data); + int ret; + int pam_status = PAM_SYSTEM_ERR; + int dp_err = DP_ERR_OK; + + ret = krb5_auth_queue_recv(req, &pam_status, &dp_err); + talloc_free(req); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth request failed.\n"); + } else { + if (pam_status == PAM_SUCCESS) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Successfully authenticated user [%s].\n", + auth_data->pd->user); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to authenticate user [%s].\n", + auth_data->pd->user); + } + } + + talloc_free(auth_data); +} + +static errno_t authenticate_stored_users( + struct deferred_auth_ctx *deferred_auth_ctx) +{ + int ret; + hash_table_t *uid_table; + struct hash_iter_context_t *iter; + hash_entry_t *entry; + hash_key_t key; + hash_value_t value; + struct pam_data *pd; + struct auth_data *auth_data; + struct tevent_timer *te; + + ret = get_uid_table(deferred_auth_ctx, &uid_table); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "get_uid_table failed.\n"); + return ret; + } + + iter = new_hash_iter_context(deferred_auth_ctx->user_table); + if (iter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "new_hash_iter_context failed.\n"); + return EINVAL; + } + + while ((entry = iter->next(iter)) != NULL) { + key.type = HASH_KEY_ULONG; + key.ul = entry->key.ul; + pd = talloc_get_type(entry->value.ptr, struct pam_data); + + ret = hash_lookup(uid_table, &key, &value); + + if (ret == HASH_SUCCESS) { + DEBUG(SSSDBG_FUNC_DATA, "User [%s] is still logged in, " + "trying online authentication.\n", pd->user); + + auth_data = talloc_zero(deferred_auth_ctx->be_ctx, + struct auth_data); + if (auth_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + } else { + auth_data->pd = talloc_steal(auth_data, pd); + auth_data->krb5_ctx = deferred_auth_ctx->krb5_ctx; + auth_data->be_ctx = deferred_auth_ctx->be_ctx; + + te = tevent_add_timer(deferred_auth_ctx->ev, + auth_data, tevent_timeval_current(), + authenticate_user, auth_data); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + } + } + } else { + DEBUG(SSSDBG_FUNC_DATA, "User [%s] is not logged in anymore, " + "discarding online authentication.\n", pd->user); + talloc_free(pd); + } + + ret = hash_delete(deferred_auth_ctx->user_table, + &entry->key); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "hash_delete failed [%s].\n", + hash_error_string(ret)); + } + } + + talloc_free(iter); + + return EOK; +} + +static void delayed_online_authentication_callback(void *private_data) +{ + struct deferred_auth_ctx *deferred_auth_ctx = + talloc_get_type(private_data, struct deferred_auth_ctx); + int ret; + + if (deferred_auth_ctx->user_table == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Delayed online authentication activated, " + "but user table does not exists.\n"); + return; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Backend is online, starting delayed online authentication.\n"); + ret = authenticate_stored_users(deferred_auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "authenticate_stored_users failed.\n"); + } + + return; +} + +errno_t add_user_to_delayed_online_authentication(struct krb5_ctx *krb5_ctx, + struct sss_domain_info *domain, + struct pam_data *pd, + uid_t uid) +{ + int ret; + hash_key_t key; + hash_value_t value; + struct pam_data *new_pd; + + if (domain->type != DOM_TYPE_POSIX) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Domain type does not support delayed authentication\n"); + return ENOTSUP; + } + + if (krb5_ctx->deferred_auth_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing context for delayed online authentication.\n"); + return EINVAL; + } + + if (krb5_ctx->deferred_auth_ctx->user_table == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "user_table not available.\n"); + return EINVAL; + } + + if (sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_PASSWORD) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid authtok for user [%s].\n", pd->user); + return EINVAL; + } + + ret = copy_pam_data(krb5_ctx->deferred_auth_ctx, pd, &new_pd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "copy_pam_data failed\n"); + return ENOMEM; + } + + +#ifdef USE_KEYRING + const char *password; + size_t len; + + ret = sss_authtok_get_password(new_pd->authtok, &password, &len); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get password [%d][%s].\n", ret, strerror(ret)); + sss_authtok_set_empty(new_pd->authtok); + talloc_free(new_pd); + return ret; + } + + new_pd->key_serial = add_key("user", new_pd->user, password, len, + KEY_SPEC_SESSION_KEYRING); + if (new_pd->key_serial == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "add_key failed [%d][%s].\n", ret, strerror(ret)); + sss_authtok_set_empty(new_pd->authtok); + talloc_free(new_pd); + return ret; + } + DEBUG(SSSDBG_TRACE_ALL, + "Saved authtok of user [%s] with serial [%"SPRIkey_ser"].\n", + new_pd->user, new_pd->key_serial); + sss_authtok_set_empty(new_pd->authtok); +#endif + + key.type = HASH_KEY_ULONG; + key.ul = uid; + value.type = HASH_VALUE_PTR; + value.ptr = new_pd; + + ret = hash_enter(krb5_ctx->deferred_auth_ctx->user_table, + &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add user [%s] to table [%s], " + "delayed online authentication not possible.\n", + pd->user, hash_error_string(ret)); + talloc_free(new_pd); + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_ALL, "Added user [%s] successfully to " + "delayed online authentication.\n", pd->user); + + return EOK; +} + +errno_t init_delayed_online_authentication(struct krb5_ctx *krb5_ctx, + struct be_ctx *be_ctx, + struct tevent_context *ev) +{ + int ret; + hash_table_t *tmp_table; + + ret = get_uid_table(krb5_ctx, &tmp_table); + if (ret != EOK) { + if (ret == ENOSYS) { + DEBUG(SSSDBG_FATAL_FAILURE, "Delayed online auth was requested " + "on an unsupported system.\n"); + } else { + DEBUG(SSSDBG_FATAL_FAILURE, "Delayed online auth was requested " + "but initialisation failed.\n"); + } + return ret; + } + ret = hash_destroy(tmp_table); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "hash_destroy failed [%s].\n", hash_error_string(ret)); + return EFAULT; + } + + krb5_ctx->deferred_auth_ctx = talloc_zero(krb5_ctx, + struct deferred_auth_ctx); + if (krb5_ctx->deferred_auth_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + ret = hash_create_ex(0, + &krb5_ctx->deferred_auth_ctx->user_table, + 0, 0, 0, 0, hash_talloc, hash_talloc_free, + krb5_ctx->deferred_auth_ctx, + NULL, NULL); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "hash_create_ex failed [%s]\n", hash_error_string(ret)); + ret = ENOMEM; + goto fail; + } + + krb5_ctx->deferred_auth_ctx->be_ctx = be_ctx; + krb5_ctx->deferred_auth_ctx->krb5_ctx = krb5_ctx; + krb5_ctx->deferred_auth_ctx->ev = ev; + + ret = be_add_online_cb(krb5_ctx, be_ctx, + delayed_online_authentication_callback, + krb5_ctx->deferred_auth_ctx, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_add_online_cb failed.\n"); + goto fail; + } + + /* TODO: add destructor */ + + return EOK; +fail: + talloc_zfree(krb5_ctx->deferred_auth_ctx); + return ret; +} diff --git a/src/providers/krb5/krb5_init.c b/src/providers/krb5/krb5_init.c new file mode 100644 index 0000000..d2c927e --- /dev/null +++ b/src/providers/krb5/krb5_init.c @@ -0,0 +1,229 @@ +/* + SSSD + + Kerberos 5 Backend Module + + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include "util/child_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_init_shared.h" +#include "providers/data_provider.h" + +static errno_t krb5_init_kpasswd(struct krb5_ctx *ctx, + struct be_ctx *be_ctx) +{ + const char *realm; + const char *primary_servers; + const char *backup_servers; + const char *kdc_servers; + bool use_kdcinfo; + size_t n_lookahead_primary; + size_t n_lookahead_backup; + errno_t ret; + + realm = dp_opt_get_string(ctx->opts, KRB5_REALM); + if (realm == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Missing krb5_realm option!\n"); + return EINVAL; + } + + kdc_servers = dp_opt_get_string(ctx->opts, KRB5_KDC); + primary_servers = dp_opt_get_string(ctx->opts, KRB5_KPASSWD); + backup_servers = dp_opt_get_string(ctx->opts, KRB5_BACKUP_KPASSWD); + use_kdcinfo = dp_opt_get_bool(ctx->opts, KRB5_USE_KDCINFO); + sss_krb5_parse_lookahead(dp_opt_get_string(ctx->opts, KRB5_KDCINFO_LOOKAHEAD), + &n_lookahead_primary, &n_lookahead_backup); + + + if (primary_servers == NULL && backup_servers != NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "kpasswd server wasn't specified but " + "backup_servers kpasswd given. Using it as primary_servers\n"); + primary_servers = backup_servers; + backup_servers = NULL; + } + + if (primary_servers == NULL && kdc_servers != NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Missing krb5_kpasswd option and KDC set " + "explicitly, will use KDC for password change operations!\n"); + ctx->kpasswd_service = NULL; + } else { + ret = krb5_service_init(ctx, be_ctx, SSS_KRB5KPASSWD_FO_SRV, + primary_servers, backup_servers, realm, + use_kdcinfo, + n_lookahead_primary, + n_lookahead_backup, + &ctx->kpasswd_service); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to init KRB5KPASSWD failover service!\n"); + return ret; + } + } + + return EOK; +} + +static errno_t krb5_init_kdc(struct krb5_ctx *ctx, struct be_ctx *be_ctx) +{ + const char *primary_servers; + const char *backup_servers; + const char *realm; + bool use_kdcinfo; + size_t n_lookahead_primary; + size_t n_lookahead_backup; + errno_t ret; + + realm = dp_opt_get_string(ctx->opts, KRB5_REALM); + if (realm == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Missing krb5_realm option!\n"); + return EINVAL; + } + + primary_servers = dp_opt_get_string(ctx->opts, KRB5_KDC); + backup_servers = dp_opt_get_string(ctx->opts, KRB5_BACKUP_KDC); + + use_kdcinfo = dp_opt_get_bool(ctx->opts, KRB5_USE_KDCINFO); + sss_krb5_parse_lookahead(dp_opt_get_string(ctx->opts, KRB5_KDCINFO_LOOKAHEAD), + &n_lookahead_primary, &n_lookahead_backup); + + ret = krb5_service_init(ctx, be_ctx, SSS_KRB5KDC_FO_SRV, + primary_servers, backup_servers, realm, + use_kdcinfo, + n_lookahead_primary, + n_lookahead_backup, + &ctx->service); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to init KRB5 failover service!\n"); + return ret; + } + + return EOK; +} + +errno_t sssm_krb5_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data) +{ + struct krb5_ctx *ctx; + errno_t ret; + + ctx = talloc_zero(mem_ctx, struct krb5_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n"); + return ENOMEM; + } + + ret = sss_krb5_get_options(ctx, be_ctx->cdb, be_ctx->conf_path, &ctx->opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get krb5 options [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ctx->action = INIT_PW; + ctx->config_type = K5C_GENERIC; + + ret = krb5_init_kdc(ctx, be_ctx); + if (ret != EOK) { + goto done; + } + + ret = krb5_init_kpasswd(ctx, be_ctx); + if (ret != EOK) { + goto done; + } + + ret = krb5_child_init(ctx, be_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not initialize krb5_child settings " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sss_regexp_new(ctx, ILLEGAL_PATH_PATTERN, 0, &(ctx->illegal_path_re)); + if (ret != EOK) { + ret = EFAULT; + goto done; + } + + ret = be_fo_set_dns_srv_lookup_plugin(be_ctx, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set SRV lookup plugin " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + *_module_data = ctx; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(ctx); + } + + return ret; +} + +errno_t sssm_krb5_auth_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct krb5_ctx *ctx; + + ctx = talloc_get_type(module_data, struct krb5_ctx); + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + krb5_pam_handler_send, krb5_pam_handler_recv, ctx, + struct krb5_ctx, struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_krb5_chpass_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + return sssm_krb5_auth_init(mem_ctx, be_ctx, module_data, dp_methods); +} + +errno_t sssm_krb5_access_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct krb5_ctx *ctx; + + ctx = talloc_get_type(module_data, struct krb5_ctx); + dp_set_method(dp_methods, DPM_ACCESS_HANDLER, + krb5_pam_handler_send, krb5_pam_handler_recv, ctx, + struct krb5_ctx, struct pam_data, struct pam_data *); + + return EOK; +} diff --git a/src/providers/krb5/krb5_init_shared.c b/src/providers/krb5/krb5_init_shared.c new file mode 100644 index 0000000..3e6ebe2 --- /dev/null +++ b/src/providers/krb5/krb5_init_shared.c @@ -0,0 +1,105 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_utils.h" +#include "providers/krb5/krb5_init_shared.h" + +errno_t krb5_child_init(struct krb5_ctx *krb5_auth_ctx, + struct be_ctx *bectx) +{ + errno_t ret; + time_t renew_intv = 0; + krb5_deltat renew_interval_delta; + char *renew_interval_str; + + if (dp_opt_get_bool(krb5_auth_ctx->opts, KRB5_STORE_PASSWORD_IF_OFFLINE)) { + ret = init_delayed_online_authentication(krb5_auth_ctx, bectx, + bectx->ev); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "init_delayed_online_authentication failed.\n"); + goto done; + } + } + renew_interval_str = dp_opt_get_string(krb5_auth_ctx->opts, + KRB5_RENEW_INTERVAL); + if (renew_interval_str != NULL) { + ret = krb5_string_to_deltat(renew_interval_str, &renew_interval_delta); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Reading krb5_renew_interval failed.\n"); + renew_interval_delta = 0; + } + renew_intv = renew_interval_delta; + } + + if (renew_intv > 0) { + ret = init_renew_tgt(krb5_auth_ctx, bectx, bectx->ev, renew_intv); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "init_renew_tgt failed.\n"); + goto done; + } + } + + ret = sss_krb5_check_options(krb5_auth_ctx->opts, bectx->domain, + krb5_auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_check_options failed.\n"); + goto done; + } + + ret = get_pac_check_config(bectx->cdb, &krb5_auth_ctx->check_pac_flags); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to get pac_check option.\n"); + goto done; + } + + if (krb5_auth_ctx->check_pac_flags != 0 + && !dp_opt_get_bool(krb5_auth_ctx->opts, KRB5_VALIDATE)) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "PAC check is requested but krb5_validate is set to false. " + "PAC checks will be skipped.\n"); + sss_log(SSS_LOG_WARNING, + "PAC check is requested but krb5_validate is set to false. " + "PAC checks will be skipped."); + } + + ret = parse_krb5_map_user(krb5_auth_ctx, + dp_opt_get_cstring(krb5_auth_ctx->opts, + KRB5_MAP_USER), + bectx->domain->name, + &krb5_auth_ctx->name_to_primary); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "parse_krb5_map_user failed: %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } + + ret = EOK; + +done: + return ret; +} diff --git a/src/providers/krb5/krb5_init_shared.h b/src/providers/krb5/krb5_init_shared.h new file mode 100644 index 0000000..883b84f --- /dev/null +++ b/src/providers/krb5/krb5_init_shared.h @@ -0,0 +1,29 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef KRB5_INIT_SHARED_H_ +#define KRB5_INIT_SHARED_H_ + +errno_t krb5_child_init(struct krb5_ctx *krb5_auth_ctx, + struct be_ctx *bectx); + +#endif /* KRB5_INIT_SHARED_H_ */ diff --git a/src/providers/krb5/krb5_keytab.c b/src/providers/krb5/krb5_keytab.c new file mode 100644 index 0000000..db383d4 --- /dev/null +++ b/src/providers/krb5/krb5_keytab.c @@ -0,0 +1,231 @@ +/* + SSSD + + Kerberos 5 Backend Module -- keytab related utilities + + Authors: + Sumit Bose + + Copyright (C) 2014 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "util/sss_krb5.h" +#include "providers/krb5/krb5_common.h" + +static krb5_error_code do_keytab_copy(krb5_context kctx, krb5_keytab s_keytab, + krb5_keytab d_keytab) +{ + krb5_error_code kerr; + krb5_error_code kt_err; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + + memset(&cursor, 0, sizeof(cursor)); + kerr = krb5_kt_start_seq_get(kctx, s_keytab, &cursor); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "error reading keytab.\n"); + return kerr; + } + + memset(&entry, 0, sizeof(entry)); + while ((kt_err = krb5_kt_next_entry(kctx, s_keytab, &entry, + &cursor)) == 0) { + kerr = krb5_kt_add_entry(kctx, d_keytab, &entry); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_kt_add_entry failed.\n"); + kt_err = krb5_kt_end_seq_get(kctx, s_keytab, &cursor); + if (kt_err != 0) { + DEBUG(SSSDBG_TRACE_ALL, + "krb5_kt_end_seq_get failed with [%d], ignored.\n", + kt_err); + } + return kerr; + } + + kerr = sss_krb5_free_keytab_entry_contents(kctx, &entry); + if (kerr != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to free keytab entry.\n"); + kt_err = krb5_kt_end_seq_get(kctx, s_keytab, &cursor); + if (kt_err != 0) { + DEBUG(SSSDBG_TRACE_ALL, + "krb5_kt_end_seq_get failed with [%d], ignored.\n", + kt_err); + } + return kerr; + } + memset(&entry, 0, sizeof(entry)); + } + + kerr = krb5_kt_end_seq_get(kctx, s_keytab, &cursor); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_kt_end_seq_get failed.\n"); + return kerr; + } + + /* check if we got any errors from krb5_kt_next_entry */ + if (kt_err != 0 && kt_err != KRB5_KT_END) { + DEBUG(SSSDBG_CRIT_FAILURE, "error reading keytab.\n"); + return kt_err; + } + + return 0; +} + +krb5_error_code copy_keytab_into_memory(TALLOC_CTX *mem_ctx, krb5_context kctx, + const char *inp_keytab_file, + char **_mem_name, + krb5_keytab *_mem_keytab) +{ + krb5_error_code kerr; + krb5_keytab keytab = NULL; + krb5_keytab mem_keytab = NULL; + krb5_keytab tmp_mem_keytab = NULL; + char keytab_name[MAX_KEYTAB_NAME_LEN]; + char *sep; + char *mem_name = NULL; + char *tmp_mem_name = NULL; + const char *keytab_file; + char default_keytab_name[MAX_KEYTAB_NAME_LEN]; + + keytab_file = inp_keytab_file; + if (keytab_file == NULL) { + kerr = krb5_kt_default_name(kctx, default_keytab_name, + sizeof(default_keytab_name)); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_kt_default_name failed.\n"); + return kerr; + } + + keytab_file = default_keytab_name; + } + + kerr = krb5_kt_resolve(kctx, keytab_file, &keytab); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "error resolving keytab [%s].\n", + keytab_file); + return kerr; + } + + kerr = sss_krb5_kt_have_content(kctx, keytab); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "keytab [%s] has not entries.\n", + keytab_file); + goto done; + } + + kerr = krb5_kt_get_name(kctx, keytab, keytab_name, sizeof(keytab_name)); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to read name for keytab [%s].\n", + keytab_file); + goto done; + } + + sep = strchr(keytab_name, ':'); + if (sep == NULL || sep[1] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Keytab name [%s] does not have delimiter[:] .\n", keytab_name); + kerr = KRB5KRB_ERR_GENERIC; + goto done; + } + + if (strncmp(keytab_name, "MEMORY:", sizeof("MEMORY:") -1) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Keytab [%s] is already memory keytab.\n", + keytab_name); + *_mem_name = talloc_strdup(mem_ctx, keytab_name); + if(*_mem_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + kerr = KRB5KRB_ERR_GENERIC; + goto done; + } + kerr = 0; + goto done; + } + + mem_name = talloc_asprintf(mem_ctx, "MEMORY:%s", sep + 1); + if (mem_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + kerr = KRB5KRB_ERR_GENERIC; + goto done; + } + + tmp_mem_name = talloc_asprintf(mem_ctx, "MEMORY:%s.tmp", sep + 1); + if (tmp_mem_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + kerr = KRB5KRB_ERR_GENERIC; + goto done; + } + + kerr = krb5_kt_resolve(kctx, mem_name, &mem_keytab); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "error resolving keytab [%s].\n", + mem_name); + goto done; + } + + kerr = krb5_kt_resolve(kctx, tmp_mem_name, &tmp_mem_keytab); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "error resolving keytab [%s].\n", + tmp_mem_name); + goto done; + } + + kerr = do_keytab_copy(kctx, keytab, tmp_mem_keytab); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy keytab [%s] into [%s].\n", + keytab_file, tmp_mem_name); + goto done; + } + + /* krb5_kt_add_entry() adds new entries into MEMORY keytabs at the + * beginning and not at the end as for FILE keytabs. Since we want to keep + * the processing order we have to copy the MEMORY keytab again to retain + * the order from the FILE keytab. */ + + kerr = do_keytab_copy(kctx, tmp_mem_keytab, mem_keytab); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy keytab [%s] into [%s].\n", + tmp_mem_name, mem_name); + goto done; + } + + *_mem_name = mem_name; + if (_mem_keytab != NULL) { + *_mem_keytab = mem_keytab; + } + + kerr = 0; +done: + + talloc_free(tmp_mem_name); + + if (kerr != 0) { + talloc_free(mem_name); + if ((mem_keytab != NULL) && krb5_kt_close(kctx, mem_keytab) != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "krb5_kt_close failed.\n"); + } + } + + if (tmp_mem_keytab != NULL && krb5_kt_close(kctx, tmp_mem_keytab) != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "krb5_kt_close failed.\n"); + } + + if (keytab != NULL && krb5_kt_close(kctx, keytab) != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "krb5_kt_close failed.\n"); + } + + return kerr; +} diff --git a/src/providers/krb5/krb5_opts.c b/src/providers/krb5/krb5_opts.c new file mode 100644 index 0000000..20dcd58 --- /dev/null +++ b/src/providers/krb5/krb5_opts.c @@ -0,0 +1,50 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "src/providers/data_provider.h" + +struct dp_option default_krb5_opts[] = { + { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_ccachedir", DP_OPT_STRING, { DEFAULT_CCACHE_DIR }, NULL_STRING }, + { "krb5_ccname_template", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_validate", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_store_password_if_offline", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_renewable_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_renew_interval", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_use_fast", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_fast_principal", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_fast_use_anonymous_pkinit", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_use_enterprise_principal", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kdcinfo_lookahead", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_map_user", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_use_subdomain_realm", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + DP_OPTION_TERMINATOR +}; diff --git a/src/providers/krb5/krb5_opts.h b/src/providers/krb5/krb5_opts.h new file mode 100644 index 0000000..798008d --- /dev/null +++ b/src/providers/krb5/krb5_opts.h @@ -0,0 +1,30 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef KRB5_OPTS_H_ +#define KRB5_OPTS_H_ + +#include "src/providers/data_provider.h" + +extern struct dp_option default_krb5_opts[]; + +#endif /* KRB5_OPTS_H_ */ diff --git a/src/providers/krb5/krb5_renew_tgt.c b/src/providers/krb5/krb5_renew_tgt.c new file mode 100644 index 0000000..9435555 --- /dev/null +++ b/src/providers/krb5/krb5_renew_tgt.c @@ -0,0 +1,631 @@ +/* + SSSD + + Kerberos 5 Backend Module -- Renew a TGT automatically + + Authors: + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ +#include + +#include "util/util.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_utils.h" +#include "providers/krb5/krb5_ccache.h" + +struct renew_tgt_ctx { + hash_table_t *tgt_table; + struct be_ctx *be_ctx; + struct tevent_context *ev; + struct krb5_ctx *krb5_ctx; + time_t timer_interval; + struct tevent_timer *te; +}; + +struct renew_data { + const char *ccfile; + time_t start_time; + time_t lifetime; + time_t start_renew_at; + struct pam_data *pd; +}; + +struct auth_data { + struct be_ctx *be_ctx; + struct krb5_ctx *krb5_ctx; + struct pam_data *pd; + struct renew_data *renew_data; + hash_table_t *table; + hash_key_t key; +}; + + +static void renew_tgt_done(struct tevent_req *req); +static void renew_tgt(struct tevent_context *ev, struct tevent_timer *te, + struct timeval current_time, void *private_data) +{ + struct auth_data *auth_data = talloc_get_type(private_data, + struct auth_data); + struct tevent_req *req; + + req = krb5_auth_queue_send(auth_data, ev, auth_data->be_ctx, auth_data->pd, + auth_data->krb5_ctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n"); +/* Give back the pam data to the renewal item to be able to retry at the next + * time the renewals re run. */ + auth_data->renew_data->pd = talloc_steal(auth_data->renew_data, + auth_data->pd); + talloc_free(auth_data); + return; + } + + tevent_req_set_callback(req, renew_tgt_done, auth_data); +} + +static void renew_tgt_done(struct tevent_req *req) +{ + struct auth_data *auth_data = tevent_req_callback_data(req, + struct auth_data); + int ret; + int pam_status = PAM_SYSTEM_ERR; + int dp_err; + hash_value_t value; + + ret = krb5_auth_queue_recv(req, &pam_status, &dp_err); + talloc_free(req); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth request failed.\n"); + if (auth_data->renew_data != NULL) { + DEBUG(SSSDBG_FUNC_DATA, "Giving back pam data.\n"); + auth_data->renew_data->pd = talloc_steal(auth_data->renew_data, + auth_data->pd); + } + } else { + switch (pam_status) { + case PAM_SUCCESS: + DEBUG(SSSDBG_CONF_SETTINGS, + "Successfully renewed TGT for user [%s].\n", + auth_data->pd->user); +/* In general a successful renewal will update the renewal item and free the + * old data. But if the TGT has reached the end of his renewable lifetime it + * will not be put into the list of renewable tickets again. In this case the + * renewal item is not updated and the value from the hash and the one we have + * stored are the same. Since the TGT cannot be renewed anymore we want to + * remove it from the list of renewable tickets. */ + ret = hash_lookup(auth_data->table, &auth_data->key, &value); + if (ret == HASH_SUCCESS) { + if (value.type == HASH_VALUE_PTR && + auth_data->renew_data == talloc_get_type(value.ptr, + struct renew_data)) { + DEBUG(SSSDBG_FUNC_DATA, + "New TGT was not added for renewal, " + "removing list entry for user [%s].\n", + auth_data->pd->user); + ret = hash_delete(auth_data->table, &auth_data->key); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "hash_delete failed.\n"); + } + } + } + break; + case PAM_AUTHINFO_UNAVAIL: + case PAM_AUTHTOK_LOCK_BUSY: + DEBUG(SSSDBG_CONF_SETTINGS, + "Cannot renewed TGT for user [%s] while offline, " + "will retry later.\n", + auth_data->pd->user); + if (auth_data->renew_data != NULL) { + DEBUG(SSSDBG_FUNC_DATA, "Giving back pam data.\n"); + auth_data->renew_data->pd = talloc_steal(auth_data->renew_data, + auth_data->pd); + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to renew TGT for user [%s].\n", + auth_data->pd->user); + ret = hash_delete(auth_data->table, &auth_data->key); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "hash_delete failed.\n"); + } + } + } + + talloc_zfree(auth_data); +} + +static errno_t renew_all_tgts(struct renew_tgt_ctx *renew_tgt_ctx) +{ + int ret; + hash_entry_t *entries; + unsigned long count; + size_t c; + time_t now; + struct auth_data *auth_data; + struct renew_data *renew_data; + struct tevent_timer *te = NULL; + + ret = hash_entries(renew_tgt_ctx->tgt_table, &count, &entries); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "hash_entries failed.\n"); + return ENOMEM; + } + + now = time(NULL); + + for (c = 0; c < count; c++) { + renew_data = talloc_get_type(entries[c].value.ptr, struct renew_data); + DEBUG(SSSDBG_TRACE_ALL, + "Checking [%s] for renewal at [%.24s].\n", renew_data->ccfile, + ctime(&renew_data->start_renew_at)); + /* If renew_data->pd == NULL a renewal request for this data is + * currently running so we skip it. */ + if (renew_data->start_renew_at < now && renew_data->pd != NULL) { + auth_data = talloc_zero(renew_tgt_ctx, struct auth_data); + if (auth_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + } else { +/* We need to steal the pam_data here, because a successful renewal of the + * ticket might add a new renewal item to the list with the same key (upn). + * This would delete renew_data and all its children. But we cannot be sure + * that adding the new renewal item is the last operation of the renewal + * process with access the pam_data. To be on the safe side we steal the + * pam_data and make it a child of auth_data which is only freed after the + * renewal process is finished. In the case of an error during renewal we + * might want to steal the pam_data back to renew_data before freeing + * auth_data to allow a new renewal attempt. */ + auth_data->pd = talloc_move(auth_data, &renew_data->pd); + auth_data->krb5_ctx = renew_tgt_ctx->krb5_ctx; + auth_data->be_ctx = renew_tgt_ctx->be_ctx; + auth_data->table = renew_tgt_ctx->tgt_table; + auth_data->renew_data = renew_data; + auth_data->key.type = entries[c].key.type; + auth_data->key.str = talloc_strdup(auth_data, + entries[c].key.str); + if (auth_data->key.str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + } else { + te = tevent_add_timer(renew_tgt_ctx->ev, + auth_data, tevent_timeval_current(), + renew_tgt, auth_data); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "tevent_add_timer failed.\n"); + } + } + } + + if (auth_data == NULL || te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to renew TGT in [%s].\n", renew_data->ccfile); + ret = hash_delete(renew_tgt_ctx->tgt_table, &entries[c].key); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "hash_delete failed.\n"); + } + } + } + } + + talloc_free(entries); + + return EOK; +} + +static void renew_handler(struct renew_tgt_ctx *renew_tgt_ctx); + +static void renew_tgt_offline_callback(void *private_data) +{ + struct renew_tgt_ctx *renew_tgt_ctx = talloc_get_type(private_data, + struct renew_tgt_ctx); + + talloc_zfree(renew_tgt_ctx->te); +} + +static void renew_tgt_online_callback(void *private_data) +{ + struct renew_tgt_ctx *renew_tgt_ctx = talloc_get_type(private_data, + struct renew_tgt_ctx); + + renew_handler(renew_tgt_ctx); +} + +static void renew_tgt_timer_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, void *data) +{ + struct renew_tgt_ctx *renew_tgt_ctx = talloc_get_type(data, + struct renew_tgt_ctx); + + /* forget the timer event, it will be freed by the tevent timer loop */ + renew_tgt_ctx->te = NULL; + + renew_handler(renew_tgt_ctx); +} + +static void renew_handler(struct renew_tgt_ctx *renew_tgt_ctx) +{ + struct timeval next; + int ret; + + if (be_is_offline(renew_tgt_ctx->be_ctx)) { + DEBUG(SSSDBG_CONF_SETTINGS, "Offline, disable renew timer.\n"); + return; + } + + ret = renew_all_tgts(renew_tgt_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "renew_all_tgts failed. " + "Disabling automatic TGT renewal\n"); + sss_log(SSS_LOG_ERR, "Disabling automatic TGT renewal."); + talloc_zfree(renew_tgt_ctx); + return; + } + + if (renew_tgt_ctx->te != NULL) { + DEBUG(SSSDBG_TRACE_LIBS, + "There is an active renewal timer, doing nothing.\n"); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Adding new renew timer.\n"); + + next = sss_tevent_timeval_current_ofs_time_t(renew_tgt_ctx->timer_interval); + renew_tgt_ctx->te = tevent_add_timer(renew_tgt_ctx->ev, renew_tgt_ctx, + next, renew_tgt_timer_handler, + renew_tgt_ctx); + if (renew_tgt_ctx->te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + sss_log(SSS_LOG_ERR, "Disabling automatic TGT renewal."); + talloc_zfree(renew_tgt_ctx); + } + + return; +} + +static void renew_del_cb(hash_entry_t *entry, hash_destroy_enum type, void *pvt) +{ + struct renew_data *renew_data; + + if (entry->value.type == HASH_VALUE_PTR) { + renew_data = talloc_get_type(entry->value.ptr, struct renew_data); + talloc_zfree(renew_data); + return; + } + + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected value type [%d].\n", entry->value.type); +} + +static errno_t check_ccache_file(struct renew_tgt_ctx *renew_tgt_ctx, + const char *ccache_file, const char *upn, + const char *user_name) +{ + int ret; + struct stat stat_buf; + struct tgt_times tgtt; + struct pam_data pd; + time_t now; + const char *filename; + + if (ccache_file == NULL || upn == NULL || user_name == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Missing one of the needed attributes: [%s][%s][%s].\n", + ccache_file == NULL ? "cache file missing" : ccache_file, + upn == NULL ? "principal missing" : upn, + user_name == NULL ? "user name missing" : user_name); + return EINVAL; + } + + if (strncmp(ccache_file, "FILE:", 5) == 0) { + filename = ccache_file + 5; + } else { + filename = ccache_file; + } + + ret = stat(filename, &stat_buf); + if (ret != EOK) { + if (ret == ENOENT) { + return EOK; + } + return ret; + } + + DEBUG(SSSDBG_TRACE_ALL, "Found ccache file [%s].\n", ccache_file); + + memset(&tgtt, 0, sizeof(tgtt)); + ret = get_ccache_file_data(ccache_file, upn, &tgtt); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "get_ccache_file_data failed.\n"); + return ret; + } + + memset(&pd, 0, sizeof(pd)); + pd.cmd = SSS_CMD_RENEW; + pd.user = discard_const_p(char, user_name); + now = time(NULL); + if (tgtt.renew_till > tgtt.endtime && tgtt.renew_till > now && + tgtt.endtime > now) { + DEBUG(SSSDBG_TRACE_LIBS, + "Adding [%s] for automatic renewal.\n", ccache_file); + ret = add_tgt_to_renew_table(renew_tgt_ctx->krb5_ctx, ccache_file, + &tgtt, &pd, upn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_tgt_to_renew_table failed, " + "automatic renewal not possible.\n"); + } + } else { + DEBUG(SSSDBG_TRACE_ALL, + "TGT in [%s] for [%s] is too old.\n", ccache_file, upn); + } + + return EOK; +} + +static errno_t check_ccache_files(struct renew_tgt_ctx *renew_tgt_ctx) +{ + TALLOC_CTX *tmp_ctx; + int ret; + const char *ccache_filter = SYSDB_CCACHE_FILE"=*"; + const char *ccache_attrs[] = { SYSDB_CCACHE_FILE, SYSDB_UPN, SYSDB_NAME, + SYSDB_CANONICAL_UPN, NULL }; + size_t msgs_count = 0; + struct ldb_message **msgs = NULL; + size_t c; + const char *ccache_file; + char *upn; + const char *user_name; + struct ldb_dn *base_dn; + char *user_dom; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + base_dn = sysdb_user_base_dn(tmp_ctx, renew_tgt_ctx->be_ctx->domain); + if (base_dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_base_dn failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, renew_tgt_ctx->be_ctx->domain->sysdb, base_dn, + LDB_SCOPE_SUBTREE, ccache_filter, ccache_attrs, + &msgs_count, &msgs); + if (ret == ENOENT) { + msgs_count = 0; /* Fall through */ + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_entry failed.\n"); + goto done; + } + + if (msgs_count == 0) { + DEBUG(SSSDBG_TRACE_ALL, + "No entries with ccache file found in cache.\n"); + ret = EOK; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, + "Found [%zu] entries with ccache file in cache.\n", msgs_count); + + for (c = 0; c < msgs_count; c++) { + user_name = ldb_msg_find_attr_as_string(msgs[c], SYSDB_NAME, NULL); + if (user_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No user name found, this is a severe error, " + "but we ignore it here.\n"); + continue; + } + + ret = sss_parse_internal_fqname(tmp_ctx, user_name, NULL, &user_dom); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse internal fqname [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = find_or_guess_upn(tmp_ctx, msgs[c], renew_tgt_ctx->krb5_ctx, + renew_tgt_ctx->be_ctx->domain, + user_name, user_dom, &upn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "find_or_guess_upn failed.\n"); + goto done; + } + + ccache_file = ldb_msg_find_attr_as_string(msgs[c], SYSDB_CCACHE_FILE, + NULL); + + ret = check_ccache_file(renew_tgt_ctx, ccache_file, upn, user_name); + if (ret != EOK) { + DEBUG(SSSDBG_FUNC_DATA, + "Failed to check ccache file [%s].\n", ccache_file); + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t init_renew_tgt(struct krb5_ctx *krb5_ctx, struct be_ctx *be_ctx, + struct tevent_context *ev, time_t renew_intv) +{ + int ret; + struct timeval next; + + krb5_ctx->renew_tgt_ctx = talloc_zero(krb5_ctx, struct renew_tgt_ctx); + if (krb5_ctx->renew_tgt_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + ret = sss_hash_create_ex(krb5_ctx->renew_tgt_ctx, 0, + &krb5_ctx->renew_tgt_ctx->tgt_table, 0, 0, 0, 0, + renew_del_cb, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_hash_create failed.\n"); + goto fail; + } + + krb5_ctx->renew_tgt_ctx->be_ctx = be_ctx; + krb5_ctx->renew_tgt_ctx->krb5_ctx = krb5_ctx; + krb5_ctx->renew_tgt_ctx->ev = ev; + krb5_ctx->renew_tgt_ctx->timer_interval = renew_intv; + + ret = check_ccache_files(krb5_ctx->renew_tgt_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read ccache files, continuing ...\n"); + } + + next = sss_tevent_timeval_current_ofs_time_t(krb5_ctx->renew_tgt_ctx->timer_interval); + krb5_ctx->renew_tgt_ctx->te = tevent_add_timer(ev, krb5_ctx->renew_tgt_ctx, + next, renew_tgt_timer_handler, + krb5_ctx->renew_tgt_ctx); + if (krb5_ctx->renew_tgt_ctx->te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + ret = ENOMEM; + goto fail; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Adding offline callback to remove renewal timer.\n"); + ret = be_add_offline_cb(krb5_ctx->renew_tgt_ctx, be_ctx, + renew_tgt_offline_callback, krb5_ctx->renew_tgt_ctx, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add offline callback.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Adding renewal task to online callbacks.\n"); + ret = be_add_online_cb(krb5_ctx->renew_tgt_ctx, be_ctx, + renew_tgt_online_callback, krb5_ctx->renew_tgt_ctx, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add renewal task to online callbacks.\n"); + goto fail; + } + + return EOK; + +fail: + talloc_zfree(krb5_ctx->renew_tgt_ctx); + return ret; +} + +errno_t add_tgt_to_renew_table(struct krb5_ctx *krb5_ctx, const char *ccfile, + struct tgt_times *tgtt, struct pam_data *pd, + const char *upn) +{ + int ret; + hash_key_t key; + hash_value_t value; + struct renew_data *renew_data = NULL; + + if (krb5_ctx->renew_tgt_ctx == NULL) { + DEBUG(SSSDBG_TRACE_LIBS ,"Renew context not initialized, " + "automatic renewal not available.\n"); + return EOK; + } + + if (pd->cmd != SSS_PAM_AUTHENTICATE && pd->cmd != SSS_CMD_RENEW && + pd->cmd != SSS_PAM_CHAUTHTOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected pam task [%d].\n", pd->cmd); + return EINVAL; + } + + if (upn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user principal name.\n"); + return EINVAL; + } + + /* hash_enter copies the content of the hash string, so it is safe to use + * discard_const_p here. */ + key.type = HASH_KEY_STRING; + key.str = discard_const_p(char, upn); + + renew_data = talloc_zero(krb5_ctx->renew_tgt_ctx, struct renew_data); + if (renew_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + if (ccfile[0] == '/') { + renew_data->ccfile = talloc_asprintf(renew_data, "FILE:%s", ccfile); + if (renew_data->ccfile == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + } else { + renew_data->ccfile = talloc_strdup(renew_data, ccfile); + } + + renew_data->start_time = tgtt->starttime; + renew_data->lifetime = tgtt->endtime; + renew_data->start_renew_at = (time_t) (tgtt->starttime + + 0.5 *(tgtt->endtime - tgtt->starttime)); + + ret = copy_pam_data(renew_data, pd, &renew_data->pd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "copy_pam_data failed.\n"); + goto done; + } + + sss_authtok_set_empty(renew_data->pd->newauthtok); + + ret = sss_authtok_set_ccfile(renew_data->pd->authtok, renew_data->ccfile, 0); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to store ccfile in auth token.\n"); + goto done; + } + + renew_data->pd->cmd = SSS_CMD_RENEW; + + value.type = HASH_VALUE_PTR; + value.ptr = renew_data; + + ret = hash_enter(krb5_ctx->renew_tgt_ctx->tgt_table, &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "hash_enter failed.\n"); + ret = EFAULT; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Added [%s] for renewal at [%.24s].\n", renew_data->ccfile, + ctime(&renew_data->start_renew_at)); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(renew_data); + } + return ret; +} diff --git a/src/providers/krb5/krb5_utils.c b/src/providers/krb5/krb5_utils.c new file mode 100644 index 0000000..b890745 --- /dev/null +++ b/src/providers/krb5/krb5_utils.c @@ -0,0 +1,605 @@ +/* + SSSD + + Kerberos 5 Backend Module -- Utilities + + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ +#include +#include +#include + +#include "providers/krb5/krb5_utils.h" +#include "providers/krb5/krb5_ccache.h" +#include "providers/krb5/krb5_auth.h" +#include "src/util/find_uid.h" +#include "util/util.h" + +errno_t find_or_guess_upn(TALLOC_CTX *mem_ctx, struct ldb_message *msg, + struct krb5_ctx *krb5_ctx, + struct sss_domain_info *dom, const char *user, + const char *user_dom, char **_upn) +{ + const char *upn = NULL; + int ret; + + if (krb5_ctx == NULL || dom == NULL || user == NULL || _upn == NULL) { + return EINVAL; + } + + if (msg != NULL) { + upn = ldb_msg_find_attr_as_string(msg, SYSDB_CANONICAL_UPN, NULL); + if (upn != NULL) { + ret = EOK; + goto done; + } + + upn = ldb_msg_find_attr_as_string(msg, SYSDB_UPN, NULL); + if (upn != NULL) { + ret = EOK; + goto done; + } + } + + ret = krb5_get_simple_upn(mem_ctx, krb5_ctx, dom, user, + user_dom, _upn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_get_simple_upn failed.\n"); + return ret; + } + +done: + if (ret == EOK && upn != NULL) { + *_upn = talloc_strdup(mem_ctx, upn); + if (*_upn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + } + + return ret; +} + +errno_t check_if_cached_upn_needs_update(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *user, + const char *upn) +{ + TALLOC_CTX *tmp_ctx; + int ret; + int sret; + const char *attrs[] = {SYSDB_UPN, SYSDB_CANONICAL_UPN, NULL}; + struct sysdb_attrs *new_attrs; + struct ldb_result *res; + bool in_transaction = false; + const char *cached_upn; + const char *cached_canonical_upn; + + if (sysdb == NULL || user == NULL || upn == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = sysdb_get_user_attr(tmp_ctx, domain, user, attrs, &res); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_user_attr failed.\n"); + goto done; + } + + if (res->count != 1) { + DEBUG(SSSDBG_OP_FAILURE, "[%d] user objects for name [%s] found, " \ + "expected 1.\n", res->count, user); + ret = EINVAL; + goto done; + } + + cached_upn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_UPN, NULL); + + if (cached_upn != NULL && strcmp(cached_upn, upn) == 0) { + DEBUG(SSSDBG_TRACE_ALL, "Cached UPN and new one match, " + "nothing to do.\n"); + ret = EOK; + goto done; + } + + cached_canonical_upn = ldb_msg_find_attr_as_string(res->msgs[0], + SYSDB_CANONICAL_UPN, + NULL); + + if (cached_canonical_upn != NULL + && strcmp(cached_canonical_upn, upn) == 0) { + DEBUG(SSSDBG_TRACE_ALL, "Cached canonical UPN and new one match, " + "nothing to do.\n"); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Replacing canonical UPN [%s] with [%s] " \ + "for user [%s].\n", + cached_canonical_upn == NULL ? + "empty" : cached_canonical_upn, + upn, user); + + new_attrs = sysdb_new_attrs(tmp_ctx); + if (new_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(new_attrs, SYSDB_CANONICAL_UPN, upn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string failed.\n"); + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Error %d starting transaction (%s)\n", ret, strerror(ret)); + goto done; + } + in_transaction = true; + + ret = sysdb_set_entry_attr(sysdb, res->msgs[0]->dn, new_attrs, + cached_canonical_upn == NULL ? SYSDB_MOD_ADD : + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_entry_attr failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + ret = EOK; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + + talloc_free(tmp_ctx); + + return ret; +} + +#define S_EXP_UID "{uid}" +#define L_EXP_UID (sizeof(S_EXP_UID) - 1) +#define S_EXP_USERID "{USERID}" +#define L_EXP_USERID (sizeof(S_EXP_USERID) - 1) +#define S_EXP_EUID "{euid}" +#define L_EXP_EUID (sizeof(S_EXP_EUID) - 1) +#define S_EXP_USERNAME "{username}" +#define L_EXP_USERNAME (sizeof(S_EXP_USERNAME) - 1) + +static errno_t +check_ccache_re(const char *filename, sss_regexp_t *illegal_re) +{ + errno_t ret; + + ret = sss_regexp_match(illegal_re, filename, 0, 0); + + if (ret == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Illegal pattern in ccache directory name [%s].\n", filename); + return EINVAL; + } else if (ret == SSS_REGEXP_ERROR_NOMATCH) { + DEBUG(SSSDBG_TRACE_LIBS, + "Ccache directory name [%s] does not contain " + "illegal patterns.\n", filename); + return EOK; + } + + DEBUG(SSSDBG_CRIT_FAILURE, "regexp match failed [%d].\n", ret); + return EFAULT; +} + +char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr, + const char *template, sss_regexp_t *illegal_re, + bool file_mode, bool case_sensitive) +{ + char *copy; + char *p; + char *n; + char *result = NULL; + char *dummy; + char *name; + char *res = NULL; + const char *cache_dir_tmpl; + TALLOC_CTX *tmp_ctx = NULL; + char action; + bool rerun; + int ret; + + if (template == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing template.\n"); + return NULL; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return NULL; + + copy = talloc_strdup(tmp_ctx, template); + if (copy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + goto done; + } + + result = talloc_strdup(tmp_ctx, ""); + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + goto done; + } + + p = copy; + while ( (n = strchr(p, '%')) != NULL) { + *n = '\0'; + n++; + if ( *n == '\0' ) { + DEBUG(SSSDBG_CRIT_FAILURE, + "format error, single %% at the end of the template.\n"); + goto done; + } + + rerun = true; + action = *n; + while (rerun) { + rerun = false; + switch (action) { + case 'u': + if (kr->pd->user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot expand user name template " + "because user name is empty.\n"); + goto done; + } + + name = sss_output_name(tmp_ctx, kr->pd->user, case_sensitive, 0); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_output_name failed\n"); + goto done; + } + + result = talloc_asprintf_append(result, "%s%s", p, + name); + break; + case 'U': + if (kr->uid <= 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand uid template " + "because uid is invalid.\n"); + goto done; + } + result = talloc_asprintf_append(result, "%s%"SPRIuid, p, + kr->uid); + break; + case 'p': + if (kr->upn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot expand user principal name template " + "because upn is empty.\n"); + goto done; + } + result = talloc_asprintf_append(result, "%s%s", p, kr->upn); + break; + case '%': + result = talloc_asprintf_append(result, "%s%%", p); + break; + case 'r': + dummy = dp_opt_get_string(kr->krb5_ctx->opts, KRB5_REALM); + if (dummy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing kerberos realm.\n"); + goto done; + } + result = talloc_asprintf_append(result, "%s%s", p, dummy); + break; + case 'h': + if (kr->homedir == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot expand home directory template " + "because the path is not available.\n"); + goto done; + } + result = talloc_asprintf_append(result, "%s%s", p, kr->homedir); + break; + case 'd': + if (file_mode) { + cache_dir_tmpl = dp_opt_get_string(kr->krb5_ctx->opts, + KRB5_CCACHEDIR); + if (cache_dir_tmpl == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing credential cache directory.\n"); + goto done; + } + + dummy = expand_ccname_template(tmp_ctx, kr, cache_dir_tmpl, + illegal_re, false, case_sensitive); + if (dummy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Expanding credential cache directory " + "template failed.\n"); + goto done; + } + result = talloc_asprintf_append(result, "%s%s", p, dummy); + talloc_zfree(dummy); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "'%%d' is not allowed in this template.\n"); + goto done; + } + break; + case 'P': + if (!file_mode) { + DEBUG(SSSDBG_CRIT_FAILURE, + "'%%P' is not allowed in this template.\n"); + goto done; + } + if (kr->pd->cli_pid == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand PID template " + "because PID is not available.\n"); + goto done; + } + result = talloc_asprintf_append(result, "%s%d", p, + kr->pd->cli_pid); + break; + + /* Additional syntax from krb5.conf default_ccache_name */ + case '{': + if (strncmp(n , S_EXP_UID, L_EXP_UID) == 0) { + action = 'U'; + n += L_EXP_UID - 1; + rerun = true; + continue; + } else if (strncmp(n , S_EXP_USERID, L_EXP_USERID) == 0) { + action = 'U'; + n += L_EXP_USERID - 1; + rerun = true; + continue; + } else if (strncmp(n , S_EXP_EUID, L_EXP_EUID) == 0) { + /* SSSD does not distinguish between uid and euid, + * so we treat both the same way */ + action = 'U'; + n += L_EXP_EUID - 1; + rerun = true; + continue; + } else if (strncmp(n , S_EXP_USERNAME, L_EXP_USERNAME) == 0) { + action = 'u'; + n += L_EXP_USERNAME - 1; + rerun = true; + continue; + } else { + /* ignore any expansion variable we do not understand and + * let libkrb5 hndle it or fail */ + name = n; + n = strchr(name, '}'); + if (!n) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid substitution sequence in cache " + "template. Missing closing '}' in [%s].\n", + template); + goto done; + } + result = talloc_asprintf_append(result, "%s%%%.*s", p, + (int)(n - name + 1), name); + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "format error, unknown template [%%%c].\n", *n); + goto done; + } + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n"); + goto done; + } + + p = n + 1; + } + + result = talloc_asprintf_append(result, "%s", p); + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n"); + goto done; + } + + if (illegal_re != NULL) { + ret = check_ccache_re(result, illegal_re); + if (ret != EOK) { + goto done; + } + } + + res = talloc_move(mem_ctx, &result); +done: + talloc_zfree(tmp_ctx); + return res; +} + +errno_t get_domain_or_subdomain(struct be_ctx *be_ctx, + char *domain_name, + struct sss_domain_info **dom) +{ + + if (domain_name != NULL && + strcasecmp(domain_name, be_ctx->domain->name) != 0) { + *dom = find_domain_by_name(be_ctx->domain, domain_name, true); + if (*dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); + return ENOMEM; + } + } else { + *dom = be_ctx->domain; + } + + return EOK; +} + +static errno_t split_tuple(TALLOC_CTX *mem_ctx, const char *tuple, + const char **_first, const char **_second) +{ + errno_t ret; + char **list; + int n; + + ret = split_on_separator(mem_ctx, tuple, ':', true, true, &list, &n); + + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "split_on_separator failed - %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } else if (n != 2) { + DEBUG(SSSDBG_MINOR_FAILURE, + "split_on_separator failed - Expected format is: " + "'username:primary' but got: '%s'.\n", tuple); + ret = EINVAL; + goto done; + } + + *_first = list[0]; + *_second = list[1]; + +done: + return ret; +} + +static errno_t +fill_name_to_primary_map(TALLOC_CTX *mem_ctx, char **map, + struct map_id_name_to_krb_primary *name_to_primary, + size_t size) +{ + int i; + errno_t ret; + + for (i = 0; i < size; i++) { + ret = split_tuple(mem_ctx, map[i], + &name_to_primary[i].id_name, + &name_to_primary[i].krb_primary); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "split_tuple failed - %s:[%d]\n", sss_strerror(ret), ret); + goto done; + } + } + + ret = EOK; + +done: + return ret; +} + +errno_t +parse_krb5_map_user(TALLOC_CTX *mem_ctx, + const char *krb5_map_user, + const char *dom_name, + struct map_id_name_to_krb_primary **_name_to_primary) +{ + int size; + char **map = NULL; + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct map_id_name_to_krb_primary *name_to_primary; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + if (krb5_map_user == NULL || strlen(krb5_map_user) == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "krb5_map_user is empty!\n"); + size = 0; + } else { + ret = split_on_separator(tmp_ctx, krb5_map_user, ',', true, true, + &map, &size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to parse krb5_map_user!\n"); + goto done; + } + } + + name_to_primary = talloc_zero_array(tmp_ctx, + struct map_id_name_to_krb_primary, + size + 1); + if (name_to_primary == NULL) { + ret = ENOMEM; + goto done; + } + /* sentinel */ + name_to_primary[size].id_name = NULL; + name_to_primary[size].krb_primary = NULL; + + if (size > 0) { + ret = fill_name_to_primary_map(name_to_primary, map, name_to_primary, + size); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "fill_name_to_primary_map failed: %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } + } + + /* conversion names to fully-qualified names */ + for (int i = 0; i < size; i++) { + name_to_primary[i].id_name = sss_create_internal_fqname( + name_to_primary, + name_to_primary[i].id_name, + dom_name); + if (name_to_primary[i].id_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed\n"); + ret = ENOMEM; + goto done; + } + + name_to_primary[i].krb_primary = sss_create_internal_fqname( + name_to_primary, + name_to_primary[i].krb_primary, + dom_name); + if (name_to_primary[i].krb_primary == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed\n"); + ret = ENOMEM; + goto done; + } + } + ret = EOK; + +done: + if (ret == EOK) { + *_name_to_primary = talloc_steal(mem_ctx, name_to_primary); + } + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/krb5/krb5_utils.h b/src/providers/krb5/krb5_utils.h new file mode 100644 index 0000000..473006b --- /dev/null +++ b/src/providers/krb5/krb5_utils.h @@ -0,0 +1,59 @@ +/* + SSSD + + Kerberos Backend, header file for utilities + + Authors: + Sumit Bose + + Copyright (C) 2009 Red Hat + + + 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 3 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; 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, see . +*/ + +#ifndef __KRB5_UTILS_H__ +#define __KRB5_UTILS_H__ + +#include +#include "config.h" + +#include "providers/krb5/krb5_auth.h" +#include "providers/data_provider.h" + +errno_t find_or_guess_upn(TALLOC_CTX *mem_ctx, struct ldb_message *msg, + struct krb5_ctx *krb5_ctx, + struct sss_domain_info *dom, const char *user, + const char *user_dom, char **_upn); + +errno_t check_if_cached_upn_needs_update(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *user, + const char *upn); + +char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr, + const char *template, sss_regexp_t *illegal_re, + bool file_mode, bool case_sensitive); + +errno_t get_domain_or_subdomain(struct be_ctx *be_ctx, + char *domain_name, + struct sss_domain_info **dom); + +errno_t +parse_krb5_map_user(TALLOC_CTX *mem_ctx, + const char *krb5_map_user, + const char *dom_name, + struct map_id_name_to_krb_primary **_name_to_primary); + +#endif /* __KRB5_UTILS_H__ */ diff --git a/src/providers/krb5/krb5_wait_queue.c b/src/providers/krb5/krb5_wait_queue.c new file mode 100644 index 0000000..06d7a98 --- /dev/null +++ b/src/providers/krb5/krb5_wait_queue.c @@ -0,0 +1,373 @@ +/* + SSSD + + Kerberos 5 Backend Module - Serialize the request of a user + + Authors: + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include + +#include "src/providers/krb5/krb5_auth.h" + +struct queue_entry { + struct queue_entry *prev; + struct queue_entry *next; + + struct be_ctx *be_ctx; + struct be_req *be_req; + struct tevent_req *parent_req; + struct pam_data *pd; + struct krb5_ctx *krb5_ctx; +}; + +static void wait_queue_auth_done(struct tevent_req *req); + +static void krb5_auth_queue_finish(struct tevent_req *req, errno_t ret, + int pam_status, int dp_err); + +static void wait_queue_auth(struct tevent_context *ev, struct tevent_timer *te, + struct timeval current_time, void *private_data) +{ + struct queue_entry *qe = talloc_get_type(private_data, struct queue_entry); + struct tevent_req *req; + + req = krb5_auth_send(qe->parent_req, qe->be_ctx->ev, + qe->be_ctx, qe->pd, qe->krb5_ctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n"); + } else { + tevent_req_set_callback(req, wait_queue_auth_done, + qe->parent_req); + } + + talloc_zfree(qe); +} + +static void wait_queue_auth_done(struct tevent_req *req) +{ + struct tevent_req *parent_req = \ + tevent_req_callback_data(req, struct tevent_req); + int pam_status; + int dp_err; + errno_t ret; + + ret = krb5_auth_recv(req, &pam_status, &dp_err); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_auth_recv failed: %d\n", ret); + } + + krb5_auth_queue_finish(parent_req, ret, pam_status, dp_err); +} + +static void wait_queue_del_cb(hash_entry_t *entry, hash_destroy_enum type, + void *pvt) +{ + struct queue_entry *head; + + if (entry->value.type == HASH_VALUE_PTR) { + head = talloc_get_type(entry->value.ptr, struct queue_entry); + talloc_zfree(head); + return; + } + + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected value type [%d].\n", entry->value.type); +} + +static errno_t add_to_wait_queue(struct be_ctx *be_ctx, + struct tevent_req *parent_req, + struct pam_data *pd, + struct krb5_ctx *krb5_ctx) +{ + int ret; + hash_key_t key; + hash_value_t value; + struct queue_entry *head; + struct queue_entry *queue_entry; + + if (krb5_ctx->wait_queue_hash == NULL) { + ret = sss_hash_create_ex(krb5_ctx, 0, + &krb5_ctx->wait_queue_hash, 0, 0, 0, 0, + wait_queue_del_cb, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_hash_create failed\n"); + return ret; + } + } + + key.type = HASH_KEY_STRING; + key.str = pd->user; + + ret = hash_lookup(krb5_ctx->wait_queue_hash, &key, &value); + switch (ret) { + case HASH_SUCCESS: + if (value.type != HASH_VALUE_PTR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected hash value type.\n"); + return EINVAL; + } + + head = talloc_get_type(value.ptr, struct queue_entry); + + queue_entry = talloc_zero(head, struct queue_entry); + if (queue_entry == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + queue_entry->be_ctx = be_ctx; + queue_entry->parent_req = parent_req; + queue_entry->pd = pd; + queue_entry->krb5_ctx = krb5_ctx; + + DLIST_ADD_END(head, queue_entry, struct queue_entry *); + + break; + case HASH_ERROR_KEY_NOT_FOUND: + value.type = HASH_VALUE_PTR; + head = talloc_zero(krb5_ctx->wait_queue_hash, struct queue_entry); + if (head == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + value.ptr = head; + + ret = hash_enter(krb5_ctx->wait_queue_hash, &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "hash_enter failed.\n"); + talloc_free(head); + return EIO; + } + + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "hash_lookup failed.\n"); + return EIO; + } + + if (head->next == NULL) { + return ENOENT; + } else { + return EOK; + } +} + +static void check_wait_queue(struct krb5_ctx *krb5_ctx, char *username) +{ + int ret; + hash_key_t key; + hash_value_t value; + struct queue_entry *head; + struct queue_entry *queue_entry; + struct tevent_timer *te; + + if (krb5_ctx->wait_queue_hash == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No wait queue available.\n"); + return; + } + + key.type = HASH_KEY_STRING; + key.str = username; + + ret = hash_lookup(krb5_ctx->wait_queue_hash, &key, &value); + + switch (ret) { + case HASH_SUCCESS: + if (value.type != HASH_VALUE_PTR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected hash value type.\n"); + return; + } + + head = talloc_get_type(value.ptr, struct queue_entry); + + if (head->next == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, + "Wait queue for user [%s] is empty.\n", username); + } else { + queue_entry = head->next; + + DLIST_REMOVE(head, queue_entry); + + te = tevent_add_timer(queue_entry->be_ctx->ev, krb5_ctx, + tevent_timeval_current(), wait_queue_auth, + queue_entry); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + } else { + return; + } + } + + ret = hash_delete(krb5_ctx->wait_queue_hash, &key); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to remove wait queue for user [%s].\n", + username); + } + + break; + case HASH_ERROR_KEY_NOT_FOUND: + DEBUG(SSSDBG_CRIT_FAILURE, + "No wait queue for user [%s] found.\n", username); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "hash_lookup failed.\n"); + } + + return; +} + +struct krb5_auth_queue_state { + struct krb5_ctx *krb5_ctx; + struct pam_data *pd; + + int pam_status; + int dp_err; +}; + +static void krb5_auth_queue_done(struct tevent_req *subreq); + +struct tevent_req *krb5_auth_queue_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct pam_data *pd, + struct krb5_ctx *krb5_ctx) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct krb5_auth_queue_state *state; + + req = tevent_req_create(mem_ctx, &state, struct krb5_auth_queue_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + state->krb5_ctx = krb5_ctx; + state->pd = pd; + + ret = add_to_wait_queue(be_ctx, req, pd, krb5_ctx); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "Request [%p] successfully added to wait queue " + "of user [%s].\n", req, pd->user); + ret = EOK; + goto immediate; + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_LIBS, "Wait queue of user [%s] is empty, " + "running request [%p] immediately.\n", pd->user, req); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add request to wait queue of user [%s], " + "running request [%p] immediately.\n", pd->user, req); + } + + subreq = krb5_auth_send(req, ev, be_ctx, pd, krb5_ctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_auth_send failed.\n"); + ret = ENOMEM; + goto immediate; + } + + tevent_req_set_callback(subreq, krb5_auth_queue_done, req); + + ret = EOK; + +immediate: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void krb5_auth_queue_done(struct tevent_req *subreq) +{ + struct tevent_req *req = \ + tevent_req_callback_data(subreq, struct tevent_req); + struct krb5_auth_queue_state *state = \ + tevent_req_data(req, struct krb5_auth_queue_state); + errno_t ret; + + ret = krb5_auth_recv(subreq, &state->pam_status, &state->dp_err); + talloc_zfree(subreq); + + check_wait_queue(state->krb5_ctx, state->pd->user); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_auth_recv failed with: %d\n", ret); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, "krb5_auth_queue request [%p] done.\n", req); + tevent_req_done(req); +} + +/* This is a violation of the tevent_req style. Ideally, the wait queue would + * be rewritten to the tevent_req style in the future, expose per-request recv + * and not hide the request underneath. But this function allows us to expose + * a tevent_req API for users of this module + */ +static void krb5_auth_queue_finish(struct tevent_req *req, + errno_t ret, + int pam_status, + int dp_err) +{ + struct krb5_auth_queue_state *state = \ + tevent_req_data(req, struct krb5_auth_queue_state); + + check_wait_queue(state->krb5_ctx, state->pd->user); + + state->pam_status = pam_status; + state->dp_err = dp_err; + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + DEBUG(SSSDBG_TRACE_LIBS, "krb5_auth_queue request [%p] done.\n", req); + tevent_req_done(req); + } +} + +int krb5_auth_queue_recv(struct tevent_req *req, + int *_pam_status, + int *_dp_err) +{ + struct krb5_auth_queue_state *state = \ + tevent_req_data(req, struct krb5_auth_queue_state); + + /* Returning values even on failure is not typical, but IPA password migration + * relies on receiving PAM_CRED_ERR even if the request fails.. + */ + if (_pam_status) { + *_pam_status = state->pam_status; + } + + if (_dp_err) { + *_dp_err = state->dp_err; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/ldap_access.c b/src/providers/ldap/ldap_access.c new file mode 100644 index 0000000..4ec4702 --- /dev/null +++ b/src/providers/ldap/ldap_access.c @@ -0,0 +1,128 @@ +/* + SSSD + + ldap_access.c + + Authors: + Simo Sorce + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include "src/util/util.h" +#include "src/providers/data_provider.h" +#include "src/providers/backend.h" +#include "src/providers/ldap/sdap_access.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_pam_access_handler_state { + struct pam_data *pd; +}; + +static void sdap_pam_access_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_pam_access_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_access_ctx *access_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct sdap_pam_access_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_pam_access_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + + subreq = sdap_access_send(state, params->ev, params->be_ctx, + params->domain, access_ctx, + access_ctx->id_ctx->conn, pd); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_pam_access_handler_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_pam_access_handler_done(struct tevent_req *subreq) +{ + struct sdap_pam_access_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_access_handler_state); + + ret = sdap_access_recv(subreq); + talloc_free(subreq); + switch (ret) { + case EOK: + case ERR_PASSWORD_EXPIRED_WARN: + state->pd->pam_status = PAM_SUCCESS; + break; + case ERR_ACCOUNT_EXPIRED: + state->pd->pam_status = PAM_ACCT_EXPIRED; + break; + case ERR_ACCESS_DENIED: + case ERR_PASSWORD_EXPIRED: + case ERR_PASSWORD_EXPIRED_REJECT: + state->pd->pam_status = PAM_PERM_DENIED; + break; + case ERR_PASSWORD_EXPIRED_RENEW: + state->pd->pam_status = PAM_NEW_AUTHTOK_REQD; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Error retrieving access check result.\n"); + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +sdap_pam_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct sdap_pam_access_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_pam_access_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/ldap/ldap_auth.c b/src/providers/ldap/ldap_auth.c new file mode 100644 index 0000000..8ec4d3a --- /dev/null +++ b/src/providers/ldap/ldap_auth.c @@ -0,0 +1,1641 @@ +/* + SSSD + + LDAP Backend Module + + Authors: + Sumit Bose + + Copyright (C) 2008 Red Hat + Copyright (C) 2010, rhafer@suse.de, Novell Inc. + + 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 3 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; 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, see . +*/ + +#ifdef WITH_MOZLDAP +#define LDAP_OPT_SUCCESS LDAP_SUCCESS +#define LDAP_TAG_EXOP_MODIFY_PASSWD_ID ((ber_tag_t) 0x80U) +#define LDAP_TAG_EXOP_MODIFY_PASSWD_OLD ((ber_tag_t) 0x81U) +#define LDAP_TAG_EXOP_MODIFY_PASSWD_NEW ((ber_tag_t) 0x82U) +#endif + +#include "config.h" + +#include +#include +#include +#include + +#include +#include + +#include "util/util.h" +#include "util/user_info_msg.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_auth.h" + + +#define LDAP_PWEXPIRE_WARNING_TIME 0 + +static errno_t add_expired_warning(struct pam_data *pd, long exp_time) +{ + int ret; + uint32_t *data; + + if (exp_time < 0 || exp_time > UINT32_MAX) { + DEBUG(SSSDBG_CRIT_FAILURE, "Time to expire out of range.\n"); + return EINVAL; + } + + data = talloc_array(pd, uint32_t, 2); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + + data[0] = SSS_PAM_USER_INFO_EXPIRE_WARN; + data[1] = (uint32_t) exp_time; + + ret = pam_add_response(pd, SSS_PAM_USER_INFO, 2 * sizeof(uint32_t), + (uint8_t *) data); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return EOK; +} + +static errno_t check_pwexpire_kerberos(const char *expire_date, time_t now, + struct pam_data *pd, + int pwd_exp_warning) +{ + time_t expire_time; + int expiration_warning; + int ret = ERR_INTERNAL; + + ret = sss_utc_to_time_t(expire_date, "%Y%m%d%H%M%SZ", + &expire_time); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "sss_utc_to_time_t failed with %d:%s.\n", + ret, sss_strerror(ret)); + return ret; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Time info: tzname[0] [%s] tzname[1] [%s] timezone [%ld] " + "daylight [%d] now [%"SPRItime"] expire_time [%"SPRItime"].\n", + tzname[0], tzname[1], timezone, daylight, now, expire_time); + + if (expire_time == 0) { + /* Used by the MIT LDAP KDB plugin to indicate "never" */ + ret = EOK; + } else if (difftime(now, expire_time) > 0.0) { + DEBUG(SSSDBG_CONF_SETTINGS, "Kerberos password expired.\n"); + if (pd != NULL) { + ret = add_expired_warning(pd, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n"); + } + } + ret = ERR_PASSWORD_EXPIRED; + } else { + if (pwd_exp_warning >= 0) { + expiration_warning = pwd_exp_warning; + } else { + expiration_warning = KERBEROS_PWEXPIRE_WARNING_TIME; + } + if (pd != NULL && + (difftime(now + expiration_warning, expire_time) > 0.0 || + expiration_warning == 0)) { + ret = add_expired_warning(pd, (long) difftime(expire_time, now)); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n"); + } + } + ret = EOK; + } + + return ret; +} + +static errno_t check_pwexpire_shadow(struct spwd *spwd, time_t now, + struct pam_data *pd) +{ + long today; + long password_age; + long exp; + int ret; + + if (spwd->sp_lstchg <= 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Last change day is not set, new password needed.\n"); + return ERR_PASSWORD_EXPIRED; + } + + today = (long) (now / (60 * 60 *24)); + password_age = today - spwd->sp_lstchg; + if (password_age < 0) { + DEBUG(SSSDBG_OP_FAILURE, + "The last password change time is in the future!.\n"); + return EOK; + } + + if ((spwd->sp_expire != -1 && today >= spwd->sp_expire) || + (spwd->sp_max != -1 && spwd->sp_inact != -1 && + password_age > spwd->sp_max + spwd->sp_inact)) + { + DEBUG(SSSDBG_CONF_SETTINGS, "Account expired.\n"); + return ERR_ACCOUNT_EXPIRED; + } + + if (spwd->sp_max != -1 && password_age > spwd->sp_max) { + DEBUG(SSSDBG_CONF_SETTINGS, "Password expired.\n"); + if (pd != NULL) { + ret = add_expired_warning(pd, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n"); + } + } + return ERR_PASSWORD_EXPIRED; + } + + if (pd != NULL && spwd->sp_max != -1 && spwd->sp_warn != -1 && + password_age > spwd->sp_max - spwd->sp_warn ) { + + /* add_expired_warning() expects time in seconds */ + exp = (spwd->sp_max - password_age) * (60 * 60 * 24); + if (exp == 0) { + /* Seconds until next midnight */ + exp = ((today + 1) * (60 * 60 * 24)) - now; + } + + ret = add_expired_warning(pd, exp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n"); + } + } + + return EOK; +} + +static errno_t check_pwexpire_ldap(struct pam_data *pd, + struct sdap_ppolicy_data *ppolicy, + int pwd_exp_warning) +{ + int ret = EOK; + + if (ppolicy->grace >= 0 || ppolicy->expire > 0) { + uint32_t *data; + uint32_t *ptr; + + if (pwd_exp_warning < 0) { + pwd_exp_warning = 0; + } + + data = talloc_size(pd, 2* sizeof(uint32_t)); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + ptr = data; + if (ppolicy->grace >= 0) { + *ptr = SSS_PAM_USER_INFO_GRACE_LOGIN; + ptr++; + *ptr = ppolicy->grace; + } else if (ppolicy->expire > 0) { + if (pwd_exp_warning != 0 && ppolicy->expire > pwd_exp_warning) { + /* do not warn */ + goto done; + } + + /* send warning */ + *ptr = SSS_PAM_USER_INFO_EXPIRE_WARN; + ptr++; + *ptr = ppolicy->expire; + } + + ret = pam_add_response(pd, SSS_PAM_USER_INFO, 2* sizeof(uint32_t), + (uint8_t*)data); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + +done: + return ret; +} + +errno_t check_pwexpire_policy(enum pwexpire pw_expire_type, + void *pw_expire_data, + struct pam_data *pd, + int pwd_expiration_warning) +{ + errno_t ret; + + switch (pw_expire_type) { + case PWEXPIRE_SHADOW: + ret = check_pwexpire_shadow(pw_expire_data, time(NULL), pd); + break; + case PWEXPIRE_KERBEROS: + ret = check_pwexpire_kerberos(pw_expire_data, time(NULL), pd, + pwd_expiration_warning); + break; + case PWEXPIRE_LDAP_PASSWORD_POLICY: + ret = check_pwexpire_ldap(pd, pw_expire_data, + pwd_expiration_warning); + break; + case PWEXPIRE_NONE: + ret = EOK; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown password expiration type %d.\n", pw_expire_type); + ret = EINVAL; + } + + return ret; +} + +static errno_t +find_password_expiration_attributes(TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + enum sdap_access_type access_type, + struct dp_option *opts, + enum pwexpire *pwd_exp_type, + void **data) +{ + const char *mark; + const char *val; + struct spwd *spwd; + const char *pwd_policy; + int ret; + + *pwd_exp_type = PWEXPIRE_NONE; + *data = NULL; + + switch (access_type) { + case SDAP_TYPE_IPA: + /* MIT-Kerberos is the only option for IPA */ + pwd_policy = PWD_POL_OPT_MIT; + break; + case SDAP_TYPE_LDAP: + pwd_policy = dp_opt_get_string(opts, SDAP_PWD_POLICY); + if (pwd_policy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing password policy.\n"); + return EINVAL; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE,"Unknown access_type [%i].\n", access_type); + return EINVAL; + } + + if (strcasecmp(pwd_policy, PWD_POL_OPT_NONE) == 0) { + DEBUG(SSSDBG_TRACE_ALL, "No password policy requested.\n"); + return EOK; + } else if (strcasecmp(pwd_policy, PWD_POL_OPT_MIT) == 0) { + mark = ldb_msg_find_attr_as_string(msg, SYSDB_KRBPW_LASTCHANGE, NULL); + if (mark != NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "Found Kerberos password expiration attributes.\n"); + val = ldb_msg_find_attr_as_string(msg, SYSDB_KRBPW_EXPIRATION, + NULL); + if (val != NULL) { + *data = talloc_strdup(mem_ctx, val); + if (*data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + *pwd_exp_type = PWEXPIRE_KERBEROS; + + return EOK; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "No Kerberos password expiration attributes found, " + "but MIT Kerberos password policy was requested. " + "Access will be denied.\n"); + return EACCES; + } + } else if (strcasecmp(pwd_policy, PWD_POL_OPT_SHADOW) == 0) { + mark = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_LASTCHANGE, NULL); + if (mark != NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "Found shadow password expiration attributes.\n"); + spwd = talloc_zero(mem_ctx, struct spwd); + if (spwd == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_LASTCHANGE, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_lstchg); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_MIN, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_min); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_MAX, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_max); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_WARNING, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_warn); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_INACTIVE, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_inact); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_EXPIRE, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_expire); + if (ret != EOK) goto shadow_fail; + + *data = spwd; + *pwd_exp_type = PWEXPIRE_SHADOW; + + return EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "No shadow password attributes found, " + "but shadow password policy was requested. " + "Access will be denied.\n"); + return EACCES; + } + } + + DEBUG(SSSDBG_TRACE_ALL, "No password expiration attributes found.\n"); + return EOK; + +shadow_fail: + talloc_free(spwd); + return ret; +} + +/* ==Get-User-DN========================================================== */ +struct get_user_dn_state { + char *username; + + char *orig_dn; +}; + +static void get_user_dn_done(struct tevent_req *subreq); + +static struct tevent_req *get_user_dn_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *domain, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *username) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct get_user_dn_state *state; + char *clean_name; + char *filter; + const char **attrs; + errno_t ret; + + req = tevent_req_create(memctx, &state, struct get_user_dn_state); + if (!req) return NULL; + + ret = sss_parse_internal_fqname(state, username, + &state->username, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot parse %s\n", username); + goto done; + } + + ret = sss_filter_sanitize(state, state->username, &clean_name); + if (ret != EOK) { + goto done; + } + + filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + opts->user_map[SDAP_AT_USER_NAME].name, + clean_name, + opts->user_map[SDAP_OC_USER].name); + talloc_zfree(clean_name); + if (filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build the base filter\n"); + ret = ENOMEM; + goto done; + } + + /* We're mostly interested in the DN anyway */ + attrs = talloc_array(state, const char *, 3); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + attrs[0] = "objectclass"; + attrs[1] = opts->user_map[SDAP_AT_USER_NAME].name; + attrs[2] = NULL; + + subreq = sdap_search_user_send(state, ev, domain, opts, + opts->sdom->user_search_bases, + sh, attrs, filter, + dp_opt_get_int(opts->basic, + SDAP_SEARCH_TIMEOUT), + SDAP_LOOKUP_SINGLE); + if (!subreq) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, get_user_dn_done, req); + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static void get_user_dn_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_user_dn_state *state = tevent_req_data(req, + struct get_user_dn_state); + struct ldb_message_element *el; + struct sysdb_attrs **users; + size_t count; + + ret = sdap_search_user_recv(state, subreq, NULL, &users, &count); + talloc_zfree(subreq); + if (ret && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to retrieve users\n"); + tevent_req_error(req, ret); + return; + } + + if (count == 0) { + DEBUG(SSSDBG_OP_FAILURE, "No such user\n"); + tevent_req_error(req, ENOMEM); + return; + } else if (count > 1) { + DEBUG(SSSDBG_OP_FAILURE, "Multiple users matched\n"); + tevent_req_error(req, EIO); + return; + } + + /* exactly one user. Get the originalDN */ + ret = sysdb_attrs_get_el_ext(users[0], SYSDB_ORIG_DN, false, &el); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "originalDN is not available for [%s].\n", state->username); + tevent_req_error(req, ret); + return; + } + + state->orig_dn = talloc_strdup(state, (const char *) el->values[0].data); + if (state->orig_dn == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Found originalDN [%s] for [%s]\n", + state->orig_dn, state->username); + tevent_req_done(req); +} + +static int get_user_dn_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, + char **orig_dn) +{ + struct get_user_dn_state *state = tevent_req_data(req, + struct get_user_dn_state); + + if (orig_dn) { + *orig_dn = talloc_move(mem_ctx, &state->orig_dn); + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +int get_user_dn(TALLOC_CTX *memctx, + struct sss_domain_info *domain, + enum sdap_access_type access_type, + struct sdap_options *opts, + const char *username, + char **user_dn, + enum pwexpire *user_pw_expire_type, + void **user_pw_expire_data) +{ + TALLOC_CTX *tmpctx; + enum pwexpire pw_expire_type = PWEXPIRE_NONE; + void *pw_expire_data; + struct ldb_result *res; + const char **attrs; + const char *dn = NULL; + int ret; + + tmpctx = talloc_new(memctx); + if (!tmpctx) { + return ENOMEM; + } + + attrs = talloc_array(tmpctx, const char *, 11); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + attrs[0] = SYSDB_ORIG_DN; + attrs[1] = SYSDB_SHADOWPW_LASTCHANGE; + attrs[2] = SYSDB_SHADOWPW_MIN; + attrs[3] = SYSDB_SHADOWPW_MAX; + attrs[4] = SYSDB_SHADOWPW_WARNING; + attrs[5] = SYSDB_SHADOWPW_INACTIVE; + attrs[6] = SYSDB_SHADOWPW_EXPIRE; + attrs[7] = SYSDB_KRBPW_LASTCHANGE; + attrs[8] = SYSDB_KRBPW_EXPIRATION; + attrs[9] = SYSDB_PWD_ATTRIBUTE; + attrs[10] = NULL; + + ret = sysdb_get_user_attr(tmpctx, domain, username, attrs, &res); + if (ret) { + goto done; + } + + switch (res->count) { + case 0: + /* No such user entry? Look it up */ + ret = EAGAIN; + break; + + case 1: + dn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_ORIG_DN, NULL); + if (dn == NULL) { + /* The user entry has no original DN. This is the case when the ID + * provider is not LDAP-based (proxy perhaps) */ + ret = EAGAIN; + break; + } + + dn = talloc_strdup(tmpctx, dn); + if (!dn) { + ret = ENOMEM; + break; + } + + ret = find_password_expiration_attributes(tmpctx, + res->msgs[0], + access_type, + opts->basic, + &pw_expire_type, + &pw_expire_data); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "find_password_expiration_attributes failed.\n"); + } + break; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "User search by name (%s) returned > 1 results!\n", + username); + ret = EFAULT; + break; + } + +done: + if (ret == EOK) { + *user_dn = talloc_strdup(memctx, dn); + if (!*user_dn) { + ret = ENOMEM; + } + /* pw_expire_data may be NULL */ + *user_pw_expire_data = talloc_steal(memctx, pw_expire_data); + *user_pw_expire_type = pw_expire_type; + } + + talloc_zfree(tmpctx); + return ret; +} + +/* ==Authenticate-User==================================================== */ + +struct auth_state { + struct tevent_context *ev; + struct sdap_auth_ctx *ctx; + const char *username; + struct sss_auth_token *authtok; + struct sdap_service *sdap_service; + + struct sdap_handle *sh; + + char *dn; + enum pwexpire pw_expire_type; + void *pw_expire_data; +}; + +static struct tevent_req *auth_connect_send(struct tevent_req *req); +static void auth_get_dn_done(struct tevent_req *subreq); +static void auth_do_bind(struct tevent_req *req); +static void auth_connect_done(struct tevent_req *subreq); +static void auth_bind_user_done(struct tevent_req *subreq); + +static struct tevent_req *auth_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_auth_ctx *ctx, + const char *username, + struct sss_auth_token *authtok, + bool try_chpass_service) +{ + struct tevent_req *req; + struct auth_state *state; + errno_t ret; + + req = tevent_req_create(memctx, &state, struct auth_state); + if (!req) return NULL; + + /* The token must be a password token */ + if (sss_authtok_get_type(authtok) != SSS_AUTHTOK_TYPE_PASSWORD) { + if (sss_authtok_get_type(authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(authtok) == SSS_AUTHTOK_TYPE_SC_KEYPAD) { + /* Tell frontend that we do not support Smartcard authentication */ + ret = ERR_SC_AUTH_NOT_SUPPORTED; + } else { + ret = ERR_AUTH_FAILED; + } + goto fail; + } + + state->ev = ev; + state->ctx = ctx; + state->username = username; + state->authtok = authtok; + if (try_chpass_service && ctx->chpass_service != NULL && + ctx->chpass_service->name != NULL) { + state->sdap_service = ctx->chpass_service; + } else { + state->sdap_service = ctx->service; + } + + ret = get_user_dn(state, state->ctx->be->domain, SDAP_TYPE_LDAP, + state->ctx->opts, state->username, &state->dn, + &state->pw_expire_type, &state->pw_expire_data); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_FUNC, + "Need to look up the DN of %s later\n", state->username); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get user DN [%d]: %s\n", ret, sss_strerror(ret)); + goto fail; + } + + if (auth_connect_send(req) == NULL) { + ret = ENOMEM; + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static struct tevent_req *auth_connect_send(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct auth_state *state = tevent_req_data(req, + struct auth_state); + bool use_tls; + bool skip_conn_auth = false; + const char *sasl_mech; + + /* Check for undocumented debugging feature to disable TLS + * for authentication. This should never be used in production + * for obvious reasons. + */ + use_tls = !dp_opt_get_bool(state->ctx->opts->basic, SDAP_DISABLE_AUTH_TLS); + if (!use_tls) { + sss_log(SSS_LOG_ALERT, "LDAP authentication being performed over " + "insecure connection. This should be done " + "for debugging purposes only."); + } + + if (state->dn != NULL) { + /* In case the user's DN is known, the connection will only be used + * to bind as the user to perform the authentication. In that case, + * we don't need to authenticate the connection, because we're not + * looking up any information using the connection. This might be + * needed e.g. in case both ID and AUTH providers are set to LDAP + * and the server is AD, because otherwise the connection would both + * do a startTLS and later bind using GSSAPI or GSS-SPNEGO which + * doesn't work well with AD. + */ + skip_conn_auth = true; + } + + if (skip_conn_auth == false) { + sasl_mech = dp_opt_get_string(state->ctx->opts->basic, + SDAP_SASL_MECH); + if (sasl_mech && sdap_sasl_mech_needs_kinit(sasl_mech)) { + /* Don't force TLS on if we're told to use GSSAPI or GSS-SPNEGO */ + use_tls = false; + } + } + + if (ldap_is_ldapi_url(state->sdap_service->uri)) { + /* Don't force TLS on if we're a unix domain socket */ + use_tls = false; + } + + subreq = sdap_cli_connect_send(state, state->ev, state->ctx->opts, + state->ctx->be, + state->sdap_service, false, + use_tls ? CON_TLS_ON : CON_TLS_OFF, + skip_conn_auth); + + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return NULL; + } + + tevent_req_set_callback(subreq, auth_connect_done, req); + + return subreq; +} + +static bool check_encryption_used(LDAP *ldap) +{ + ber_len_t sasl_ssf = 0; + int tls_inplace = 0; + int ret; + + ret = ldap_get_option(ldap, LDAP_OPT_X_SASL_SSF, &sasl_ssf); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_TRACE_LIBS, "ldap_get_option failed to get sasl ssf, " + "assuming SASL is not used.\n"); + sasl_ssf = 0; + } + + tls_inplace = ldap_tls_inplace(ldap); + + DEBUG(SSSDBG_TRACE_ALL, + "Encryption used: SASL SSF [%lu] tls_inplace [%s].\n", sasl_ssf, + tls_inplace == 1 ? "TLS inplace" : "TLS NOT inplace"); + + if (sasl_ssf <= 1 && tls_inplace != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No encryption detected on LDAP connection.\n"); + sss_log(SSS_LOG_CRIT, "No encryption detected on LDAP connection.\n"); + return false; + } + + return true; +} + +static void auth_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct auth_state *state = tevent_req_data(req, + struct auth_state); + int ret; + + ret = sdap_cli_connect_recv(subreq, state, NULL, &state->sh, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + /* As sdap_cli_connect_recv() returns EIO in case all the servers are + * down and we have to go offline, let's treat it accordingly here and + * allow the PAM responder to switch to offline authentication. + * + * Unfortunately, there's not much pattern within our code and the way + * to indicate we're going down in this part of the code is returning + * an ETIMEDOUT. + */ + if (ret == EIO) { + tevent_req_error(req, ETIMEDOUT); + } else { + if (auth_connect_send(req) == NULL) { + tevent_req_error(req, ENOMEM); + } + } + return; + } + + if (!ldap_is_ldapi_url(state->sdap_service->uri) && + !check_encryption_used(state->sh->ldap) && + !dp_opt_get_bool(state->ctx->opts->basic, SDAP_DISABLE_AUTH_TLS)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Aborting the authentication request.\n"); + sss_log(SSS_LOG_CRIT, "Aborting the authentication request.\n"); + tevent_req_error(req, ERR_AUTH_FAILED); + return; + } + + if (state->dn == NULL) { + /* The cached user entry was missing the bind DN. Need to look + * it up based on user name in order to perform the bind */ + subreq = get_user_dn_send(req, state->ev, state->ctx->be->domain, + state->sh, state->ctx->opts, state->username); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, auth_get_dn_done, req); + return; + } + + /* All required user data was pre-cached during an identity lookup. + * We can proceed with the bind */ + auth_do_bind(req); + return; +} + +static void auth_get_dn_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct auth_state *state = tevent_req_data(req, struct auth_state); + errno_t ret; + + ret = get_user_dn_recv(state, subreq, &state->dn); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ERR_ACCOUNT_UNKNOWN); + return; + } + + /* The DN was found with an LDAP lookup + * We can proceed with the bind */ + return auth_do_bind(req); +} + +static void auth_do_bind(struct tevent_req *req) +{ + struct auth_state *state = tevent_req_data(req, struct auth_state); + struct tevent_req *subreq; + + subreq = sdap_auth_send(state, state->ev, state->sh, + NULL, NULL, state->dn, + state->authtok, + dp_opt_get_int(state->ctx->opts->basic, + SDAP_OPT_TIMEOUT)); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, auth_bind_user_done, req); +} + +static void auth_bind_user_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct auth_state *state = tevent_req_data(req, + struct auth_state); + int ret; + struct sdap_ppolicy_data *ppolicy = NULL; + + ret = sdap_auth_recv(subreq, state, &ppolicy); + talloc_zfree(subreq); + if (ppolicy != NULL) { + DEBUG(SSSDBG_TRACE_ALL,"Found ppolicy data, " + "assuming LDAP password policies are active.\n"); + state->pw_expire_type = PWEXPIRE_LDAP_PASSWORD_POLICY; + state->pw_expire_data = ppolicy; + } + switch (ret) { + case EOK: + break; + case ETIMEDOUT: + case ERR_NETWORK_IO: + if (auth_connect_send(req) == NULL) { + tevent_req_error(req, ENOMEM); + } + return; + default: + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t auth_recv(struct tevent_req *req, TALLOC_CTX *memctx, + struct sdap_handle **sh, char **dn, + enum pwexpire *pw_expire_type, void **pw_expire_data) +{ + struct auth_state *state = tevent_req_data(req, struct auth_state); + + if (sh != NULL) { + *sh = talloc_steal(memctx, state->sh); + if (*sh == NULL) return ENOMEM; + } + + if (dn != NULL) { + *dn = talloc_steal(memctx, state->dn); + if (*dn == NULL) return ENOMEM; + } + + if (pw_expire_data != NULL) { + *pw_expire_data = talloc_steal(memctx, state->pw_expire_data); + } + + *pw_expire_type = state->pw_expire_type; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_pam_auth_handler_state { + struct pam_data *pd; + struct be_ctx *be_ctx; +}; + +static void sdap_pam_auth_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_pam_auth_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct sdap_pam_auth_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_pam_auth_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->be_ctx = params->be_ctx; + pd->pam_status = PAM_SYSTEM_ERR; + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + subreq = auth_send(state, params->ev, auth_ctx, + pd->user, pd->authtok, false); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_pam_auth_handler_done, req); + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + subreq = auth_send(state, params->ev, auth_ctx, + pd->user, pd->authtok, true); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_pam_auth_handler_done, req); + break; + case SSS_PAM_CHAUTHTOK: + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_SETCRED: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + pd->pam_status = PAM_SUCCESS; + goto immediately; + default: + pd->pam_status = PAM_MODULE_UNKNOWN; + goto immediately; + } + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_pam_auth_handler_done(struct tevent_req *subreq) +{ + struct sdap_pam_auth_handler_state *state; + struct tevent_req *req; + enum pwexpire pw_expire_type; + void *pw_expire_data; + const char *password; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_auth_handler_state); + + ret = auth_recv(subreq, state, NULL, NULL, + &pw_expire_type, &pw_expire_data); + talloc_free(subreq); + + if (ret == EOK) { + ret = check_pwexpire_policy(pw_expire_type, pw_expire_data, state->pd, + state->be_ctx->domain->pwd_expiration_warning); + if (ret == EINVAL) { + /* Unknown password expiration type. */ + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + } + + switch (ret) { + case EOK: + state->pd->pam_status = PAM_SUCCESS; + break; + case ERR_AUTH_DENIED: + state->pd->pam_status = PAM_PERM_DENIED; + break; + case ERR_AUTH_FAILED: + state->pd->pam_status = PAM_AUTH_ERR; + break; + case ETIMEDOUT: + case ERR_NETWORK_IO: + state->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + be_mark_offline(state->be_ctx); + break; + case ERR_ACCOUNT_EXPIRED: + state->pd->pam_status = PAM_ACCT_EXPIRED; + break; + case ERR_PASSWORD_EXPIRED: + state->pd->pam_status = PAM_NEW_AUTHTOK_REQD; + break; + case ERR_ACCOUNT_LOCKED: + state->pd->account_locked = true; + state->pd->pam_status = PAM_PERM_DENIED; + break; + case ERR_SC_AUTH_NOT_SUPPORTED: + state->pd->pam_status = PAM_BAD_ITEM; + break; + default: + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + } + + if (ret == EOK && state->be_ctx->domain->cache_credentials) { + ret = sss_authtok_get_password(state->pd->authtok, &password, NULL); + if (ret == EOK) { + ret = sysdb_cache_password(state->be_ctx->domain, state->pd->user, + password); + } + + /* password caching failures are not fatal errors */ + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to cache password for %s\n", + state->pd->user); + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "Password successfully cached for %s\n", + state->pd->user); + } + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +sdap_pam_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct sdap_pam_auth_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_pam_auth_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} + +struct sdap_pam_change_password_state { + enum pwmodify_mode mode; + char *user_error_message; +}; + +static void sdap_pam_change_password_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_pam_change_password_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct pam_data *pd, + char *user_dn) +{ + struct sdap_pam_change_password_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + const char *password; + const char *new_password; + char *pwd_attr; + int timeout; + errno_t ret; + + pwd_attr = opts->user_map[SDAP_AT_USER_PWD].name; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_pam_change_password_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->mode = opts->pwmodify_mode; + + ret = sss_authtok_get_password(pd->authtok, &password, NULL); + if (ret != EOK) { + goto done; + } + + ret = sss_authtok_get_password(pd->newauthtok, &new_password, NULL); + if (ret != EOK) { + goto done; + } + + timeout = dp_opt_get_int(opts->basic, SDAP_OPT_TIMEOUT); + + switch (opts->pwmodify_mode) { + case SDAP_PWMODIFY_EXOP: + subreq = sdap_exop_modify_passwd_send(state, ev, sh, user_dn, + password, new_password, + timeout); + break; + case SDAP_PWMODIFY_LDAP: + subreq = sdap_modify_passwd_send(state, ev, sh, timeout, pwd_attr, + user_dn, new_password); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unrecognized pwmodify mode: %d\n", + opts->pwmodify_mode); + ret = EINVAL; + goto done; + } + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_pam_change_password_done, req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_pam_change_password_done(struct tevent_req *subreq) +{ + struct sdap_pam_change_password_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_change_password_state); + + switch (state->mode) { + case SDAP_PWMODIFY_EXOP: + ret = sdap_exop_modify_passwd_recv(subreq, state, + &state->user_error_message); + break; + case SDAP_PWMODIFY_LDAP: + ret = sdap_modify_passwd_recv(subreq, state, + &state->user_error_message); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unrecognized pwmodify mode: %d\n", + state->mode); + ret = EINVAL; + } + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +static errno_t +sdap_pam_change_password_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_user_error_message) +{ + struct sdap_pam_change_password_state *state; + state = tevent_req_data(req, struct sdap_pam_change_password_state); + + /* We want to return the error message even on failure */ + *_user_error_message = talloc_steal(mem_ctx, state->user_error_message); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +struct sdap_pam_chpass_handler_state { + struct be_ctx *be_ctx; + struct tevent_context *ev; + struct sdap_auth_ctx *auth_ctx; + struct pam_data *pd; + struct sdap_handle *sh; + char *dn; + enum pwexpire pw_expire_type; +}; + +static void sdap_pam_chpass_handler_auth_done(struct tevent_req *subreq); +static int +sdap_pam_chpass_handler_change_step(struct sdap_pam_chpass_handler_state *state, + struct tevent_req *req, + enum pwexpire pw_expire_type); +static void sdap_pam_chpass_handler_chpass_done(struct tevent_req *subreq); +static void sdap_pam_chpass_handler_last_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_pam_chpass_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct sdap_pam_chpass_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_pam_chpass_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->be_ctx = params->be_ctx; + state->auth_ctx = auth_ctx; + state->ev = params->ev; + + if (be_is_offline(state->be_ctx)) { + pd->pam_status = PAM_AUTHINFO_UNAVAIL; + goto immediately; + } + + if ((pd->priv == 1) && (pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) && + (sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_PASSWORD)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Password reset by root is not supported.\n"); + pd->pam_status = PAM_PERM_DENIED; + goto immediately; + } + + DEBUG(SSSDBG_OP_FAILURE, + "starting password change request for user [%s].\n", pd->user); + + pd->pam_status = PAM_SYSTEM_ERR; + + if (pd->cmd != SSS_PAM_CHAUTHTOK && pd->cmd != SSS_PAM_CHAUTHTOK_PRELIM) { + DEBUG(SSSDBG_OP_FAILURE, + "chpass target was called by wrong pam command.\n"); + goto immediately; + } + + subreq = auth_send(state, params->ev, auth_ctx, + pd->user, pd->authtok, true); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_pam_chpass_handler_auth_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static bool confdb_is_set_explicit(struct confdb_ctx *cdb, const char *section, + const char *attribute) +{ + int ret; + char **vals = NULL; + bool update_option_set_explictly = false; + + ret = confdb_get_param(cdb, NULL, section, attribute, &vals); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to check if [%s] is set " + "explicitly in sssd.conf, assuming it is not set.\n", + attribute); + } else if (vals != NULL && vals[0] != NULL) { + update_option_set_explictly = true; + } + talloc_free(vals); + + return update_option_set_explictly; +} + +static void sdap_pam_chpass_handler_auth_done(struct tevent_req *subreq) +{ + struct sdap_pam_chpass_handler_state *state; + struct tevent_req *req; + void *pw_expire_data; + size_t msg_len; + uint8_t *msg; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_chpass_handler_state); + + ret = auth_recv(subreq, state, &state->sh, &state->dn, + &state->pw_expire_type, &pw_expire_data); + talloc_free(subreq); + + if ((ret == EOK || ret == ERR_PASSWORD_EXPIRED) && + state->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { + DEBUG(SSSDBG_TRACE_ALL, "Initial authentication for change " + "password operation successful.\n"); + state->pd->pam_status = PAM_SUCCESS; + goto done; + } + + if (ret == EOK) { + switch (state->pw_expire_type) { + case PWEXPIRE_SHADOW: + ret = check_pwexpire_shadow(pw_expire_data, time(NULL), NULL); + break; + case PWEXPIRE_KERBEROS: + ret = check_pwexpire_kerberos(pw_expire_data, time(NULL), NULL, + state->be_ctx->domain->pwd_expiration_warning); + + if (ret == ERR_PASSWORD_EXPIRED) { + DEBUG(SSSDBG_CRIT_FAILURE, "LDAP provider cannot change " + "kerberos passwords.\n"); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + break; + case PWEXPIRE_LDAP_PASSWORD_POLICY: + case PWEXPIRE_NONE: + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown password expiration type %d.\n", + state->pw_expire_type); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + } + + switch (ret) { + case EOK: + case ERR_PASSWORD_EXPIRED: + DEBUG(SSSDBG_TRACE_LIBS, + "user [%s] successfully authenticated.\n", state->dn); + ret = sdap_pam_chpass_handler_change_step(state, req, + state->pw_expire_type); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_pam_chpass_handler_change_step() failed.\n"); + goto done; + } + return; + break; + case ERR_AUTH_DENIED: + case ERR_AUTH_FAILED: + state->pd->pam_status = PAM_AUTH_ERR; + ret = pack_user_info_chpass_error(state->pd, "Old password not " + "accepted.", &msg_len, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pack_user_info_chpass_error failed.\n"); + } else { + ret = pam_add_response(state->pd, SSS_PAM_USER_INFO, + msg_len, msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + break; + case ETIMEDOUT: + case ERR_NETWORK_IO: + state->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + be_mark_offline(state->be_ctx); + break; + default: + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static int +sdap_pam_chpass_handler_change_step(struct sdap_pam_chpass_handler_state *state, + struct tevent_req *req, + enum pwexpire pw_expire_type) +{ + int ret; + struct tevent_req *subreq; + bool update_option_set_explictly = false; + + if (pw_expire_type == PWEXPIRE_SHADOW) { + + update_option_set_explictly = confdb_is_set_explicit( + state->be_ctx->cdb, state->be_ctx->conf_path, + state->auth_ctx->opts->basic[SDAP_CHPASS_UPDATE_LAST_CHANGE].opt_name); + + if (!dp_opt_get_bool(state->auth_ctx->opts->basic, + SDAP_CHPASS_UPDATE_LAST_CHANGE) + && !update_option_set_explictly) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Shadow password policy is selected but " + "ldap_chpass_update_last_change is not set, please " + "make sure your LDAP server can update the [%s] " + "attribute automatically. Otherwise SSSD might " + "consider your password as expired.\n", + state->auth_ctx->opts->user_map[SDAP_AT_SP_LSTCHG].name); + } + ret = sysdb_invalidate_cache_entry(state->be_ctx->domain, + state->pd->user, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to invalidate cache entry for user [%s] with error code " + "[%d][%s]. The last changed attribute [%s] might not get " + "refreshed after the password and the password might still be " + "considered as expired. Call sss_cache for this user " + "to expire the entry manually in this case.\n", + state->pd->user, ret, sss_strerror(ret), + state->auth_ctx->opts->user_map[SDAP_AT_SP_LSTCHG].name); + } + } + subreq = sdap_pam_change_password_send(state, state->ev, + state->sh, + state->auth_ctx->opts, + state->pd, + state->dn); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to change password for " + "%s\n", state->pd->user); + state->pd->pam_status = PAM_SYSTEM_ERR; + return ENOMEM; + } + + tevent_req_set_callback(subreq, + sdap_pam_chpass_handler_chpass_done, + req); + return EOK; +} + +static void sdap_pam_chpass_handler_chpass_done(struct tevent_req *subreq) +{ + struct sdap_pam_chpass_handler_state *state; + struct tevent_req *req; + char *user_error_message = NULL; + char *lastchanged_name; + size_t msg_len; + uint8_t *msg; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_chpass_handler_state); + + ret = sdap_pam_change_password_recv(state, subreq, &user_error_message); + talloc_free(subreq); + + switch (ret) { + case EOK: + if (state->pw_expire_type == PWEXPIRE_SHADOW) { + ret = sysdb_update_user_shadow_last_change(state->be_ctx->domain, + state->pd->user, SYSDB_SHADOWPW_LASTCHANGE); + if (ret != EOK) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + } + + state->pd->pam_status = PAM_SUCCESS; + break; + case ERR_CHPASS_DENIED: + state->pd->pam_status = PAM_NEW_AUTHTOK_REQD; + break; + case ERR_NETWORK_IO: + state->pd->pam_status = PAM_AUTHTOK_ERR; + break; + default: + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + } + + if (state->pd->pam_status != PAM_SUCCESS && user_error_message != NULL) { + ret = pack_user_info_chpass_error(state->pd, user_error_message, + &msg_len, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pack_user_info_chpass_error failed.\n"); + } else { + ret = pam_add_response(state->pd, SSS_PAM_USER_INFO, msg_len, msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + } + + if (state->pd->pam_status == PAM_SUCCESS && + dp_opt_get_bool(state->auth_ctx->opts->basic, + SDAP_CHPASS_UPDATE_LAST_CHANGE)) { + lastchanged_name = state->auth_ctx->opts->user_map[SDAP_AT_SP_LSTCHG].name; + + subreq = sdap_modify_shadow_lastchange_send(state, state->ev, + state->sh, state->dn, + lastchanged_name); + if (subreq == NULL) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + tevent_req_set_callback(subreq, sdap_pam_chpass_handler_last_done, req); + return; + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void sdap_pam_chpass_handler_last_done(struct tevent_req *subreq) +{ + struct sdap_pam_chpass_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_chpass_handler_state); + + ret = sdap_modify_shadow_lastchange_recv(subreq); + talloc_free(subreq); + + if (ret != EOK) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + state->pd->pam_status = PAM_SUCCESS; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +sdap_pam_chpass_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct sdap_pam_chpass_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_pam_chpass_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/ldap/ldap_auth.h b/src/providers/ldap/ldap_auth.h new file mode 100644 index 0000000..0a277ed --- /dev/null +++ b/src/providers/ldap/ldap_auth.h @@ -0,0 +1,49 @@ +/* + SSSD + + Copyright (C) Pavel Reichl 2015 + + 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 3 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; 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, see . +*/ + +#ifndef _LDAP_AUTH_H_ +#define _LDAP_AUTH_H_ + +#include "config.h" + +#include "providers/ldap/sdap_access.h" + +enum pwexpire { + PWEXPIRE_NONE = 0, + PWEXPIRE_LDAP_PASSWORD_POLICY, + PWEXPIRE_KERBEROS, + PWEXPIRE_SHADOW +}; + +int get_user_dn(TALLOC_CTX *memctx, + struct sss_domain_info *domain, + enum sdap_access_type access_type, + struct sdap_options *opts, + const char *username, + char **user_dn, + enum pwexpire *user_pw_expire_type, + void **user_pw_expire_data); + +errno_t check_pwexpire_policy(enum pwexpire pw_expire_type, + void *pw_expire_data, + struct pam_data *pd, + errno_t checkb); + + +#endif /* _LDAP_AUTH_H_ */ diff --git a/src/providers/ldap/ldap_child.c b/src/providers/ldap/ldap_child.c new file mode 100644 index 0000000..6c167d2 --- /dev/null +++ b/src/providers/ldap/ldap_child.c @@ -0,0 +1,788 @@ +/* + SSSD + + LDAP Backend Module -- prime ccache with TGT in a child process + + Authors: + Jakub Hrozek + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "util/sss_krb5.h" +#include "util/child_common.h" +#include "providers/backend.h" +#include "providers/krb5/krb5_common.h" + +char *global_ccname_file_dummy = NULL; + +static void sig_term_handler(int sig) +{ + if (global_ccname_file_dummy != NULL) { + /* Cast to void to avoid a complaint by Coverity */ + (void) unlink(global_ccname_file_dummy); + } + + _exit(CHILD_TIMEOUT_EXIT_CODE); +} + +static krb5_context krb5_error_ctx; +#define LDAP_CHILD_DEBUG(level, error) KRB5_DEBUG(level, krb5_error_ctx, error) + +struct input_buffer { + const char *realm_str; + const char *princ_str; + char *keytab_name; + krb5_deltat lifetime; + krb5_context context; + uid_t uid; + gid_t gid; +}; + +static errno_t unpack_buffer(uint8_t *buf, size_t size, + struct input_buffer *ibuf) +{ + size_t p = 0; + uint32_t len; + + DEBUG(SSSDBG_TRACE_LIBS, "total buffer size: %zu\n", size); + + /* realm_str size and length */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + + DEBUG(SSSDBG_TRACE_LIBS, "realm_str size: %d\n", len); + if (len) { + if (len > size - p) return EINVAL; + ibuf->realm_str = talloc_strndup(ibuf, (char *)(buf + p), len); + DEBUG(SSSDBG_TRACE_LIBS, "got realm_str: %s\n", ibuf->realm_str); + if (ibuf->realm_str == NULL) return ENOMEM; + p += len; + } + + /* princ_str size and length */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + + DEBUG(SSSDBG_TRACE_LIBS, "princ_str size: %d\n", len); + if (len) { + if (len > size - p) return EINVAL; + ibuf->princ_str = talloc_strndup(ibuf, (char *)(buf + p), len); + DEBUG(SSSDBG_TRACE_LIBS, "got princ_str: %s\n", ibuf->princ_str); + if (ibuf->princ_str == NULL) return ENOMEM; + p += len; + } + + /* keytab_name size and length */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + + DEBUG(SSSDBG_TRACE_LIBS, "keytab_name size: %d\n", len); + if (len) { + if (len > size - p) return EINVAL; + ibuf->keytab_name = talloc_strndup(ibuf, (char *)(buf + p), len); + DEBUG(SSSDBG_TRACE_LIBS, "got keytab_name: %s\n", ibuf->keytab_name); + if (ibuf->keytab_name == NULL) return ENOMEM; + p += len; + } + + /* ticket lifetime */ + SAFEALIGN_COPY_UINT32_CHECK(&ibuf->lifetime, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_LIBS, "lifetime: %u\n", ibuf->lifetime); + + /* UID and GID to run as */ + SAFEALIGN_COPY_UINT32_CHECK(&ibuf->uid, buf + p, size, &p); + SAFEALIGN_COPY_UINT32_CHECK(&ibuf->gid, buf + p, size, &p); + DEBUG(SSSDBG_FUNC_DATA, + "Will run as [%"SPRIuid"][%"SPRIgid"].\n", ibuf->uid, ibuf->gid); + + return EOK; +} + +static int pack_buffer(struct response *r, int result, krb5_error_code krberr, + const char *msg, time_t expire_time) +{ + int len; + size_t p = 0; + + len = strlen(msg); + r->size = 2 * sizeof(uint32_t) + sizeof(krb5_error_code) + + len + sizeof(time_t); + + DEBUG(SSSDBG_TRACE_INTERNAL, "response size: %zu\n",r->size); + + r->buf = talloc_array(r, uint8_t, r->size); + if(!r->buf) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "result [%d] krberr [%d] msgsize [%d] msg [%s]\n", + result, krberr, len, msg); + + /* result */ + SAFEALIGN_SET_UINT32(&r->buf[p], result, &p); + + /* krb5 error code */ + safealign_memcpy(&r->buf[p], &krberr, sizeof(krberr), &p); + + /* message size */ + SAFEALIGN_SET_UINT32(&r->buf[p], len, &p); + + /* message itself */ + safealign_memcpy(&r->buf[p], msg, len, &p); + + /* ticket expiration time */ + safealign_memcpy(&r->buf[p], &expire_time, sizeof(expire_time), &p); + + return EOK; +} + +static errno_t +set_child_debugging(krb5_context ctx) +{ + krb5_error_code kerr; + + /* Set the global error context */ + krb5_error_ctx = ctx; + + if (debug_level & SSSDBG_TRACE_ALL) { + kerr = sss_child_set_krb5_tracing(ctx); + if (kerr) { + LDAP_CHILD_DEBUG(SSSDBG_MINOR_FAILURE, kerr); + return EIO; + } + } + + return EOK; +} + +static int lc_verify_keytab_ex(const char *principal, + const char *keytab_name, + krb5_context context, + krb5_keytab keytab) +{ + bool found; + char *kt_principal; + krb5_error_code krberr; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + + krberr = krb5_kt_start_seq_get(context, keytab, &cursor); + if (krberr) { + const char *__err_msg = sss_krb5_get_error_message(context, krberr); + + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot read keytab [%s]: [%d][%s].\n", + sss_printable_keytab_name(context, keytab_name), + krberr, __err_msg); + + sss_log(SSS_LOG_ERR, "Error reading keytab file [%s]: [%d][%s]. " + "Unable to create GSSAPI-encrypted LDAP " + "connection.", + sss_printable_keytab_name(context, keytab_name), + krberr, __err_msg); + + sss_krb5_free_error_message(context, __err_msg); + return EIO; + } + + found = false; + while ((krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) { + krberr = krb5_unparse_name(context, entry.principal, &kt_principal); + if (krberr) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not parse keytab entry\n"); + sss_log(SSS_LOG_ERR, "Could not parse keytab entry\n"); + krb5_kt_end_seq_get(context, keytab, &cursor); + return EIO; + } + + if (strcmp(principal, kt_principal) == 0) { + found = true; + } + free(kt_principal); + krberr = sss_krb5_free_keytab_entry_contents(context, &entry); + if (krberr) { + /* This should never happen. The API docs for this function + * specify only success for this function + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Could not free keytab entry contents\n"); + /* This is non-fatal, so we'll continue here */ + } + + if (found) { + break; + } + } + + krberr = krb5_kt_end_seq_get(context, keytab, &cursor); + if (krberr) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not close keytab.\n"); + sss_log(SSS_LOG_ERR, "Could not close keytab file [%s].", + sss_printable_keytab_name(context, keytab_name)); + return EIO; + } + + if (!found) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Principal [%s] not found in keytab [%s]\n", + principal, + sss_printable_keytab_name(context, keytab_name)); + sss_log(SSS_LOG_ERR, "Error processing keytab file [%s]: " + "Principal [%s] was not found. " + "Unable to create GSSAPI-encrypted LDAP connection.", + sss_printable_keytab_name(context, keytab_name), + principal); + + return EFAULT; + } + + return EOK; +} + +static krb5_error_code ldap_child_get_tgt_sync(TALLOC_CTX *memctx, + krb5_context context, + const char *realm_str, + const char *princ_str, + const char *keytab_name, + const krb5_deltat lifetime, + const char **ccname_out, + time_t *expire_time_out, + char **_krb5_msg) +{ + char *ccname; + char *ccname_dummy; + char *realm_name = NULL; + char *full_princ = NULL; + char *default_realm = NULL; + char *tmp_str = NULL; + krb5_keytab keytab = NULL; + krb5_ccache ccache = NULL; + krb5_principal kprinc; + krb5_creds my_creds; + krb5_get_init_creds_opt *options = NULL; + krb5_error_code krberr; + krb5_timestamp kdc_time_offset; + int canonicalize = 0; + int kdc_time_offset_usec; + int ret; + errno_t error_code; + TALLOC_CTX *tmp_ctx; + char *ccname_file_dummy = NULL; + char *ccname_file; + + *_krb5_msg = NULL; + + tmp_ctx = talloc_new(memctx); + if (tmp_ctx == NULL) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + + error_code = set_child_debugging(context); + if (error_code != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot set krb5_child debugging\n"); + } + + if (!realm_str) { + krberr = krb5_get_default_realm(context, &default_realm); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_get_default_realm() failed: %d\n", krberr); + goto done; + } + + realm_name = talloc_strdup(tmp_ctx, default_realm); + krb5_free_default_realm(context, default_realm); + if (!realm_name) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + } else { + realm_name = talloc_strdup(tmp_ctx, realm_str); + if (!realm_name) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "got realm_name: [%s]\n", realm_name); + + if (princ_str) { + if (!strchr(princ_str, '@')) { + full_princ = talloc_asprintf(tmp_ctx, "%s@%s", + princ_str, realm_name); + } else { + full_princ = talloc_strdup(tmp_ctx, princ_str); + } + } else { + char hostname[HOST_NAME_MAX + 1]; + + ret = gethostname(hostname, sizeof(hostname)); + if (ret == -1) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_asprintf(memctx, "hostname() failed: [%d][%s]", + errno, strerror(errno)); + goto done; + } + hostname[HOST_NAME_MAX] = '\0'; + + DEBUG(SSSDBG_TRACE_LIBS, "got hostname: [%s]\n", hostname); + + ret = select_principal_from_keytab(tmp_ctx, hostname, realm_name, + keytab_name, &full_princ, NULL, NULL); + if (ret) { + krberr = KRB5_KT_IOERR; + *_krb5_msg = talloc_strdup(memctx, + "select_principal_from_keytab() failed"); + goto done; + } + } + if (!full_princ) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + DEBUG(SSSDBG_CONF_SETTINGS, "Principal name is: [%s]\n", full_princ); + + if (keytab_name) { + krberr = krb5_kt_resolve(context, keytab_name, &keytab); + } else { + krberr = krb5_kt_default(context, &keytab); + } + DEBUG(SSSDBG_CONF_SETTINGS, "Using keytab [%s]\n", + sss_printable_keytab_name(context, keytab_name)); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to read keytab file: %d\n", krberr); + goto done; + } + + /* Verify the keytab */ + ret = lc_verify_keytab_ex(full_princ, keytab_name, context, keytab); + if (ret) { + krberr = KRB5_KT_IOERR; + *_krb5_msg = talloc_strdup(memctx, "Unable to verify principal is present in the keytab"); + goto done; + } + + memset(&my_creds, 0, sizeof(my_creds)); + + krberr = krb5_get_init_creds_opt_alloc(context, &options); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_get_init_creds_opt_alloc failed.\n"); + goto done; + } + + krb5_get_init_creds_opt_set_address_list(options, NULL); + krb5_get_init_creds_opt_set_forwardable(options, 0); + krb5_get_init_creds_opt_set_proxiable(options, 0); + krb5_get_init_creds_opt_set_tkt_life(options, lifetime); + krberr = krb5_get_init_creds_opt_set_pa(context, options, + "X509_user_identity", ""); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_get_init_creds_opt_set_pa failed [%d], ignored.\n", + krberr); + } + + + tmp_str = getenv("KRB5_CANONICALIZE"); + if (tmp_str != NULL && strcasecmp(tmp_str, "true") == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "Will canonicalize principals\n"); + canonicalize = 1; + } + sss_krb5_get_init_creds_opt_set_canonicalize(options, canonicalize); + + ccname_file = talloc_asprintf(tmp_ctx, "%s/ccache_%s", + DB_PATH, realm_name); + if (ccname_file == NULL) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + + ccname_file_dummy = talloc_asprintf(tmp_ctx, "%s/ccache_%s_XXXXXX", + DB_PATH, realm_name); + if (ccname_file_dummy == NULL) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + global_ccname_file_dummy = ccname_file_dummy; + + ret = sss_unique_filename(tmp_ctx, ccname_file_dummy); + if (ret != EOK) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_asprintf(memctx, + "sss_unique_filename() failed: [%d][%s]", + ret, strerror(ret)); + goto done; + } + + krberr = krb5_parse_name(context, full_princ, &kprinc); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_parse_name() failed: %d\n", krberr); + goto done; + } + krberr = krb5_get_init_creds_keytab(context, &my_creds, kprinc, + keytab, 0, NULL, options); + krb5_free_principal(context, kprinc); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_get_init_creds_keytab() failed: %d\n", krberr); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "credentials initialized\n"); + krb5_kt_close(context, keytab); + keytab = NULL; + + ccname_dummy = talloc_asprintf(tmp_ctx, "FILE:%s", ccname_file_dummy); + ccname = talloc_asprintf(tmp_ctx, "FILE:%s", ccname_file); + if (ccname_dummy == NULL || ccname == NULL) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "keytab ccname: [%s]\n", ccname_dummy); + + krberr = krb5_cc_resolve(context, ccname_dummy, &ccache); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_cc_resolve() failed: %d\n", krberr); + goto done; + } + + /* Use updated principal if changed due to canonicalization. */ + krberr = krb5_cc_initialize(context, ccache, my_creds.client); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_cc_initialize() failed: %d\n", krberr); + goto done; + } + + krberr = krb5_cc_store_cred(context, ccache, &my_creds); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_cc_store_cred() failed: %d\n", krberr); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "credentials stored\n"); + +#ifdef HAVE_KRB5_GET_TIME_OFFSETS + krberr = krb5_get_time_offsets(context, &kdc_time_offset, + &kdc_time_offset_usec); + if (krberr != 0) { + const char *__err_msg = sss_krb5_get_error_message(context, krberr); + DEBUG(SSSDBG_OP_FAILURE, "Failed to get KDC time offset: %s\n", + __err_msg); + sss_krb5_free_error_message(context, __err_msg); + kdc_time_offset = 0; + } else { + if (kdc_time_offset_usec > 0) { + kdc_time_offset++; + } + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Got KDC time offset\n"); +#else + /* If we don't have this function, just assume no offset */ + kdc_time_offset = 0; +#endif + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Renaming [%s] to [%s]\n", ccname_file_dummy, ccname_file); + ret = rename(ccname_file_dummy, ccname_file); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "rename failed [%d][%s].\n", ret, strerror(ret)); + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_asprintf(memctx, + "rename() failed: [%d][%s]", + ret, strerror(ret)); + + goto done; + } + global_ccname_file_dummy = NULL; + + krberr = 0; + *ccname_out = talloc_steal(memctx, ccname); + *expire_time_out = my_creds.times.endtime - kdc_time_offset; + +done: + krb5_get_init_creds_opt_free(context, options); + if (krberr != 0) { + if (*_krb5_msg == NULL) { + /* no custom error message provided hence get one from libkrb5 */ + const char *__krberr_msg = sss_krb5_get_error_message(context, krberr); + *_krb5_msg = talloc_strdup(memctx, __krberr_msg); + sss_krb5_free_error_message(context, __krberr_msg); + } + + sss_log(SSS_LOG_ERR, + "Failed to initialize credentials using keytab [%s]: %s. " + "Unable to create GSSAPI-encrypted LDAP connection.", + sss_printable_keytab_name(context, keytab_name), *_krb5_msg); + + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to initialize credentials using keytab [%s]: %s. " + "Unable to create GSSAPI-encrypted LDAP connection.\n", + sss_printable_keytab_name(context, keytab_name), *_krb5_msg); + } + if (keytab) krb5_kt_close(context, keytab); + if (context) krb5_free_context(context); + talloc_free(tmp_ctx); + return krberr; +} + +static int prepare_response(TALLOC_CTX *mem_ctx, + const char *ccname, + time_t expire_time, + krb5_error_code kerr, + char *krb5_msg, + struct response **rsp) +{ + int ret; + struct response *r = NULL; + + r = talloc_zero(mem_ctx, struct response); + if (!r) return ENOMEM; + + r->buf = NULL; + r->size = 0; + + DEBUG(SSSDBG_TRACE_FUNC, "Building response for result [%d]\n", kerr); + + if (kerr == 0) { + ret = pack_buffer(r, EOK, kerr, ccname, expire_time); + } else { + if (krb5_msg == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Empty krb5 error message for non-zero kerr: %"PRIi32"\n", + kerr); + return ENOMEM; + } + ret = pack_buffer(r, EFAULT, kerr, krb5_msg, 0); + } + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pack_buffer failed\n"); + return ret; + } + + *rsp = r; + return EOK; +} + +static krb5_error_code privileged_krb5_setup(struct input_buffer *ibuf) +{ + krb5_error_code kerr; + char *keytab_name; + + kerr = sss_krb5_init_context(&ibuf->context); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to init kerberos context\n"); + return kerr; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Kerberos context initialized\n"); + + kerr = copy_keytab_into_memory(ibuf, ibuf->context, ibuf->keytab_name, + &keytab_name, NULL); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "copy_keytab_into_memory failed.\n"); + return kerr; + } + talloc_free(ibuf->keytab_name); + ibuf->keytab_name = keytab_name; + + return 0; +} + +int main(int argc, const char *argv[]) +{ + int ret; + int kerr; + int opt; + int dumpable = 1; + int debug_fd = -1; + const char *opt_logger = NULL; + poptContext pc; + TALLOC_CTX *main_ctx = NULL; + uint8_t *buf = NULL; + ssize_t len = 0; + const char *ccname = NULL; + char *krb5_msg = NULL; + time_t expire_time = 0; + struct input_buffer *ibuf = NULL; + struct response *resp = NULL; + ssize_t written; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, + _("Allow core dumps"), NULL }, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + SSSD_LOGGER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + } + + poptFreeContext(pc); + + prctl(PR_SET_DUMPABLE, (dumpable == 0) ? 0 : 1); + + debug_prg_name = talloc_asprintf(NULL, "ldap_child[%d]", getpid()); + if (!debug_prg_name) { + debug_prg_name = "ldap_child"; + ERROR("talloc_asprintf failed.\n"); + goto fail; + } + + if (debug_fd != -1) { + opt_logger = sss_logger_str[FILES_LOGGER]; + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + opt_logger = sss_logger_str[STDERR_LOGGER]; + ERROR("set_debug_file_from_fd failed.\n"); + } + } + + DEBUG_INIT(debug_level, opt_logger); + + BlockSignals(false, SIGTERM); + CatchSignal(SIGTERM, sig_term_handler); + + DEBUG(SSSDBG_TRACE_FUNC, "ldap_child started.\n"); + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + talloc_free(discard_const(debug_prg_name)); + goto fail; + } + talloc_steal(main_ctx, debug_prg_name); + + buf = talloc_size(main_ctx, sizeof(uint8_t)*IN_BUF_SIZE); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + goto fail; + } + + ibuf = talloc_zero(main_ctx, struct input_buffer); + if (ibuf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "context initialized\n"); + + errno = 0; + len = sss_atomic_read_s(STDIN_FILENO, buf, IN_BUF_SIZE); + if (len == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "read failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + close(STDIN_FILENO); + + ret = unpack_buffer(buf, len, ibuf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "unpack_buffer failed.[%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + kerr = privileged_krb5_setup(ibuf); + if (kerr != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Privileged Krb5 setup failed.\n"); + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Kerberos context initialized\n"); + + kerr = become_user(ibuf->uid, ibuf->gid); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "become_user failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Running as [%"SPRIuid"][%"SPRIgid"].\n", geteuid(), getegid()); + + DEBUG(SSSDBG_TRACE_INTERNAL, "getting TGT sync\n"); + kerr = ldap_child_get_tgt_sync(main_ctx, ibuf->context, + ibuf->realm_str, ibuf->princ_str, + ibuf->keytab_name, ibuf->lifetime, + &ccname, &expire_time, &krb5_msg); + if (kerr != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_child_get_tgt_sync failed.\n"); + /* Do not return, must report failure */ + } + + ret = prepare_response(main_ctx, ccname, expire_time, kerr, krb5_msg, + &resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "prepare_response failed. [%d][%s].\n", + ret, strerror(ret)); + goto fail; + } + + errno = 0; + written = sss_atomic_write_s(STDOUT_FILENO, resp->buf, resp->size); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "write failed [%d][%s].\n", ret, + strerror(ret)); + goto fail; + } + + if (written != resp->size) { + DEBUG(SSSDBG_CRIT_FAILURE, "Expected to write %zu bytes, wrote %zu\n", + resp->size, written); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "ldap_child completed successfully\n"); + close(STDOUT_FILENO); + talloc_free(main_ctx); + _exit(0); + +fail: + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_child failed!\n"); + close(STDOUT_FILENO); + talloc_free(main_ctx); + _exit(-1); +} diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c new file mode 100644 index 0000000..90ca22d --- /dev/null +++ b/src/providers/ldap/ldap_common.c @@ -0,0 +1,891 @@ +/* + SSSD + + LDAP Provider Common Functions + + Authors: + Simo Sorce + + Copyright (C) 2008-2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/ldap_common.h" +#include "providers/fail_over.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/krb5/krb5_common.h" +#include "db/sysdb_sudo.h" +#include "db/sysdb_services.h" +#include "db/sysdb_autofs.h" + +#include "util/sss_krb5.h" +#include "util/crypto/sss_crypto.h" + +#include "providers/ldap/sdap_idmap.h" + +errno_t ldap_id_setup_tasks(struct sdap_id_ctx *ctx) +{ + return sdap_id_setup_tasks(ctx->be, ctx, ctx->opts->sdom, + ldap_id_enumeration_send, + ldap_id_enumeration_recv, + ctx); +} + +errno_t sdap_id_setup_tasks(struct be_ctx *be_ctx, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt) +{ + int ret; + + /* set up enumeration task */ + if (sdom->dom->enumerate) { + DEBUG(SSSDBG_TRACE_FUNC, "Setting up enumeration for %s\n", + sdom->dom->name); + ret = ldap_id_setup_enumeration(be_ctx, ctx, sdom, + send_fn, recv_fn, pvt); + } else { + /* the enumeration task, runs the cleanup process by itself, + * but if enumeration is not running we need to schedule it */ + DEBUG(SSSDBG_TRACE_FUNC, "Setting up cleanup task for %s\n", + sdom->dom->name); + ret = ldap_id_setup_cleanup(ctx, sdom); + } + + return ret; +} + +static void sdap_uri_callback(void *private_data, struct fo_server *server) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct sdap_service *service; + struct resolv_hostent *srvaddr; + struct sockaddr *sockaddr; + const char *tmp; + const char *srv_name; + char *new_uri; + socklen_t sockaddr_len; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed\n"); + return; + } + + service = talloc_get_type(private_data, struct sdap_service); + if (!service) { + talloc_free(tmp_ctx); + return; + } + + tmp = (const char *)fo_get_server_user_data(server); + + srvaddr = fo_get_server_hostent(server); + if (!srvaddr) { + DEBUG(SSSDBG_CRIT_FAILURE, + "FATAL: No hostent available for server (%s)\n", + fo_get_server_str_name(server)); + talloc_free(tmp_ctx); + return; + } + + sockaddr = resolv_get_sockaddr_address(tmp_ctx, srvaddr, + fo_get_server_port(server), + &sockaddr_len); + if (sockaddr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "resolv_get_sockaddr_address failed.\n"); + talloc_free(tmp_ctx); + return; + } + + if (fo_is_srv_lookup(server)) { + if (!tmp) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown service, using ldap\n"); + tmp = SSS_LDAP_SRV_NAME; + } + + srv_name = fo_get_server_name(server); + if (srv_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not get server host name\n"); + talloc_free(tmp_ctx); + return; + } + + new_uri = talloc_asprintf(service, "%s://%s:%d", + tmp, srv_name, + fo_get_server_port(server)); + } else { + new_uri = talloc_strdup(service, tmp); + } + + if (!new_uri) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy URI ...\n"); + talloc_free(tmp_ctx); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Constructed uri '%s'\n", new_uri); + + /* free old one and replace with new one */ + talloc_zfree(service->uri); + service->uri = new_uri; + talloc_zfree(service->sockaddr); + service->sockaddr = talloc_steal(service, sockaddr); + service->sockaddr_len = sockaddr_len; + talloc_free(tmp_ctx); +} + +errno_t +sdap_set_sasl_options(struct sdap_options *id_opts, + char *default_primary, + char *default_realm, + const char *keytab_path) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + char *sasl_primary; + char *desired_primary; + char *primary_realm; + char *sasl_realm; + char *desired_realm; + bool primary_requested = true; + bool realm_requested = true; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + /* Configuration of SASL auth ID and realm */ + desired_primary = dp_opt_get_string(id_opts->basic, SDAP_SASL_AUTHID); + if (!desired_primary) { + primary_requested = false; + desired_primary = default_primary; + } + + if ((primary_realm = strchr(desired_primary, '@'))) { + *primary_realm = '\0'; + desired_realm = primary_realm+1; + DEBUG(SSSDBG_TRACE_INTERNAL, + "authid contains realm [%s]\n", desired_realm); + } else { + desired_realm = dp_opt_get_string(id_opts->basic, SDAP_SASL_REALM); + if (!desired_realm) { + realm_requested = false; + desired_realm = default_realm; + } + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Will look for %s@%s in %s\n", + desired_primary, desired_realm, + keytab_path ? keytab_path : "default keytab"); + + ret = select_principal_from_keytab(tmp_ctx, + desired_primary, desired_realm, + keytab_path, + NULL, &sasl_primary, &sasl_realm); + if (ret != EOK) { + goto done; + } + + if (primary_requested && strcmp(desired_primary, sasl_primary) != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Configured SASL auth ID not found in keytab. " + "Requested %s, found %s\n", desired_primary, sasl_primary); + } + + if (realm_requested && strcmp(desired_realm, sasl_realm) != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Configured SASL realm not found in keytab. " + "Requested %s, found %s\n", desired_realm, sasl_realm); + } + + ret = dp_opt_set_string(id_opts->basic, + SDAP_SASL_AUTHID, sasl_primary); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + id_opts->basic[SDAP_SASL_AUTHID].opt_name, + dp_opt_get_string(id_opts->basic, SDAP_SASL_AUTHID)); + + ret = dp_opt_set_string(id_opts->basic, + SDAP_SASL_REALM, sasl_realm); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + id_opts->basic[SDAP_SASL_REALM].opt_name, + dp_opt_get_string(id_opts->basic, SDAP_SASL_REALM)); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +sdap_gssapi_get_default_realm(TALLOC_CTX *mem_ctx) +{ + char *krb5_realm = NULL; + const char *realm = NULL; + krb5_error_code krberr; + krb5_context context = NULL; + + krberr = sss_krb5_init_context(&context); + if (krberr) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to init kerberos context\n"); + goto done; + } + + krberr = krb5_get_default_realm(context, &krb5_realm); + if (krberr) { + const char *__err_msg = sss_krb5_get_error_message(context, krberr); + DEBUG(SSSDBG_OP_FAILURE, "Failed to get default realm name: %s\n", + __err_msg); + sss_krb5_free_error_message(context, __err_msg); + goto done; + } + + realm = talloc_strdup(mem_ctx, krb5_realm); + krb5_free_default_realm(context, krb5_realm); + if (!realm) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Will use default realm %s\n", realm); +done: + if (context) krb5_free_context(context); + return realm; +} + +const char *sdap_gssapi_realm(struct dp_option *opts) +{ + const char *realm; + + realm = dp_opt_get_cstring(opts, SDAP_SASL_REALM); + if (!realm) { + realm = dp_opt_get_cstring(opts, SDAP_KRB5_REALM); + } + + return realm; +} + +int sdap_gssapi_init(TALLOC_CTX *mem_ctx, + struct dp_option *opts, + struct be_ctx *bectx, + struct sdap_service *sdap_service, + struct krb5_service **krb5_service) +{ + int ret; + const char *krb5_servers; + const char *krb5_backup_servers; + const char *krb5_realm; + const char *krb5_opt_realm; + struct krb5_service *service = NULL; + TALLOC_CTX *tmp_ctx; + size_t n_lookahead_primary; + size_t n_lookahead_backup; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + krb5_servers = dp_opt_get_string(opts, SDAP_KRB5_KDC); + krb5_backup_servers = dp_opt_get_string(opts, SDAP_KRB5_BACKUP_KDC); + + krb5_opt_realm = sdap_gssapi_realm(opts); + if (krb5_opt_realm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing krb5_realm option, will use libkrb default\n"); + krb5_realm = sdap_gssapi_get_default_realm(tmp_ctx); + if (krb5_realm == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot determine the Kerberos realm, aborting\n"); + ret = EIO; + goto done; + } + } else { + krb5_realm = talloc_strdup(tmp_ctx, krb5_opt_realm); + if (krb5_realm == NULL) { + ret = ENOMEM; + goto done; + } + } + + sss_krb5_parse_lookahead( + dp_opt_get_string(opts, SDAP_KRB5_KDCINFO_LOOKAHEAD), + &n_lookahead_primary, + &n_lookahead_backup); + + ret = krb5_service_init(mem_ctx, bectx, + SSS_KRB5KDC_FO_SRV, krb5_servers, + krb5_backup_servers, krb5_realm, + dp_opt_get_bool(opts, + SDAP_KRB5_USE_KDCINFO), + n_lookahead_primary, + n_lookahead_backup, + &service); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to init KRB5 failover service!\n"); + goto done; + } + + sdap_service->kinit_service_name = talloc_strdup(sdap_service, + service->name); + if (sdap_service->kinit_service_name == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + *krb5_service = service; +done: + talloc_free(tmp_ctx); + if (ret != EOK) talloc_free(service); + return ret; +} + +static errno_t _sdap_urls_init(struct be_ctx *ctx, + struct sdap_service *service, + const char *service_name, + const char *dns_service_name, + const char *urls, + bool primary) +{ + TALLOC_CTX *tmp_ctx; + char *srv_user_data; + char **list = NULL; + LDAPURLDesc *lud; + errno_t ret = 0; + int i; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + + /* split server parm into a list */ + ret = split_on_separator(tmp_ctx, urls, ',', true, true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse server list!\n"); + goto done; + } + + /* now for each URI add a new server to the failover service */ + for (i = 0; list[i]; i++) { + if (be_fo_is_srv_identifier(list[i])) { + if (!primary) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add server [%s] to failover service: " + "SRV resolution only allowed for primary servers!\n", + list[i]); + continue; + } + + if (!dns_service_name) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Missing DNS service name for service [%s].\n", + service_name); + ret = EINVAL; + goto done; + } + srv_user_data = talloc_strdup(service, dns_service_name); + if (!srv_user_data) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_srv_server(ctx, service_name, + dns_service_name, NULL, + BE_FO_PROTO_TCP, false, srv_user_data); + if (ret) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added service lookup\n"); + continue; + } + + ret = ldap_url_parse(list[i], &lud); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to parse ldap URI (%s)!\n", list[i]); + ret = EINVAL; + goto done; + } + + if (lud->lud_host == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "The LDAP URI (%s) did not contain a host name\n", + list[i]); + ldap_free_urldesc(lud); + continue; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added URI %s\n", list[i]); + + talloc_steal(service, list[i]); + + /* It could be ipv6 address in square brackets. Remove + * the brackets if needed. */ + ret = remove_ipv6_brackets(lud->lud_host); + if (ret != EOK) { + goto done; + } + + ret = be_fo_add_server(ctx, service->name, lud->lud_host, + lud->lud_port, list[i], primary); + ldap_free_urldesc(lud); + if (ret) { + goto done; + } + } + +done: + talloc_free(tmp_ctx); + return ret; +} + + +static inline errno_t +sdap_primary_urls_init(struct be_ctx *ctx, struct sdap_service *service, + const char *service_name, const char *dns_service_name, + const char *urls) +{ + return _sdap_urls_init(ctx, service, service_name, + dns_service_name, urls, true); +} + +static inline errno_t +sdap_backup_urls_init(struct be_ctx *ctx, struct sdap_service *service, + const char *service_name, const char *dns_service_name, + const char *urls) +{ + return _sdap_urls_init(ctx, service, service_name, + dns_service_name, urls, false); +} + +static int ldap_user_data_cmp(void *ud1, void *ud2) +{ + return strcasecmp((char*) ud1, (char*) ud2); +} + +void sdap_service_reset_fo(struct be_ctx *ctx, + struct sdap_service *service) +{ + if (service == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "NULL service\n"); + return; + } + + be_fo_reset_svc(ctx, service->name); +} + +int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *service_name, const char *dns_service_name, + const char *urls, const char *backup_urls, + struct sdap_service **_service) +{ + TALLOC_CTX *tmp_ctx; + struct sdap_service *service; + int ret; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + service = talloc_zero(tmp_ctx, struct sdap_service); + if (!service) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_service(ctx, service_name, ldap_user_data_cmp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create failover service!\n"); + goto done; + } + + service->name = talloc_strdup(service, service_name); + if (!service->name) { + ret = ENOMEM; + goto done; + } + + if (!urls) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No primary servers defined, using service discovery\n"); + urls = BE_SRV_IDENTIFIER; + } + + ret = sdap_primary_urls_init(ctx, service, service_name, dns_service_name, + urls); + if (ret != EOK) { + goto done; + } + + if (backup_urls) { + ret = sdap_backup_urls_init(ctx, service, service_name, + dns_service_name, backup_urls); + if (ret != EOK) { + goto done; + } + } + + ret = be_fo_service_add_callback(memctx, ctx, service->name, + sdap_uri_callback, service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add failover callback!\n"); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *_service = talloc_steal(memctx, service); + } + talloc_zfree(tmp_ctx); + return ret; +} + +errno_t string_to_shadowpw_days(const char *s, long *d) +{ + long l; + char *endptr; + int ret; + + if (s == NULL || *s == '\0') { + *d = -1; + return EOK; + } + + errno = 0; + l = strtol(s, &endptr, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "strtol failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + if (*endptr != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Input string [%s] is invalid.\n", s); + return EINVAL; + } + + if (l < -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Input string contains not allowed negative value [%ld].\n", + l); + return EINVAL; + } + + *d = l; + + return EOK; +} + +errno_t get_sysdb_attr_name(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + size_t map_size, + const char *ldap_name, + char **sysdb_name) +{ + size_t i; + + for (i = 0; i < map_size; i++) { + /* Skip map entries with no name (may depend on + * schema selected) + */ + if (!map[i].name) continue; + + /* Check if it is a mapped attribute */ + if(strcasecmp(ldap_name, map[i].name) == 0) break; + } + + if (i < map_size) { + /* We found a mapped name, return that */ + *sysdb_name = talloc_strdup(mem_ctx, map[i].sys_name); + } else { + /* Not mapped, use the same name */ + *sysdb_name = talloc_strdup(mem_ctx, ldap_name); + } + + if (!*sysdb_name) { + return ENOMEM; + } + + return EOK; +} + +errno_t list_missing_attrs(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + size_t map_size, + struct sysdb_attrs *recvd_attrs, + char ***missing_attrs) +{ + errno_t ret; + size_t attr_count = 0; + size_t i, j, k; + char **missing = NULL; + const char **expected_attrs; + char *sysdb_name; + TALLOC_CTX *tmp_ctx; + + if (!recvd_attrs || !missing_attrs) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = build_attrs_from_map(tmp_ctx, map, map_size, NULL, + &expected_attrs, &attr_count); + if (ret != EOK) { + goto done; + } + + /* Allocate the maximum possible values for missing_attrs, to + * be on the safe side + */ + missing = talloc_array(tmp_ctx, char *, attr_count + 2); + if (!missing) { + ret = ENOMEM; + goto done; + } + + k = 0; + /* Check for each expected attribute */ + for (i = 0; i < attr_count; i++) { + ret = get_sysdb_attr_name(tmp_ctx, map, map_size, + expected_attrs[i], + &sysdb_name); + if (ret != EOK) { + goto done; + } + + /* objectClass is a special-case and we need to + * check for it explicitly. + */ + if (strcasecmp(sysdb_name, "objectClass") == 0) { + talloc_free(sysdb_name); + continue; + } + + /* GECOS is another special case. Its value can come + * either from the 'gecos' attribute or the 'cn' + * attribute. It's best if we just never remove it. + */ + if (strcasecmp(sysdb_name, SYSDB_GECOS) == 0) { + talloc_free(sysdb_name); + continue; + } + + for (j = 0; j < recvd_attrs->num; j++) { + /* Check whether this expected attribute appeared in the + * received attributes and had a non-zero number of + * values. + */ + if ((strcasecmp(recvd_attrs->a[j].name, sysdb_name) == 0) && + (recvd_attrs->a[j].num_values > 0)) { + break; + } + } + + if (j < recvd_attrs->num) { + /* Attribute was found, therefore not missing */ + talloc_free(sysdb_name); + } else { + /* Attribute could not be found. Add to the missing list */ + missing[k] = talloc_steal(missing, sysdb_name); + k++; + + /* Remove originalMemberOf as well if MemberOf is missing */ + if (strcmp(sysdb_name, SYSDB_MEMBEROF) == 0) { + missing[k] = talloc_strdup(missing, SYSDB_ORIG_MEMBEROF); + k++; + } + } + } + + if (k == 0) { + *missing_attrs = NULL; + } else { + /* Terminate the list */ + missing[k] = NULL; + *missing_attrs = talloc_steal(mem_ctx, missing); + } + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +bool sdap_is_secure_uri(const char *uri) +{ + /* LDAPS URI's are secure channels */ + if (strncasecmp(uri, LDAP_SSL_URI, strlen(LDAP_SSL_URI)) == 0) { + return true; + } + return false; +} + +char *sdap_get_access_filter(TALLOC_CTX *mem_ctx, + const char *base_filter) +{ + char *filter = NULL; + + if (base_filter == NULL) return NULL; + + if (base_filter[0] == '(') { + /* This filter is wrapped in parentheses. + * Pass it as-is to the openldap libraries. + */ + filter = talloc_strdup(mem_ctx, base_filter); + } else { + filter = talloc_asprintf(mem_ctx, "(%s)", base_filter); + } + + return filter; +} + +errno_t +sdap_attrs_get_sid_str(TALLOC_CTX *mem_ctx, + struct sdap_idmap_ctx *idmap_ctx, + struct sysdb_attrs *sysdb_attrs, + const char *sid_attr, + char **_sid_str) +{ + errno_t ret; + enum idmap_error_code err; + struct ldb_message_element *el; + char *sid_str; + + ret = sysdb_attrs_get_el(sysdb_attrs, sid_attr, &el); + if (ret != EOK || el->num_values != 1) { + DEBUG(SSSDBG_TRACE_LIBS, + "No [%s] attribute. [%d][%s]\n", + sid_attr, el->num_values, strerror(ret)); + return ENOENT; + } + + if (el->values[0].length > 2 && + el->values[0].data[0] == 'S' && + el->values[0].data[1] == '-') { + sid_str = talloc_strndup(mem_ctx, (char *) el->values[0].data, + el->values[0].length); + if (sid_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + return ENOMEM; + } + } else { + err = sss_idmap_bin_sid_to_sid(idmap_ctx->map, + el->values[0].data, + el->values[0].length, + &sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert SID: [%s]\n", + idmap_error_string(err)); + return EIO; + } + } + + *_sid_str = talloc_steal(mem_ctx, sid_str); + + return EOK; +} + +struct sdap_id_conn_ctx * +sdap_id_ctx_conn_add(struct sdap_id_ctx *id_ctx, + struct sdap_service *sdap_service) +{ + struct sdap_id_conn_ctx *conn; + errno_t ret; + + conn = talloc_zero(id_ctx, struct sdap_id_conn_ctx); + if (conn == NULL) { + return NULL; + } + conn->service = talloc_steal(conn, sdap_service); + conn->id_ctx = id_ctx; + + /* Create a connection cache */ + ret = sdap_id_conn_cache_create(conn, conn, &conn->conn_cache); + if (ret != EOK) { + talloc_free(conn); + return NULL; + } + DLIST_ADD_END(id_ctx->conn, conn, struct sdap_id_conn_ctx *); + + return conn; +} + +static int sdap_id_ctx_destructor(struct sdap_id_ctx *id_ctx) +{ + be_ptask_destroy(&id_ctx->task); + return 0; +} + +struct sdap_id_ctx * +sdap_id_ctx_new(TALLOC_CTX *mem_ctx, struct be_ctx *bectx, + struct sdap_service *sdap_service) +{ + struct sdap_id_ctx *sdap_ctx; + + sdap_ctx = talloc_zero(mem_ctx, struct sdap_id_ctx); + if (sdap_ctx == NULL) { + return NULL; + } + talloc_set_destructor(sdap_ctx, sdap_id_ctx_destructor); + + sdap_ctx->be = bectx; + + /* There should be at least one connection context */ + sdap_ctx->conn = sdap_id_ctx_conn_add(sdap_ctx, sdap_service); + if (sdap_ctx->conn == NULL) { + talloc_free(sdap_ctx); + return NULL; + } + + return sdap_ctx; +} + +errno_t +sdap_resolver_ctx_new(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_resolver_ctx **out_ctx) +{ + struct sdap_resolver_ctx *sdap_ctx; + + sdap_ctx = talloc_zero(mem_ctx, struct sdap_resolver_ctx); + if (sdap_ctx == NULL) { + return ENOMEM; + } + sdap_ctx->id_ctx = id_ctx; + + *out_ctx = sdap_ctx; + + return EOK; +} diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h new file mode 100644 index 0000000..7159d63 --- /dev/null +++ b/src/providers/ldap/ldap_common.h @@ -0,0 +1,486 @@ +/* + SSSD + + LDAP Common utility code + + Copyright (C) Simo Sorce 2009 + + 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 3 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; 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, see . +*/ + +#ifndef _LDAP_COMMON_H_ +#define _LDAP_COMMON_H_ + +#include + +#include "providers/backend.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_id_op.h" +#include "providers/fail_over.h" +#include "providers/krb5/krb5_common.h" +#include "lib/idmap/sss_idmap.h" + +#define PWD_POL_OPT_NONE "none" +#define PWD_POL_OPT_SHADOW "shadow" +#define PWD_POL_OPT_MIT "mit_kerberos" + +#define SSS_LDAP_SRV_NAME "ldap" + +#define LDAP_STANDARD_URI "ldap://" +#define LDAP_SSL_URI "ldaps://" +#define LDAP_LDAPI_URI "ldapi://" + +/* Only the asterisk is allowed in wildcard requests */ +#define LDAP_ALLOWED_WILDCARDS "*" + +#define LDAP_ENUM_PURGE_TIMEOUT 10800 + +struct sdap_id_ctx; + +struct sdap_id_conn_ctx { + struct sdap_id_ctx *id_ctx; + + struct sdap_service *service; + /* LDAP connection cache */ + struct sdap_id_conn_cache *conn_cache; + /* dlinklist pointers */ + struct sdap_id_conn_ctx *prev, *next; + /* do not go offline, try another connection */ + bool ignore_mark_offline; + /* do not fall back to user lookups for mpg domains on this connection */ + bool no_mpg_user_fallback; +}; + +struct sdap_id_ctx { + struct be_ctx *be; + struct sdap_options *opts; + + /* If using GSSAPI or GSS-SPNEGO */ + struct krb5_service *krb5_service; + /* connection to a server */ + struct sdap_id_conn_ctx *conn; + + struct sdap_server_opts *srv_opts; + + /* Enumeration/cleanup periodic task. Only the enumeration or the cleanup + * task is started depending on the value of the domain's enumeration + * setting, this is why there is only one task pointer for both tasks. */ + struct be_ptask *task; + + /* enumeration loop timer */ + struct timeval last_enum; + /* cleanup loop timer */ + struct timeval last_purge; +}; + +struct sdap_auth_ctx { + struct be_ctx *be; + struct sdap_options *opts; + struct sdap_service *service; + struct sdap_service *chpass_service; +}; + +struct sdap_resolver_ctx { + struct sdap_id_ctx *id_ctx; + + /* Enumeration/cleanup periodic task */ + struct be_ptask *task; + + /* enumeration loop timer */ + struct timeval last_enum; + /* cleanup loop timer */ + struct timeval last_purge; +}; + +struct tevent_req * +sdap_online_check_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + void *data, + struct dp_req_params *params); + +errno_t sdap_online_check_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +struct tevent_req* sdap_reinit_cleanup_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx); + +errno_t sdap_reinit_cleanup_recv(struct tevent_req *req); + +/* id */ +struct tevent_req * +sdap_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params); + +errno_t sdap_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +/* Set up enumeration and/or cleanup */ +errno_t ldap_id_setup_tasks(struct sdap_id_ctx *ctx); +errno_t sdap_id_setup_tasks(struct be_ctx *be_ctx, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt); + +/* Allow shortcutting an enumeration request */ +bool sdap_is_enum_request(struct dp_id_data *ar); + +struct tevent_req * +sdap_handle_acct_req_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct dp_id_data *ar, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + bool noexist_delete); +errno_t +sdap_handle_acct_req_recv(struct tevent_req *req, + int *_dp_error, const char **_err, + int *sdap_ret); + +struct tevent_req * +sdap_pam_auth_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +sdap_pam_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +struct tevent_req * +sdap_pam_chpass_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +sdap_pam_chpass_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +/* autofs */ +struct tevent_req * +sdap_autofs_enumerate_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params); + +errno_t +sdap_autofs_enumerate_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output); + +struct tevent_req * +sdap_autofs_get_map_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params); + +errno_t +sdap_autofs_get_map_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output); + +struct tevent_req * +sdap_autofs_get_entry_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params); + +errno_t +sdap_autofs_get_entry_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output); + +int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *service_name, const char *dns_service_name, + const char *urls, const char *backup_urls, + struct sdap_service **_service); + +void sdap_service_reset_fo(struct be_ctx *ctx, + struct sdap_service *service); + +const char *sdap_gssapi_realm(struct dp_option *opts); + +int sdap_gssapi_init(TALLOC_CTX *mem_ctx, + struct dp_option *opts, + struct be_ctx *bectx, + struct sdap_service *sdap_service, + struct krb5_service **krb5_service); + +errno_t sdap_install_offline_callback(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + const char *realm, + const char *service_name); + +void sdap_remove_kdcinfo_files_callback(void *pvt); + +/* options parser */ +int ldap_get_options(TALLOC_CTX *memctx, + struct sss_domain_info *dom, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sdap_options **_opts); + +int ldap_get_sudo_options(struct confdb_ctx *cdb, + struct ldb_context *ldb, + const char *conf_path, + struct sdap_options *opts, + struct sdap_attr_map *native_map, + bool *use_host_filter, + bool *include_regexp, + bool *include_netgroups); + +int ldap_get_autofs_options(TALLOC_CTX *memctx, + struct ldb_context *ldb, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options *opts); + +/* Calling ldap_id_setup_enumeration will set up a periodic task + * that would periodically call send_fn/recv_fn request. The + * send_fn's pvt parameter will be a pointer to ldap_enum_ctx + * structure that contains the request data + */ +struct ldap_enum_ctx { + struct sdap_domain *sdom; + void *pvt; +}; + +errno_t ldap_id_setup_enumeration(struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt); +struct tevent_req * +ldap_id_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); +errno_t ldap_id_enumeration_recv(struct tevent_req *req); + +errno_t ldap_id_setup_cleanup(struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom); + +errno_t ldap_id_cleanup(struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom); + +struct tevent_req *groups_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *name, + int filter_type, + bool noexist_delete, + bool no_members, + bool set_non_posix); +int groups_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret); + +struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + const char *extra_value, + bool noexist_delete, + bool set_non_posix); + +int groups_by_user_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret); + +struct tevent_req *ldap_netgroup_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *name, + bool noexist_delete); +int ldap_netgroup_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret); + +struct tevent_req * +services_get_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *name, + const char *protocol, + int filter_type, + bool noexist_delete); + +errno_t +services_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret); + +struct tevent_req * +sdap_iphost_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params); + +errno_t +sdap_iphost_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +struct tevent_req * +sdap_ipnetwork_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params); + +errno_t +sdap_ipnetwork_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + + +errno_t string_to_shadowpw_days(const char *s, long *d); + +errno_t get_sysdb_attr_name(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + size_t map_size, + const char *ldap_name, + char **sysdb_name); + +errno_t list_missing_attrs(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + size_t map_size, + struct sysdb_attrs *recvd_attrs, + char ***missing_attrs); + +bool sdap_is_secure_uri(const char *uri); + +char *sdap_or_filters(TALLOC_CTX *mem_ctx, + const char *base_filter, + const char *extra_filter); + +char *sdap_combine_filters(TALLOC_CTX *mem_ctx, + const char *base_filter, + const char *extra_filter); + +char *get_enterprise_principal_string_filter(TALLOC_CTX *mem_ctx, + const char *attr_name, + const char *princ, + struct dp_option *sdap_basic_opts); + +char *sdap_get_access_filter(TALLOC_CTX *mem_ctx, + const char *base_filter); + +errno_t msgs2attrs_array(TALLOC_CTX *mem_ctx, size_t count, + struct ldb_message **msgs, + struct sysdb_attrs ***attrs); + +errno_t sdap_domain_add(struct sdap_options *opts, + struct sss_domain_info *dom, + struct sdap_domain **_sdom); +errno_t +sdap_domain_subdom_add(struct sdap_id_ctx *sdap_id_ctx, + struct sdap_domain *sdom_list, + struct sss_domain_info *parent); + +void +sdap_domain_remove(struct sdap_options *opts, + struct sss_domain_info *dom); + +struct sdap_domain *sdap_domain_get(struct sdap_options *opts, + struct sss_domain_info *dom); + +struct sdap_domain *sdap_domain_get_by_name(struct sdap_options *opts, + const char *dom_name); + +struct sdap_domain *sdap_domain_get_by_dn(struct sdap_options *opts, + const char *dn); + +errno_t sdap_parse_search_base(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + struct dp_option *opts, int class, + struct sdap_search_base ***_search_bases); +errno_t common_parse_search_base(TALLOC_CTX *mem_ctx, + const char *unparsed_base, + struct ldb_context *ldb, + const char *class_name, + const char *old_filter, + struct sdap_search_base ***_search_bases); + +errno_t +sdap_attrs_get_sid_str(TALLOC_CTX *mem_ctx, + struct sdap_idmap_ctx *idmap_ctx, + struct sysdb_attrs *sysdb_attrs, + const char *sid_attr, + char **_sid_str); + +errno_t +sdap_set_sasl_options(struct sdap_options *id_opts, + char *default_primary, + char *default_realm, + const char *keytab_path); + +struct sdap_id_conn_ctx * +sdap_id_ctx_conn_add(struct sdap_id_ctx *id_ctx, + struct sdap_service *sdap_service); + +struct sdap_id_ctx * +sdap_id_ctx_new(TALLOC_CTX *mem_ctx, struct be_ctx *bectx, + struct sdap_service *sdap_service); + +errno_t +sdap_resolver_ctx_new(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_resolver_ctx **out_ctx); + +errno_t sdap_refresh_init(struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx); + +errno_t sdap_init_certmap(TALLOC_CTX *mem_ctx, struct sdap_id_ctx *id_ctx); + +errno_t sdap_setup_certmap(struct sdap_certmap_ctx *sdap_certmap_ctx, + struct certmap_info **certmap_list); +struct sss_certmap_ctx *sdap_get_sss_certmap(struct sdap_certmap_ctx *ctx); + +errno_t users_get_handle_no_user(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + int filter_type, const char *filter_value, + bool name_is_upn); + +errno_t groups_get_handle_no_group(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + int filter_type, const char *filter_value); + +#ifdef BUILD_SUBID +struct tevent_req *subid_ranges_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char* filter_value, + const char *extra_value); + +int subid_ranges_get_recv(struct tevent_req *req, int *dp_error_out, + int *sdap_ret); +#endif + +#endif /* _LDAP_COMMON_H_ */ diff --git a/src/providers/ldap/ldap_id.c b/src/providers/ldap/ldap_id.c new file mode 100644 index 0000000..da54816 --- /dev/null +++ b/src/providers/ldap/ldap_id.c @@ -0,0 +1,1955 @@ +/* + SSSD + + LDAP Identity Backend Module + + Authors: + Simo Sorce + + Copyright (C) 2008 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "util/util.h" +#include "util/probes.h" +#include "util/strtonum.h" +#include "util/cert.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_users.h" + +errno_t users_get_handle_no_user(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + int filter_type, const char *filter_value, + bool name_is_upn) +{ + int ret; + const char *del_name; + struct ldb_message *msg = NULL; + uid_t uid; + char *endptr; + + switch (filter_type) { + case BE_FILTER_ENUM: + ret = EOK; + break; + case BE_FILTER_NAME: + if (name_is_upn == true) { + ret = sysdb_search_user_by_upn(mem_ctx, domain, false, + filter_value, + NULL, &msg); + if (ret == ENOENT) { + return EOK; + } else if (ret != EOK && ret != ENOENT) { + return ret; + } + del_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + } else { + del_name = filter_value; + } + + if (del_name == NULL) { + ret = ENOMEM; + break; + } + + ret = sysdb_delete_user(domain, del_name, 0); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_delete_user failed [%d].\n", ret); + } else { + ret = EOK; + } + break; + + case BE_FILTER_IDNUM: + uid = (uid_t) strtouint32(filter_value, &endptr, 10); + if (errno || *endptr || (filter_value == endptr)) { + ret = errno ? errno : EINVAL; + break; + } + + ret = sysdb_delete_user(domain, NULL, uid); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_delete_user failed [%d].\n", ret); + } else { + ret = EOK; + } + break; + + case BE_FILTER_SECID: + case BE_FILTER_UUID: + /* Since it is not clear if the SID/UUID belongs to a user or a + * group we have nothing to do here. */ + ret = EOK; + break; + + case BE_FILTER_WILDCARD: + /* We can't know if all users are up-to-date, especially in a large + * environment. Do not delete any records, let the responder fetch + * the entries they are requested in + */ + ret = EOK; + break; + + case BE_FILTER_CERT: + ret = sysdb_remove_cert(domain, filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to remove user certificate" + "[%d]: %s\n", ret, sss_strerror(ret)); + } + break; + + default: + ret = EINVAL; + } + + talloc_free(msg); + return ret; +} + +/* =Users-Related-Functions-(by-name,by-uid)============================== */ + +struct users_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + char *shortname; + + const char *filter_value; + int filter_type; + bool name_is_upn; + + char *filter; + const char **attrs; + bool use_id_mapping; + bool non_posix; + + int dp_error; + int sdap_ret; + bool noexist_delete; + struct sysdb_attrs *extra_attrs; +}; + +static int users_get_retry(struct tevent_req *req); +static void users_get_connect_done(struct tevent_req *subreq); +static void users_get_search(struct tevent_req *req); +static void users_get_done(struct tevent_req *subreq); + +struct tevent_req *users_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + const char *extra_value, + bool noexist_delete, + bool set_non_posix) +{ + struct tevent_req *req; + struct users_get_state *state; + const char *attr_name = NULL; + char *clean_value = NULL; + char *endptr; + int ret; + uid_t uid; + enum idmap_error_code err; + char *sid; + char *user_filter = NULL; + char *ep_filter; + + req = tevent_req_create(memctx, &state, struct users_get_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->noexist_delete = noexist_delete; + state->extra_attrs = NULL; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->filter_value = filter_value; + state->filter_type = filter_type; + + if (state->domain->type == DOM_TYPE_APPLICATION || set_non_posix) { + state->non_posix = true; + } + + state->use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + ctx->opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + switch (filter_type) { + case BE_FILTER_WILDCARD: + attr_name = ctx->opts->user_map[SDAP_AT_USER_NAME].name; + ret = sss_filter_sanitize_ex(state, filter_value, &clean_value, + LDAP_ALLOWED_WILDCARDS); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_NAME: + if (extra_value && strcmp(extra_value, EXTRA_NAME_IS_UPN) == 0) { + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + + ep_filter = get_enterprise_principal_string_filter(state, + ctx->opts->user_map[SDAP_AT_USER_PRINC].name, + clean_value, ctx->opts->basic); + /* TODO: Do we have to check the attribute names more carefully? */ + user_filter = talloc_asprintf(state, "(|(%s=%s)(%s=%s)%s)", + ctx->opts->user_map[SDAP_AT_USER_PRINC].name, + clean_value, + ctx->opts->user_map[SDAP_AT_USER_EMAIL].name, + clean_value, + ep_filter == NULL ? "" : ep_filter); + talloc_zfree(clean_value); + if (user_filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + } else { + attr_name = ctx->opts->user_map[SDAP_AT_USER_NAME].name; + + ret = sss_parse_internal_fqname(state, filter_value, + &state->shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot parse %s\n", filter_value); + goto done; + } + + ret = sss_filter_sanitize(state, state->shortname, &clean_value); + if (ret != EOK) { + goto done; + } + } + break; + case BE_FILTER_IDNUM: + if (state->use_id_mapping) { + /* If we're ID-mapping, we need to use the objectSID + * in the search filter. + */ + uid = strtouint32(filter_value, &endptr, 10); + if ((errno != EOK) || *endptr || (filter_value == endptr)) { + ret = EINVAL; + goto done; + } + + /* Convert the UID to its objectSID */ + err = sss_idmap_unix_to_sid(ctx->opts->idmap_ctx->map, + uid, &sid); + if (err == IDMAP_NO_DOMAIN) { + DEBUG(SSSDBG_MINOR_FAILURE, + "[%s] did not match any configured ID mapping domain\n", + filter_value); + + ret = sysdb_delete_user(state->domain, NULL, uid); + if (ret == ENOENT) { + /* Ignore errors to remove users that were not cached previously */ + ret = EOK; + } + + goto done; + } else if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Mapping ID [%s] to SID failed: [%s]\n", + filter_value, idmap_error_string(err)); + ret = EIO; + goto done; + } + + attr_name = ctx->opts->user_map[SDAP_AT_USER_OBJECTSID].name; + ret = sss_filter_sanitize(state, sid, &clean_value); + sss_idmap_free_sid(ctx->opts->idmap_ctx->map, sid); + if (ret != EOK) { + goto done; + } + + } else { + attr_name = ctx->opts->user_map[SDAP_AT_USER_UID].name; + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + } + break; + case BE_FILTER_SECID: + attr_name = ctx->opts->user_map[SDAP_AT_USER_OBJECTSID].name; + + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_UUID: + attr_name = ctx->opts->user_map[SDAP_AT_USER_UUID].name; + if (attr_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "UUID search not configured for this backend.\n"); + ret = EINVAL; + goto done; + } + + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_CERT: + attr_name = ctx->opts->user_map[SDAP_AT_USER_CERT].name; + if (attr_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate search not configured for this backend.\n"); + ret = EINVAL; + goto done; + } + + ret = sss_cert_derb64_to_ldap_filter(state, filter_value, attr_name, + sdap_get_sss_certmap(ctx->opts->sdap_certmap_ctx), + state->domain, &user_filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter failed.\n"); + + /* Typically sss_cert_derb64_to_ldap_filter() will fail if there + * is no mapping rule matching the current certificate. But this + * just means that no matching user can be found so we can finish + * the request with this result. Even if + * sss_cert_derb64_to_ldap_filter() would fail for other reason + * there is no need to return an error which might cause the + * domain go offline. */ + + if (noexist_delete) { + ret = sysdb_remove_cert(state->domain, filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Ignoring error while removing user certificate " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + } + + ret = EOK; + state->sdap_ret = ENOENT; + state->dp_error = DP_ERR_OK; + goto done; + } + + state->extra_attrs = sysdb_new_attrs(state); + if (state->extra_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_base64_blob(state->extra_attrs, + SYSDB_USER_MAPPED_CERT, filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_base64_blob failed.\n"); + goto done; + } + + break; + default: + ret = EINVAL; + goto done; + } + + if (attr_name == NULL && user_filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing search attribute name or filter.\n"); + ret = EINVAL; + goto done; + } + + if (user_filter == NULL) { + user_filter = talloc_asprintf(state, "(%s=%s)", attr_name, clean_value); + talloc_free(clean_value); + if (user_filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (state->non_posix) { + state->filter = talloc_asprintf(state, + "(&%s(objectclass=%s)(%s=*))", + user_filter, + ctx->opts->user_map[SDAP_OC_USER].name, + ctx->opts->user_map[SDAP_AT_USER_NAME].name); + } else if (state->use_id_mapping || filter_type == BE_FILTER_SECID) { + /* When mapping IDs or looking for SIDs, we don't want to limit + * ourselves to users with a UID value. But there must be a SID to map + * from. + */ + state->filter = talloc_asprintf(state, + "(&%s(objectclass=%s)(%s=*)(%s=*))", + user_filter, + ctx->opts->user_map[SDAP_OC_USER].name, + ctx->opts->user_map[SDAP_AT_USER_NAME].name, + ctx->opts->user_map[SDAP_AT_USER_OBJECTSID].name); + } else { + /* When not ID-mapping or looking up POSIX users, + * make sure there is a non-NULL UID */ + state->filter = talloc_asprintf(state, + "(&%s(objectclass=%s)(%s=*)(&(%s=*)(!(%s=0))))", + user_filter, + ctx->opts->user_map[SDAP_OC_USER].name, + ctx->opts->user_map[SDAP_AT_USER_NAME].name, + ctx->opts->user_map[SDAP_AT_USER_UID].name, + ctx->opts->user_map[SDAP_AT_USER_UID].name); + } + + talloc_zfree(user_filter); + if (!state->filter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build the base filter\n"); + ret = ENOMEM; + goto done; + } + + ret = build_attrs_from_map(state, ctx->opts->user_map, + ctx->opts->user_map_cnt, + NULL, &state->attrs, NULL); + if (ret != EOK) goto done; + + ret = users_get_retry(req); + if (ret != EOK) { + goto done; + } + + return req; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + return tevent_req_post(req, ev); +} + +static int users_get_retry(struct tevent_req *req) +{ + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, users_get_connect_done, req); + return EOK; +} + +static void users_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + users_get_search(req); +} + +static void users_get_search(struct tevent_req *req) +{ + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + struct tevent_req *subreq; + enum sdap_entry_lookup_type lookup_type; + + if (state->filter_type == BE_FILTER_WILDCARD) { + lookup_type = SDAP_LOOKUP_WILDCARD; + } else { + lookup_type = SDAP_LOOKUP_SINGLE; + } + + subreq = sdap_get_users_send(state, state->ev, + state->domain, state->sysdb, + state->ctx->opts, + state->sdom->user_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + lookup_type, state->extra_attrs); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, users_get_done, req); +} + +static void users_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + char *endptr; + uid_t uid = 0; + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_get_users_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = users_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + return; + } + + if ((ret == ENOENT) && + (state->ctx->opts->schema_type == SDAP_SCHEMA_RFC2307) && + (dp_opt_get_bool(state->ctx->opts->basic, + SDAP_RFC2307_FALLBACK_TO_LOCAL_USERS) == true)) { + struct sysdb_attrs **usr_attrs; + bool fallback; + + switch (state->filter_type) { + case BE_FILTER_NAME: + uid = -1; + fallback = true; + break; + case BE_FILTER_IDNUM: + uid = (uid_t) strtouint32(state->filter_value, &endptr, 10); + if (errno || *endptr || (state->filter_value == endptr)) { + tevent_req_error(req, errno ? errno : EINVAL); + return; + } + fallback = true; + break; + default: + fallback = false; + break; + } + + if (fallback) { + ret = sdap_fallback_local_user(state, state->shortname, uid, &usr_attrs); + if (ret == EOK) { + ret = sdap_save_user(state, state->ctx->opts, state->domain, + usr_attrs[0], NULL, NULL, 0, + state->non_posix); + } + } + } + state->sdap_ret = ret; + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT && state->noexist_delete == true) { + ret = users_get_handle_no_user(state, state->domain, state->filter_type, + state->filter_value, state->name_is_upn); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + } + + state->dp_error = DP_ERR_OK; + /* FIXME - return sdap error so that we know the user was not found */ + tevent_req_done(req); +} + +int users_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret) +{ + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* =Groups-Related-Functions-(by-name,by-uid)============================= */ + +struct groups_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *filter_value; + int filter_type; + + char *filter; + const char **attrs; + bool use_id_mapping; + bool non_posix; + + int dp_error; + int sdap_ret; + bool noexist_delete; + bool no_members; +}; + +static int groups_get_retry(struct tevent_req *req); +static void groups_get_connect_done(struct tevent_req *subreq); +static void groups_get_mpg_done(struct tevent_req *subreq); +static void groups_get_search(struct tevent_req *req); +static void groups_get_done(struct tevent_req *subreq); + +struct tevent_req *groups_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + bool noexist_delete, + bool no_members, + bool set_non_posix) +{ + struct tevent_req *req; + struct groups_get_state *state; + const char *attr_name = NULL; + char *shortname = NULL; + char *clean_value; + char *endptr; + int ret; + gid_t gid; + enum idmap_error_code err; + char *sid; + const char *member_filter[2]; + char *oc_list; + + req = tevent_req_create(memctx, &state, struct groups_get_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->noexist_delete = noexist_delete; + state->no_members = no_members; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->filter_value = filter_value; + state->filter_type = filter_type; + + if (state->domain->type == DOM_TYPE_APPLICATION || set_non_posix) { + state->non_posix = true; + } + + state->use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + ctx->opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + + switch(filter_type) { + case BE_FILTER_WILDCARD: + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name; + ret = sss_filter_sanitize_ex(state, filter_value, &clean_value, + LDAP_ALLOWED_WILDCARDS); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_NAME: + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name; + + ret = sss_parse_internal_fqname(state, filter_value, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot parse %s\n", filter_value); + goto done; + } + + ret = sss_filter_sanitize(state, shortname, &clean_value); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_IDNUM: + if (state->use_id_mapping) { + /* If we're ID-mapping, we need to use the objectSID + * in the search filter. + */ + gid = strtouint32(filter_value, &endptr, 10); + if ((errno != EOK) || *endptr || (filter_value == endptr)) { + ret = EINVAL; + goto done; + } + + /* Convert the GID to its objectSID */ + err = sss_idmap_unix_to_sid(ctx->opts->idmap_ctx->map, + gid, &sid); + if (err == IDMAP_NO_DOMAIN) { + DEBUG(SSSDBG_MINOR_FAILURE, + "[%s] did not match any configured ID mapping domain\n", + filter_value); + + ret = sysdb_delete_group(state->domain, NULL, gid); + if (ret == ENOENT) { + /* Ignore errors to remove users that were not cached previously */ + ret = EOK; + } + + goto done; + } else if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Mapping ID [%s] to SID failed: [%s]\n", + filter_value, idmap_error_string(err)); + ret = EIO; + goto done; + } + + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name; + ret = sss_filter_sanitize(state, sid, &clean_value); + sss_idmap_free_sid(ctx->opts->idmap_ctx->map, sid); + if (ret != EOK) { + goto done; + } + + } else { + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_GID].name; + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + } + break; + case BE_FILTER_SECID: + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name; + + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_UUID: + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_UUID].name; + if (attr_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "UUID search not configured for this backend.\n"); + ret = EINVAL; + goto done; + } + + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + break; + default: + ret = EINVAL; + goto done; + } + + if (attr_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing search attribute name.\n"); + ret = EINVAL; + goto done; + } + + oc_list = sdap_make_oc_list(state, ctx->opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto done; + } + + if (state->non_posix + || state->use_id_mapping + || filter_type == BE_FILTER_SECID) { + /* When mapping IDs or looking for SIDs, or when in a non-POSIX domain, + * we don't want to limit ourselves to groups with a GID value + */ + + state->filter = talloc_asprintf(state, + "(&(%s=%s)(%s)(%s=*))", + attr_name, clean_value, oc_list, + ctx->opts->group_map[SDAP_AT_GROUP_NAME].name); + } else { + state->filter = talloc_asprintf(state, + "(&(%s=%s)(%s)(%s=*)(&(%s=*)(!(%s=0))))", + attr_name, clean_value, oc_list, + ctx->opts->group_map[SDAP_AT_GROUP_NAME].name, + ctx->opts->group_map[SDAP_AT_GROUP_GID].name, + ctx->opts->group_map[SDAP_AT_GROUP_GID].name); + } + + talloc_zfree(clean_value); + if (!state->filter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + member_filter[0] = (const char *)ctx->opts->group_map[SDAP_AT_GROUP_MEMBER].name; + member_filter[1] = NULL; + + ret = build_attrs_from_map(state, ctx->opts->group_map, SDAP_OPTS_GROUP, + (state->domain->ignore_group_members + || state->no_members) ? + (const char **)member_filter : NULL, + &state->attrs, NULL); + + if (ret != EOK) goto done; + + ret = groups_get_retry(req); + if (ret != EOK) { + goto done; + } + + return req; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + return tevent_req_post(req, ev); +} + +static int groups_get_retry(struct tevent_req *req) +{ + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, groups_get_connect_done, req); + return EOK; +} + +static void groups_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + groups_get_search(req); +} + +static void groups_get_search(struct tevent_req *req) +{ + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + struct tevent_req *subreq; + enum sdap_entry_lookup_type lookup_type; + + if (state->filter_type == BE_FILTER_WILDCARD) { + lookup_type = SDAP_LOOKUP_WILDCARD; + } else { + lookup_type = SDAP_LOOKUP_SINGLE; + } + + subreq = sdap_get_groups_send(state, state->ev, + state->sdom, + state->ctx->opts, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + lookup_type, + state->no_members); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, groups_get_done, req); +} + +static void groups_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_get_groups_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = groups_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + return; + } + state->sdap_ret = ret; + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT + && sss_domain_is_mpg(state->domain) == true + && !state->conn->no_mpg_user_fallback) { + /* The requested filter did not find a group. Before giving up, we must + * also check if the GID can be resolved through a primary group of a + * user + */ + subreq = users_get_send(state, + state->ev, + state->ctx, + state->sdom, + state->conn, + state->filter_value, + state->filter_type, + NULL, + state->noexist_delete, + false); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, groups_get_mpg_done, req); + return; + } else if (ret == ENOENT && state->noexist_delete == true) { + ret = groups_get_handle_no_group(state, state->domain, + state->filter_type, + state->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not delete group [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +static void groups_get_mpg_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + + ret = users_get_recv(subreq, &state->dp_error, &state->sdap_ret); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->sdap_ret == ENOENT && state->noexist_delete == true) { + ret = groups_get_handle_no_group(state, state->domain, + state->filter_type, + state->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not delete group [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + } + + /* GID resolved to a user private group, done */ + tevent_req_done(req); + return; +} + +errno_t groups_get_handle_no_group(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + int filter_type, const char *filter_value) +{ + errno_t ret; + char *endptr; + gid_t gid; + + switch (filter_type) { + case BE_FILTER_ENUM: + ret = ENOENT; + break; + case BE_FILTER_NAME: + ret = sysdb_delete_group(domain, filter_value, 0); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete group %s [%d]: %s\n", + filter_value, ret, sss_strerror(ret)); + return ret; + } + ret = EOK; + break; + case BE_FILTER_IDNUM: + gid = (gid_t) strtouint32(filter_value, &endptr, 10); + if (errno || *endptr || (filter_value == endptr)) { + ret = errno ? errno : EINVAL; + break; + } + + ret = sysdb_delete_group(domain, NULL, gid); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete group %"SPRIgid" [%d]: %s\n", + gid, ret, sss_strerror(ret)); + return ret; + } + ret = EOK; + break; + case BE_FILTER_SECID: + case BE_FILTER_UUID: + /* Since it is not clear if the SID/UUID belongs to a user or a + * group we have nothing to do here. */ + ret = EOK; + break; + case BE_FILTER_WILDCARD: + /* We can't know if all groups are up-to-date, especially in + * a large environment. Do not delete any records, let the + * responder fetch the entries they are requested in. + */ + ret = EOK; + break; + default: + ret = EINVAL; + break; + } + + return ret; +} + +int groups_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret) +{ + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +/* =Get-Groups-for-User================================================== */ + +struct groups_by_user_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *filter_value; + int filter_type; + const char *extra_value; + const char **attrs; + bool non_posix; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static int groups_by_user_retry(struct tevent_req *req); +static void groups_by_user_connect_done(struct tevent_req *subreq); +static void groups_by_user_done(struct tevent_req *subreq); + +struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + const char *extra_value, + bool noexist_delete, + bool set_non_posix) +{ + struct tevent_req *req; + struct groups_by_user_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct groups_by_user_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->dp_error = DP_ERR_FATAL; + state->conn = conn; + state->sdom = sdom; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->filter_value = filter_value; + state->filter_type = filter_type; + state->extra_value = extra_value; + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + + if (state->domain->type == DOM_TYPE_APPLICATION || set_non_posix) { + state->non_posix = true; + } + + ret = build_attrs_from_map(state, ctx->opts->group_map, SDAP_OPTS_GROUP, + NULL, &state->attrs, NULL); + if (ret != EOK) goto fail; + + ret = groups_by_user_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static int groups_by_user_retry(struct tevent_req *req) +{ + struct groups_by_user_state *state = tevent_req_data(req, + struct groups_by_user_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, groups_by_user_connect_done, req); + return EOK; +} + +static void groups_by_user_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct groups_by_user_state *state = tevent_req_data(req, + struct groups_by_user_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_initgr_send(state, + state->ev, + state->sdom, + sdap_id_op_handle(state->op), + state->ctx, + state->conn, + state->filter_value, + state->filter_type, + state->extra_value, + state->attrs, + state->non_posix); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, groups_by_user_done, req); +} + +static void groups_by_user_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct groups_by_user_state *state = tevent_req_data(req, + struct groups_by_user_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_get_initgr_recv(subreq); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = groups_by_user_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + return; + } + state->sdap_ret = ret; + + switch (state->sdap_ret) { + case ENOENT: + if (state->noexist_delete == true) { + const char *cname; + + /* state->filter_value is still the name used for the original + * req. The cached object might have a different name, e.g. a + * fully-qualified name. */ + ret = sysdb_get_real_name(state, + state->domain, + state->filter_value, + &cname); + if (ret != EOK) { + cname = state->filter_value; + DEBUG(SSSDBG_TRACE_INTERNAL, + "Failed to canonicalize name, using [%s] [%d]: %s.\n", + cname, ret, sss_strerror(ret)); + } + + ret = sysdb_delete_user(state->domain, cname, 0); + if (ret != EOK && ret != ENOENT) { + tevent_req_error(req, ret); + return; + } + } + break; + case EOK: + break; + default: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +int groups_by_user_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret) +{ + struct groups_by_user_state *state = tevent_req_data(req, + struct groups_by_user_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* =Get-Account-Info-Call================================================= */ + +/* FIXME: embed this function in sssd_be and only call out + * specific functions from modules? */ + +static struct tevent_req *get_user_and_group_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + bool noexist_delete); + +errno_t sdap_get_user_and_group_recv(struct tevent_req *req, + int *dp_error_out, int *sdap_ret); + +bool sdap_is_enum_request(struct dp_id_data *ar) +{ + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: + case BE_REQ_GROUP: + case BE_REQ_SERVICES: + if (ar->filter_type == BE_FILTER_ENUM) { + return true; + } + } + + return false; +} + +/* A generic LDAP account info handler */ +struct sdap_handle_acct_req_state { + struct dp_id_data *ar; + const char *err; + int dp_error; + int sdap_ret; +}; + +static void sdap_handle_acct_req_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_handle_acct_req_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct dp_id_data *ar, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + bool noexist_delete) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_handle_acct_req_state *state; + errno_t ret; + + + req = tevent_req_create(mem_ctx, &state, + struct sdap_handle_acct_req_state); + if (!req) { + return NULL; + } + state->ar = ar; + + if (ar == NULL) { + ret = EINVAL; + goto done; + } + + PROBE(SDAP_ACCT_REQ_SEND, + state->ar->entry_type & BE_REQ_TYPE_MASK, + state->ar->filter_type, state->ar->filter_value, + PROBE_SAFE_STR(state->ar->extra_value)); + + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: /* user */ + subreq = users_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + ar->extra_value, + noexist_delete, + false); + break; + + case BE_REQ_GROUP: /* group */ + subreq = groups_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + noexist_delete, false, false); + break; + + case BE_REQ_INITGROUPS: /* init groups for user */ + if (ar->filter_type != BE_FILTER_NAME + && ar->filter_type != BE_FILTER_SECID + && ar->filter_type != BE_FILTER_UUID) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = groups_by_user_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + ar->extra_value, + noexist_delete, false); + break; + + case BE_REQ_SUBID_RANGES: +#ifdef BUILD_SUBID + if (!ar->extra_value) { + ret = ERR_GET_ACCT_SUBID_RANGES_NOT_SUPPORTED; + state->err = "This id_provider doesn't support subid ranges"; + goto done; + } + subreq = subid_ranges_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->extra_value); +#else + ret = ERR_GET_ACCT_SUBID_RANGES_NOT_SUPPORTED; + state->err = "Subid ranges are not supported"; + goto done; +#endif + break; + + case BE_REQ_NETGROUP: + if (ar->filter_type != BE_FILTER_NAME) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = ldap_netgroup_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + noexist_delete); + break; + + case BE_REQ_SERVICES: + if (ar->filter_type == BE_FILTER_SECID + || ar->filter_type == BE_FILTER_UUID) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = services_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->extra_value, + ar->filter_type, + noexist_delete); + break; + + case BE_REQ_BY_SECID: + if (ar->filter_type != BE_FILTER_SECID) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = get_user_and_group_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + noexist_delete); + break; + + case BE_REQ_BY_UUID: + if (ar->filter_type != BE_FILTER_UUID) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = get_user_and_group_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + noexist_delete); + break; + + case BE_REQ_USER_AND_GROUP: + if (!(ar->filter_type == BE_FILTER_NAME || + ar->filter_type == BE_FILTER_IDNUM)) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = get_user_and_group_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + noexist_delete); + break; + + case BE_REQ_BY_CERT: + subreq = users_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + ar->extra_value, + noexist_delete, + false); + break; + + default: /*fail*/ + ret = EINVAL; + state->err = "Invalid request type"; + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected request type: 0x%X [%s:%s] in %s\n", + ar->entry_type, ar->filter_value, + ar->extra_value?ar->extra_value:"-", + ar->domain); + goto done; + } + + if (!subreq) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_handle_acct_req_done, req); + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + tevent_req_post(req, be_ctx->ev); + return req; +} + +static void +sdap_handle_acct_req_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_handle_acct_req_state *state; + errno_t ret; + const char *err = "Invalid request type"; + + state = tevent_req_data(req, struct sdap_handle_acct_req_state); + + switch (state->ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: /* user */ + err = "User lookup failed"; + ret = users_get_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + case BE_REQ_GROUP: /* group */ + err = "Group lookup failed"; + ret = groups_get_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + case BE_REQ_INITGROUPS: /* init groups for user */ + err = "Init group lookup failed"; + ret = groups_by_user_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + case BE_REQ_SUBID_RANGES: + err = "Subid ranges lookup failed"; +#ifdef BUILD_SUBID + ret = subid_ranges_get_recv(subreq, &state->dp_error, &state->sdap_ret); +#else + ret = EINVAL; +#endif + break; + case BE_REQ_NETGROUP: + err = "Netgroup lookup failed"; + ret = ldap_netgroup_get_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + case BE_REQ_SERVICES: + err = "Service lookup failed"; + ret = services_get_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + case BE_REQ_BY_SECID: + /* Fall through */ + case BE_REQ_BY_UUID: + /* Fall through */ + case BE_REQ_USER_AND_GROUP: + err = "Lookup by SID failed"; + ret = sdap_get_user_and_group_recv(subreq, &state->dp_error, + &state->sdap_ret); + break; + case BE_REQ_BY_CERT: + err = "User lookup by certificate failed"; + ret = users_get_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + default: /* fail */ + ret = EINVAL; + break; + } + talloc_zfree(subreq); + + if (ret != EOK) { + state->err = err; + tevent_req_error(req, ret); + return; + } + + state->err = "Success"; + tevent_req_done(req); +} + +errno_t +sdap_handle_acct_req_recv(struct tevent_req *req, + int *_dp_error, const char **_err, + int *sdap_ret) +{ + struct sdap_handle_acct_req_state *state; + + state = tevent_req_data(req, struct sdap_handle_acct_req_state); + + PROBE(SDAP_ACCT_REQ_RECV, + state->ar->entry_type & BE_REQ_TYPE_MASK, + state->ar->filter_type, state->ar->filter_value, + PROBE_SAFE_STR(state->ar->extra_value)); + + if (_dp_error) { + *_dp_error = state->dp_error; + } + + if (_err) { + *_err = state->err; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct get_user_and_group_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *filter_val; + int filter_type; + + char *filter; + const char **attrs; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static void get_user_and_group_users_done(struct tevent_req *subreq); +static void get_user_and_group_groups_done(struct tevent_req *subreq); + +static struct tevent_req *get_user_and_group_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_val, + int filter_type, + bool noexist_delete) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct get_user_and_group_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct get_user_and_group_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->id_ctx = id_ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->filter_val = filter_val; + state->filter_type = filter_type; + + subreq = groups_get_send(req, state->ev, state->id_ctx, + state->sdom, state->conn, + state->filter_val, state->filter_type, + state->noexist_delete, false, false); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "groups_get_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, get_user_and_group_groups_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void get_user_and_group_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_user_and_group_state *state = tevent_req_data(req, + struct get_user_and_group_state); + int ret; + struct sdap_id_conn_ctx *user_conn; + + ret = groups_get_recv(subreq, &state->dp_error, &state->sdap_ret); + talloc_zfree(subreq); + + if (ret != EOK) { /* Fatal error while looking up group */ + tevent_req_error(req, ret); + return; + } + + if (state->sdap_ret == EOK) { /* Matching group found */ + tevent_req_done(req); + return; + } else if (state->sdap_ret != ENOENT) { + tevent_req_error(req, EIO); + return; + } + + /* Now the search finished fine but did not find an entry. + * Retry with users. */ + + /* Prefer LDAP over GC for users */ + user_conn = get_ldap_conn_from_sdom_pvt(state->id_ctx->opts, state->sdom); + if (user_conn == NULL) { + user_conn = state->conn; + } + + subreq = users_get_send(req, state->ev, state->id_ctx, + state->sdom, user_conn, + state->filter_val, state->filter_type, NULL, + state->noexist_delete, false); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "users_get_send failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, get_user_and_group_users_done, req); +} + +static void get_user_and_group_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_user_and_group_state *state = tevent_req_data(req, + struct get_user_and_group_state); + int ret; + + ret = users_get_recv(subreq, &state->dp_error, &state->sdap_ret); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + if (state->sdap_ret == ENOENT) { + if (state->noexist_delete == true) { + /* The search ran to completion, but nothing was found. + * Delete the existing entry, if any. */ + ret = sysdb_delete_by_sid(state->sysdb, state->domain, + state->filter_val); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not delete entry by SID!\n"); + tevent_req_error(req, ret); + return; + } + } + } else if (state->sdap_ret != EOK) { + tevent_req_error(req, EIO); + return; + } + + /* Both ret and sdap->ret are EOK. Matching user found */ + tevent_req_done(req); + return; +} + +errno_t sdap_get_user_and_group_recv(struct tevent_req *req, + int *dp_error_out, int *sdap_ret) +{ + struct get_user_and_group_state *state = tevent_req_data(req, + struct get_user_and_group_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_account_info_handler_state { + struct dp_reply_std reply; +}; + +static void sdap_account_info_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params) +{ + struct sdap_account_info_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_account_info_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sdap_is_enum_request(data)) { + DEBUG(SSSDBG_TRACE_LIBS, "Skipping enumeration on demand\n"); + ret = EOK; + goto immediately; + } + + subreq = sdap_handle_acct_req_send(state, params->be_ctx, data, id_ctx, + id_ctx->opts->sdom, id_ctx->conn, true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_account_info_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_account_info_handler_done(struct tevent_req *subreq) +{ + struct sdap_account_info_handler_state *state; + struct tevent_req *req; + const char *error_msg; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_account_info_handler_state); + + ret = sdap_handle_acct_req_recv(subreq, &dp_error, &error_msg, NULL); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, error_msg); + tevent_req_done(req); +} + +errno_t sdap_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_account_info_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_account_info_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/ldap/ldap_id_cleanup.c b/src/providers/ldap/ldap_id_cleanup.c new file mode 100644 index 0000000..ac06753 --- /dev/null +++ b/src/providers/ldap/ldap_id_cleanup.c @@ -0,0 +1,520 @@ +/* + SSSD + + LDAP Identity Cleanup Functions + + Authors: + Simo Sorce + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "util/util.h" +#include "util/find_uid.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" + +/* ==Cleanup-Task========================================================= */ +struct ldap_id_cleanup_ctx { + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; +}; + +static errno_t ldap_cleanup_task(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ldap_id_cleanup_ctx *cleanup_ctx = NULL; + + cleanup_ctx = talloc_get_type(pvt, struct ldap_id_cleanup_ctx); + return ldap_id_cleanup(cleanup_ctx->ctx, cleanup_ctx->sdom); +} + +errno_t ldap_id_setup_cleanup(struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom) +{ + errno_t ret; + time_t first_delay; + time_t period; + time_t offset; + struct ldap_id_cleanup_ctx *cleanup_ctx = NULL; + char *name = NULL; + + period = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if (period == 0) { + /* Cleanup has been explicitly disabled, so we won't + * create any cleanup tasks. */ + ret = EOK; + goto done; + } + offset = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_OFFSET); + + /* Run the first one in a couple of seconds so that we have time to + * finish initializations first. */ + first_delay = 10; + + cleanup_ctx = talloc_zero(sdom, struct ldap_id_cleanup_ctx); + if (cleanup_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cleanup_ctx->ctx = id_ctx; + cleanup_ctx->sdom = sdom; + + name = talloc_asprintf(cleanup_ctx, "Cleanup [id] of %s", sdom->dom->name); + if (name == NULL) { + return ENOMEM; + } + + ret = be_ptask_create_sync(id_ctx, id_ctx->be, period, first_delay, + 5 /* enabled delay */, offset /* random offset */, + period /* timeout */, 0, + ldap_cleanup_task, cleanup_ctx, name, + BE_PTASK_OFFLINE_SKIP, + &id_ctx->task); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize cleanup periodic " + "task for %s\n", sdom->dom->name); + goto done; + } + + ret = EOK; + +done: + talloc_free(name); + if (ret != EOK) { + talloc_free(cleanup_ctx); + } + + return ret; +} + +static int cleanup_users(struct sdap_options *opts, + struct sss_domain_info *dom); +static int cleanup_groups(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain); + +errno_t ldap_id_cleanup(struct sdap_id_ctx *ctx, + struct sdap_domain *sdom) +{ + int ret, tret; + bool in_transaction = false; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sdom->dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = cleanup_users(ctx->opts, sdom->dom); + if (ret && ret != ENOENT) { + goto done; + } + + ret = cleanup_groups(tmp_ctx, sdom->dom->sysdb, sdom->dom); + if (ret) { + goto done; + } + + ret = sysdb_transaction_commit(sdom->dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ctx->last_purge = tevent_timeval_current(); + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(sdom->dom->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + + +/* ==User-Cleanup-Process================================================= */ + +static int cleanup_users_logged_in(hash_table_t *table, + const struct ldb_message *msg); + +static errno_t expire_memberof_target_groups(struct sss_domain_info *dom, + struct ldb_message *user); + +static int cleanup_users(struct sdap_options *opts, + struct sss_domain_info *dom) +{ + TALLOC_CTX *tmpctx; + const char *attrs[] = { SYSDB_NAME, SYSDB_UIDNUM, SYSDB_MEMBEROF, NULL }; + time_t now = time(NULL); + char *subfilter = NULL; + char *ts_subfilter = NULL; + int account_cache_expiration; + hash_table_t *uid_table; + struct ldb_message **msgs; + size_t count; + const char *name; + int ret; + int i; + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + account_cache_expiration = dp_opt_get_int(opts->basic, SDAP_ACCOUNT_CACHE_EXPIRATION); + DEBUG(SSSDBG_TRACE_ALL, "Cache expiration is set to %d days\n", + account_cache_expiration); + + if (account_cache_expiration > 0) { + subfilter = talloc_asprintf(tmpctx, + "(&(!(%s=0))(|(!(%s=*))(%s<=%"SPRItime")))", + SYSDB_CACHE_EXPIRE, + SYSDB_LAST_LOGIN, + SYSDB_LAST_LOGIN, + (now - (account_cache_expiration * 86400))); + + ts_subfilter = talloc_asprintf(tmpctx, + "(&(!(%s=0))(%s<=%"SPRItime")(|(!(%s=*))(%s<=%"SPRItime")))", + SYSDB_CACHE_EXPIRE, + SYSDB_CACHE_EXPIRE, + now, + SYSDB_LAST_LOGIN, + SYSDB_LAST_LOGIN, + (now - (account_cache_expiration * 86400))); + } else { + subfilter = talloc_asprintf(tmpctx, + "(&(!(%s=0))(!(%s=*)))", + SYSDB_CACHE_EXPIRE, + SYSDB_LAST_LOGIN); + + ts_subfilter = talloc_asprintf(tmpctx, + "(&(!(%s=0))(%s<=%"SPRItime")(!(%s=*)))", + SYSDB_CACHE_EXPIRE, + SYSDB_CACHE_EXPIRE, + now, + SYSDB_LAST_LOGIN); + } + if (subfilter == NULL || ts_subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_users_by_timestamp(tmpctx, dom, subfilter, ts_subfilter, + attrs, &count, &msgs); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_users failed: %d\n", ret); + goto done; + } + DEBUG(SSSDBG_FUNC_DATA, "Found %zu expired user entries!\n", count); + + if (count == 0) { + ret = EOK; + goto done; + } + + ret = get_uid_table(tmpctx, &uid_table); + /* get_uid_table returns ENOSYS on non-Linux platforms. We proceed with + * the cleanup in that case + */ + if (ret != EOK && ret != ENOSYS) { + DEBUG(SSSDBG_CRIT_FAILURE, "get_uid_table failed: %d\n", ret); + goto done; + } + + for (i = 0; i < count; i++) { + name = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); + if (!name) { + DEBUG(SSSDBG_OP_FAILURE, "Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(msgs[i]->dn)); + ret = EFAULT; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Processing user %s\n", name); + + if (uid_table) { + ret = cleanup_users_logged_in(uid_table, msgs[i]); + if (ret == EOK) { + /* If the user is logged in, proceed to the next one */ + DEBUG(SSSDBG_FUNC_DATA, + "User %s is still logged in or a dummy entry, " + "keeping data\n", name); + continue; + } else if (ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot check if user is logged in: %d\n", ret); + goto done; + } + } + + /* If not logged in or cannot check the table, delete him */ + DEBUG(SSSDBG_TRACE_ALL, "About to delete user %s\n", name); + ret = sysdb_delete_user(dom, name, 0); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_delete_user failed: %d\n", ret); + goto done; + } + + /* Mark all groups of which user was a member as expired in cache, + * so that its ghost/member attributes are refreshed on next + * request. */ + ret = expire_memberof_target_groups(dom, msgs[i]); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "expire_memberof_target_groups failed: [%d]:%s\n", + ret, sss_strerror(ret)); + goto done; + } + } + +done: + talloc_zfree(tmpctx); + return ret; +} + +static errno_t expire_memberof_target_groups(struct sss_domain_info *dom, + struct ldb_message *user) +{ + struct ldb_message_element *memberof_el = NULL; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + memberof_el = ldb_msg_find_element(user, SYSDB_MEMBEROF); + if (memberof_el == NULL) { + /* User has no cached groups. Nothing to be marked as expired. */ + ret = EOK; + goto done; + } + + for (unsigned int i = 0; i < memberof_el->num_values; i++) { + ret = sysdb_mark_entry_as_expired_ldb_val(dom, + &memberof_el->values[i]); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_mark_entry_as_expired_ldb_val failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int cleanup_users_logged_in(hash_table_t *table, + const struct ldb_message *msg) +{ + uid_t uid; + hash_key_t key; + hash_value_t value; + int ret; + + uid = ldb_msg_find_attr_as_uint64(msg, + SYSDB_UIDNUM, 0); + if (!uid) { + DEBUG(SSSDBG_OP_FAILURE, "Entry %s has no UID Attribute!\n", + ldb_dn_get_linearized(msg->dn)); + return ENOENT; + } + + key.type = HASH_KEY_ULONG; + key.ul = (unsigned long) uid; + + ret = hash_lookup(table, &key, &value); + if (ret == HASH_SUCCESS) { + return EOK; + } else if (ret == HASH_ERROR_KEY_NOT_FOUND) { + return ENOENT; + } + + DEBUG(SSSDBG_OP_FAILURE, "hash_lookup failed: %d\n", ret); + return EIO; +} + +/* ==Group-Cleanup-Process================================================ */ + +static int cleanup_groups(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmpctx; + const char *attrs[] = { SYSDB_NAME, SYSDB_GIDNUM, NULL }; + time_t now = time(NULL); + char *subfilter; + char *ts_subfilter; + const char *dn; + gid_t gid; + struct ldb_message **msgs; + size_t count; + struct ldb_message **u_msgs; + size_t u_count; + int ret; + int i; + const char *posix; + struct ldb_dn *base_dn; + + tmpctx = talloc_new(memctx); + if (!tmpctx) { + return ENOMEM; + } + + subfilter = talloc_asprintf(tmpctx, "(!(%s=0))", SYSDB_CACHE_EXPIRE); + if (subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ts_subfilter = talloc_asprintf(tmpctx, "(&(!(%s=0))(%s<=%"SPRItime"))", + SYSDB_CACHE_EXPIRE, SYSDB_CACHE_EXPIRE, now); + if (ts_subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_groups_by_timestamp(tmpctx, domain, subfilter, + ts_subfilter, attrs, &count, &msgs); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_groups failed: %d\n", ret); + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Found %zu expired group entries!\n", count); + + if (count == 0) { + ret = EOK; + goto done; + } + + for (i = 0; i < count; i++) { + char *sanitized_dn; + + dn = ldb_dn_get_linearized(msgs[i]->dn); + if (!dn) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot linearize DN!\n"); + ret = EFAULT; + goto done; + } + + /* sanitize dn */ + ret = sss_filter_sanitize_dn(tmpctx, dn, &sanitized_dn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sss_filter_sanitize failed: %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } + + posix = ldb_msg_find_attr_as_string(msgs[i], SYSDB_POSIX, NULL); + if (!posix || strcmp(posix, "TRUE") == 0) { + /* Search for users that are members of this group, or + * that have this group as their primary GID. + * Include subdomain users as well. + */ + gid = (gid_t) ldb_msg_find_attr_as_uint(msgs[i], SYSDB_GIDNUM, 0); + subfilter = talloc_asprintf(tmpctx, "(&(%s=%s)(|(%s=%s)(%s=%lu)))", + SYSDB_OBJECTCATEGORY, SYSDB_USER_CLASS, + SYSDB_MEMBEROF, sanitized_dn, + SYSDB_GIDNUM, (long unsigned) gid); + } else { + subfilter = talloc_asprintf(tmpctx, "(%s=%s)", SYSDB_MEMBEROF, + sanitized_dn); + } + talloc_zfree(sanitized_dn); + + if (!subfilter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + base_dn = sysdb_base_dn(sysdb, tmpctx); + if (base_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build base dn\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Searching with: %s\n", subfilter); + + ret = sysdb_search_entry(tmpctx, sysdb, base_dn, + LDB_SCOPE_SUBTREE, subfilter, NULL, + &u_count, &u_msgs); + if (ret == ENOENT) { + const char *name; + + name = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); + if (!name) { + DEBUG(SSSDBG_OP_FAILURE, "Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(msgs[i]->dn)); + ret = EFAULT; + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "About to delete group %s\n", name); + ret = sysdb_delete_group(domain, name, 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Group delete returned %d (%s)\n", + ret, strerror(ret)); + goto done; + } + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to search sysdb using %s: [%d] %s\n", + subfilter, ret, sss_strerror(ret)); + goto done; + } + talloc_zfree(u_msgs); + } + +done: + talloc_zfree(tmpctx); + return ret; +} diff --git a/src/providers/ldap/ldap_id_enum.c b/src/providers/ldap/ldap_id_enum.c new file mode 100644 index 0000000..684dc70 --- /dev/null +++ b/src/providers/ldap/ldap_id_enum.c @@ -0,0 +1,212 @@ +/* + SSSD + + LDAP Identity Enumeration + + Authors: + Simo Sorce + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_enum.h" + +errno_t ldap_id_setup_enumeration(struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt) +{ + errno_t ret; + time_t first_delay; + time_t period; + time_t offset; + time_t cleanup; + bool has_enumerated; + struct ldap_enum_ctx *ectx = NULL; + char *name = NULL; + + ret = sysdb_has_enumerated(sdom->dom, SYSDB_HAS_ENUMERATED_ID, + &has_enumerated); + if (ret == ENOENT) { + /* default value */ + has_enumerated = false; + } else if (ret != EOK) { + return ret; + } + + if (has_enumerated) { + /* At least one enumeration has previously run, + * so clients will get cached data. We will delay + * starting to enumerate by 10s so we don't slow + * down the startup process if this is happening + * during system boot. + */ + first_delay = 10; + } else { + /* This is our first startup. Schedule the + * enumeration to start immediately once we + * enter the mainloop. + */ + first_delay = 0; + } + + cleanup = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if (cleanup == 0) { + /* We need to cleanup the cache once in a while when enumerating, otherwise + * enumeration would only download deltas since the previous lastUSN and would + * not detect removed entries + */ + ret = dp_opt_set_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT, + LDAP_ENUM_PURGE_TIMEOUT); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot set cleanup timeout, enumeration wouldn't " + "detect removed entries!\n"); + return ret; + } + } + + period = dp_opt_get_int(id_ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + offset = dp_opt_get_int(id_ctx->opts->basic, SDAP_ENUM_REFRESH_OFFSET); + + ectx = talloc(sdom, struct ldap_enum_ctx); + if (ectx == NULL) { + return ENOMEM; + } + ectx->sdom = sdom; + ectx->pvt = pvt; + + name = talloc_asprintf(NULL, "Enumeration [id] of %s", + sdom->dom->name); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + ret = be_ptask_create(id_ctx, be_ctx, + period, /* period */ + first_delay, /* first_delay */ + 5, /* enabled delay */ + offset, /* random offset */ + period, /* timeout */ + 0, /* max_backoff */ + send_fn, recv_fn, + ectx, name, + BE_PTASK_OFFLINE_SKIP | BE_PTASK_SCHEDULE_FROM_LAST, + &id_ctx->task); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to initialize enumeration periodic task\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(name); + if (ret != EOK) { + talloc_free(ectx); + } + return ret; +} + +struct ldap_enumeration_state { + struct ldap_enum_ctx *ectx; + struct sdap_id_ctx *id_ctx; + struct sss_domain_info *dom; +}; + +static void ldap_enumeration_done(struct tevent_req *subreq); + +struct tevent_req * +ldap_id_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ldap_enumeration_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct ldap_enum_ctx *ectx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ldap_enumeration_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ectx = talloc_get_type(pvt, struct ldap_enum_ctx); + if (ectx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve ldap_enum_ctx!\n"); + ret = EFAULT; + goto fail; + } + state->ectx = ectx; + state->dom = ectx->sdom->dom; + state->id_ctx = talloc_get_type_abort(ectx->pvt, struct sdap_id_ctx); + + subreq = sdap_dom_enum_send(state, ev, state->id_ctx, ectx->sdom, + state->id_ctx->conn); + if (subreq == NULL) { + /* The ptask API will reschedule the enumeration on its own on + * failure */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to schedule enumeration, retrying later!\n"); + ret = EIO; + goto fail; + } + + tevent_req_set_callback(subreq, ldap_enumeration_done, req); + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +ldap_enumeration_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = sdap_dom_enum_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +ldap_id_enumeration_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/ldap_id_netgroup.c b/src/providers/ldap/ldap_id_netgroup.c new file mode 100644 index 0000000..1fb01cf --- /dev/null +++ b/src/providers/ldap/ldap_id_netgroup.c @@ -0,0 +1,247 @@ +/* + SSSD + + LDAP Identity Backend Module - Netgroup support + + Authors: + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" + + +struct ldap_netgroup_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + struct sdap_id_conn_ctx *conn; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *name; + int timeout; + + char *filter; + const char **attrs; + + size_t count; + struct sysdb_attrs **netgroups; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static int ldap_netgroup_get_retry(struct tevent_req *req); +static void ldap_netgroup_get_connect_done(struct tevent_req *subreq); +static void ldap_netgroup_get_done(struct tevent_req *subreq); + +struct tevent_req *ldap_netgroup_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *name, + bool noexist_delete) +{ + struct tevent_req *req; + struct ldap_netgroup_get_state *state; + char *clean_name; + int ret; + + req = tevent_req_create(memctx, &state, struct ldap_netgroup_get_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->name = name; + state->timeout = dp_opt_get_int(ctx->opts->basic, SDAP_SEARCH_TIMEOUT); + + ret = sss_filter_sanitize(state, name, &clean_name); + if (ret != EOK) { + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + ctx->opts->netgroup_map[SDAP_AT_NETGROUP_NAME].name, + clean_name, + ctx->opts->netgroup_map[SDAP_OC_NETGROUP].name); + if (!state->filter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto fail; + } + talloc_zfree(clean_name); + + ret = build_attrs_from_map(state, ctx->opts->netgroup_map, SDAP_OPTS_NETGROUP, + NULL, &state->attrs, NULL); + if (ret != EOK) goto fail; + + ret = ldap_netgroup_get_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static int ldap_netgroup_get_retry(struct tevent_req *req) +{ + struct ldap_netgroup_get_state *state = tevent_req_data(req, + struct ldap_netgroup_get_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, ldap_netgroup_get_connect_done, req); + return EOK; +} + +static void ldap_netgroup_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ldap_netgroup_get_state *state = tevent_req_data(req, + struct ldap_netgroup_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_netgroups_send(state, state->ev, + state->domain, state->sysdb, + state->ctx->opts, + state->sdom->netgroup_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + state->timeout); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ldap_netgroup_get_done, req); + + return; +} + +static void ldap_netgroup_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ldap_netgroup_get_state *state = tevent_req_data(req, + struct ldap_netgroup_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_get_netgroups_recv(subreq, state, NULL, &state->count, + &state->netgroups); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ldap_netgroup_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + return; + } + state->sdap_ret = ret; + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == EOK && state->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Found more than one netgroup with the name [%s].\n", + state->name); + tevent_req_error(req, EINVAL); + return; + } + + if (ret == ENOENT && state->noexist_delete == true) { + ret = sysdb_delete_netgroup(state->domain, state->name); + if (ret != EOK && ret != ENOENT) { + tevent_req_error(req, ret); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; +} + +int ldap_netgroup_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret) +{ + struct ldap_netgroup_get_state *state = tevent_req_data(req, + struct ldap_netgroup_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/ldap_id_services.c b/src/providers/ldap/ldap_id_services.c new file mode 100644 index 0000000..52a1563 --- /dev/null +++ b/src/providers/ldap/ldap_id_services.c @@ -0,0 +1,308 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + + +#include + +#include "util/util.h" +#include "util/strtonum.h" +#include "db/sysdb.h" +#include "db/sysdb_services.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" + +struct sdap_services_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct sdap_id_conn_ctx *conn; + + const char *name; + const char *protocol; + + char *filter; + const char **attrs; + + int filter_type; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static errno_t +services_get_retry(struct tevent_req *req); +static void +services_get_connect_done(struct tevent_req *subreq); +static void +services_get_done(struct tevent_req *subreq); + +struct tevent_req * +services_get_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *name, + const char *protocol, + int filter_type, + bool noexist_delete) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_services_get_state *state; + const char *attr_name; + char *clean_name; + char *clean_protocol = NULL; + + req = tevent_req_create(mem_ctx, &state, struct sdap_services_get_state); + if (!req) return NULL; + + state->ev = ev; + state->id_ctx = id_ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->name = name; + state->protocol = protocol; + state->filter_type = filter_type; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_MINOR_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto error; + } + + switch(filter_type) { + case BE_FILTER_NAME: + attr_name = id_ctx->opts->service_map[SDAP_AT_SERVICE_NAME].name; + break; + case BE_FILTER_IDNUM: + attr_name = id_ctx->opts->service_map[SDAP_AT_SERVICE_PORT].name; + break; + default: + ret = EINVAL; + goto error; + } + + ret = sss_filter_sanitize(state, name, &clean_name); + if (ret != EOK) goto error; + + if (protocol != NULL) { + ret = sss_filter_sanitize(state, protocol, &clean_protocol); + if (ret != EOK) goto error; + } + + if (clean_protocol) { + state->filter = talloc_asprintf( + state, "(&(%s=%s)(%s=%s)(objectclass=%s))", + attr_name, clean_name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_PROTOCOL].name, + clean_protocol, + id_ctx->opts->service_map[SDAP_OC_SERVICE].name); + } else { + state->filter = + talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + attr_name, clean_name, + id_ctx->opts->service_map[SDAP_OC_SERVICE].name); + } + talloc_zfree(clean_name); + talloc_zfree(clean_protocol); + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build the base filter\n"); + ret = ENOMEM; + goto error; + } + DEBUG(SSSDBG_TRACE_LIBS, + "Preparing to search for services with filter [%s]\n", + state->filter); + + ret = build_attrs_from_map(state, id_ctx->opts->service_map, + SDAP_OPTS_SERVICES, NULL, + &state->attrs, NULL); + if (ret != EOK) goto error; + + ret = services_get_retry(req); + if (ret != EOK) goto error; + + return req; + +error: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +services_get_retry(struct tevent_req *req) +{ + errno_t ret; + struct sdap_services_get_state *state = + tevent_req_data(req, struct sdap_services_get_state); + struct tevent_req *subreq; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, services_get_connect_done, req); + return EOK; +} + +static void +services_get_connect_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_services_get_state *state = + tevent_req_data(req, struct sdap_services_get_state); + int dp_error = DP_ERR_FATAL; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_services_send(state, state->ev, + state->domain, state->sysdb, + state->id_ctx->opts, + state->sdom->service_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, services_get_done, req); +} + +static void +services_get_done(struct tevent_req *subreq) +{ + errno_t ret; + uint16_t port; + char *endptr; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_services_get_state *state = + tevent_req_data(req, struct sdap_services_get_state); + int dp_error = DP_ERR_FATAL; + + ret = sdap_get_services_recv(NULL, subreq, NULL); + talloc_zfree(subreq); + + /* Check whether we need to try again with another + * failover server. + */ + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = services_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Return to the mainloop to retry */ + return; + } + state->sdap_ret = ret; + + /* An error occurred. */ + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT && state->noexist_delete == true) { + /* Ensure that this entry is removed from the sysdb */ + switch(state->filter_type) { + case BE_FILTER_NAME: + ret = sysdb_svc_delete(state->domain, state->name, + 0, state->protocol); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + case BE_FILTER_IDNUM: + port = strtouint16(state->name, &endptr, 10); + if (errno || *endptr || (state->name == endptr)) { + tevent_req_error(req, (errno ? errno : EINVAL)); + return; + } + + ret = sysdb_svc_delete(state->domain, NULL, port, + state->protocol); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + default: + tevent_req_error(req, EINVAL); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +errno_t +services_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret) +{ + struct sdap_services_get_state *state = + tevent_req_data(req, struct sdap_services_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/ldap_id_subid.c b/src/providers/ldap/ldap_id_subid.c new file mode 100644 index 0000000..d25c6aa --- /dev/null +++ b/src/providers/ldap/ldap_id_subid.c @@ -0,0 +1,255 @@ +/* + SSSD + + LDAP Identity Backend Module - subid ranges support + + Copyright (C) 2021 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "db/sysdb_subid.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_ops.h" + +static int subid_ranges_get_retry(struct tevent_req *req); +static void subid_ranges_get_connect_done(struct tevent_req *subreq); +static void subid_ranges_get_search(struct tevent_req *req); +static void subid_ranges_get_done(struct tevent_req *subreq); + + +struct subid_ranges_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *op; + struct sss_domain_info *domain; + + char *filter; + char *name; + const char **attrs; + + int dp_error; + int sdap_ret; +}; + +struct tevent_req *subid_ranges_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + const char *extra_value) +{ + struct tevent_req *req; + struct subid_ranges_get_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct subid_ranges_get_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->name = talloc_strdup(state, filter_value); + if (!state->name) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed\n"); + ret = ENOMEM; + goto done; + } + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + state->domain = sdom->dom; + + state->filter = talloc_asprintf(state, + "(&(%s=%s)(%s=%s))", + SYSDB_OBJECTCLASS, + ctx->opts->subid_map[SDAP_OC_SUBID_RANGE].name, + ctx->opts->subid_map[SDAP_AT_SUBID_RANGE_OWNER].name, + extra_value); + + ret = subid_ranges_get_retry(req); + if (ret != EOK) { + goto done; + } + + return req; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + return tevent_req_post(req, ev); +} + +static int subid_ranges_get_retry(struct tevent_req *req) +{ + struct subid_ranges_get_state *state = tevent_req_data(req, + struct subid_ranges_get_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, subid_ranges_get_connect_done, req); + return EOK; +} + +static void subid_ranges_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct subid_ranges_get_state *state = tevent_req_data(req, + struct subid_ranges_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subid_ranges_get_search(req); +} + +static void subid_ranges_get_search(struct tevent_req *req) +{ + struct subid_ranges_get_state *state = tevent_req_data(req, + struct subid_ranges_get_state); + struct tevent_req *subreq = NULL; + const char **attrs; + int ret; + + ret = build_attrs_from_map(state, state->ctx->opts->subid_map, + SDAP_OPTS_SUBID_RANGE, NULL, &attrs, NULL); + if (ret != EOK) { + tevent_req_error(req, ENOMEM); + return; + } + + subreq = sdap_search_bases_send(state, state->ev, state->ctx->opts, + sdap_id_op_handle(state->op), + state->sdom->subid_ranges_search_bases, + state->ctx->opts->subid_map, + false, /* allow_paging */ + dp_opt_get_int(state->ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + state->filter, + attrs, + NULL); + talloc_free(attrs); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, subid_ranges_get_done, req); +} + +static void subid_ranges_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct subid_ranges_get_state *state = tevent_req_data(req, + struct subid_ranges_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + struct sysdb_attrs **results; + size_t num_results; + + ret = sdap_search_bases_recv(subreq, state, &num_results, &results); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = subid_ranges_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } + state->sdap_ret = ret; + + if (ret && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to retrieve subid ranges.\n"); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (num_results == 0 || !results) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No such user '%s' or user doesn't have subid range\n", + state->name); + sysdb_delete_subid_range(state->domain, state->name); + } else { + if (num_results > 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Multiple subid ranges, only first will be processed\n"); + } + + /* store range */ + sysdb_store_subid_range(state->domain, state->name, + state->domain->user_timeout, + results[0]); + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +int subid_ranges_get_recv(struct tevent_req *req, int *dp_error_out, + int *sdap_ret) +{ + struct subid_ranges_get_state *state = tevent_req_data(req, + struct subid_ranges_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c new file mode 100644 index 0000000..8d96ea1 --- /dev/null +++ b/src/providers/ldap/ldap_init.c @@ -0,0 +1,529 @@ +/* + SSSD + + LDAP Provider Initialization functions + + Authors: + Simo Sorce + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/child_common.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/ldap_opts.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/sdap_access.h" +#include "providers/ldap/sdap_hostid.h" +#include "providers/ldap/sdap_sudo.h" +#include "providers/ldap/sdap_autofs.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/ldap_resolver_enum.h" +#include "providers/fail_over_srv.h" +#include "providers/be_refresh.h" + +struct ldap_init_ctx { + struct sdap_options *options; + struct sdap_id_ctx *id_ctx; + struct sdap_auth_ctx *auth_ctx; + struct sdap_resolver_ctx *resolver_ctx; +}; + +static errno_t ldap_init_auth_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_options *options, + struct sdap_auth_ctx **_auth_ctx) +{ + struct sdap_auth_ctx *auth_ctx; + + auth_ctx = talloc(mem_ctx, struct sdap_auth_ctx); + if (auth_ctx == NULL) { + return ENOMEM; + } + + auth_ctx->be = be_ctx; + auth_ctx->opts = options; + auth_ctx->service = id_ctx->conn->service; + auth_ctx->chpass_service = NULL; + + *_auth_ctx = auth_ctx; + + return EOK; +} + +static errno_t init_chpass_service(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_options *opts, + struct sdap_service **_chpass_service) +{ + errno_t ret; + const char *urls; + const char *backup_urls; + const char *dns_service_name; + struct sdap_service *chpass_service; + + dns_service_name = dp_opt_get_string(opts->basic, + SDAP_CHPASS_DNS_SERVICE_NAME); + if (dns_service_name != NULL) { + DEBUG(SSSDBG_TRACE_LIBS, + "Service name for chpass discovery set to %s\n", + dns_service_name); + } + + urls = dp_opt_get_string(opts->basic, SDAP_CHPASS_URI); + backup_urls = dp_opt_get_string(opts->basic, SDAP_CHPASS_BACKUP_URI); + + if (urls != NULL || backup_urls != NULL || dns_service_name != NULL) { + ret = sdap_service_init(mem_ctx, + be_ctx, + "LDAP_CHPASS", + dns_service_name, + urls, + backup_urls, + &chpass_service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize failover service!\n"); + return ret; + } + } else { + DEBUG(SSSDBG_TRACE_ALL, + "ldap_chpass_uri and ldap_chpass_dns_service_name not set, " + "using ldap_uri.\n"); + chpass_service = NULL; + } + + *_chpass_service = chpass_service; + return EOK; +} + +static errno_t get_sdap_service(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_options *opts, + struct sdap_service **_sdap_service) +{ + errno_t ret; + const char *urls; + const char *backup_urls; + const char *dns_service_name; + struct sdap_service *sdap_service; + + urls = dp_opt_get_string(opts->basic, SDAP_URI); + backup_urls = dp_opt_get_string(opts->basic, SDAP_BACKUP_URI); + dns_service_name = dp_opt_get_string(opts->basic, SDAP_DNS_SERVICE_NAME); + if (dns_service_name != NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Service name for discovery set to %s\n", dns_service_name); + } + + ret = sdap_service_init(mem_ctx, be_ctx, "LDAP", + dns_service_name, + urls, + backup_urls, + &sdap_service); + if (ret != EOK) { + return ret; + } + + *_sdap_service = sdap_service; + return EOK; +} + +static bool should_call_gssapi_init(struct sdap_options *opts) +{ + const char *sasl_mech; + + sasl_mech = dp_opt_get_string(opts->basic, SDAP_SASL_MECH); + if (sasl_mech == NULL) { + return false; + } + + if (!sdap_sasl_mech_needs_kinit(sasl_mech)) { + return false; + } + + if (dp_opt_get_bool(opts->basic, SDAP_KRB5_KINIT) == false) { + return false; + } + + return true; +} + +static errno_t ldap_init_misc(struct be_ctx *be_ctx, + struct sdap_options *options, + struct sdap_id_ctx *id_ctx) +{ + errno_t ret; + + if (should_call_gssapi_init(options)) { + ret = sdap_gssapi_init(id_ctx, options->basic, be_ctx, + id_ctx->conn->service, &id_ctx->krb5_service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_gssapi_init failed [%d][%s].\n", + ret, sss_strerror(ret)); + return ret; + } + } + + setup_ldap_debug(options->basic); + + ret = setup_tls_config(options->basic); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get TLS options [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + /* Setup the ID mapping object */ + ret = sdap_idmap_init(id_ctx, id_ctx, &options->idmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not initialize ID mapping. In case ID mapping properties " + "changed on the server, please remove the SSSD database\n"); + return ret; + } + + ret = ldap_id_setup_tasks(id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup background tasks " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + /* Setup SRV lookup plugin */ + ret = be_fo_set_dns_srv_lookup_plugin(be_ctx, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set SRV lookup plugin " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + /* Setup periodical refresh of expired records */ + ret = sdap_refresh_init(be_ctx, id_ctx); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh will not work " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + ret = confdb_certmap_to_sysdb(be_ctx->cdb, be_ctx->domain, false); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize certificate mapping rules. " + "Authentication with certificates/Smartcards might not work " + "as expected.\n"); + /* not fatal, ignored */ + } + + ret = sdap_init_certmap(id_ctx, id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialized certificate mapping.\n"); + return ret; + } + + return EOK; +} + +errno_t sssm_ldap_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data) +{ + struct sdap_service *sdap_service; + struct ldap_init_ctx *init_ctx; + errno_t ret; + + init_ctx = talloc_zero(mem_ctx, struct ldap_init_ctx); + if (init_ctx == NULL) { + return ENOMEM; + } + + /* Always initialize options since it is needed everywhere. */ + ret = ldap_get_options(init_ctx, be_ctx->domain, be_ctx->cdb, + be_ctx->conf_path, be_ctx->provider, + &init_ctx->options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize LDAP options " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Always initialize id_ctx since it is needed everywhere. */ + ret = get_sdap_service(init_ctx, be_ctx, init_ctx->options, &sdap_service); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to initialize failover service " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + init_ctx->id_ctx = sdap_id_ctx_new(init_ctx, be_ctx, sdap_service); + if (init_ctx->id_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize LDAP ID context\n"); + ret = ENOMEM; + goto done; + } + + init_ctx->id_ctx->opts = init_ctx->options; + + /* Setup miscellaneous things. */ + ret = ldap_init_misc(be_ctx, init_ctx->options, init_ctx->id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init LDAP module " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Initialize auth_ctx only if one of the target is enabled. */ + if (dp_target_enabled(provider, module_name, DPT_AUTH, DPT_CHPASS)) { + ret = ldap_init_auth_ctx(init_ctx, be_ctx, init_ctx->id_ctx, + init_ctx->options, &init_ctx->auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create auth context " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } + + *_module_data = init_ctx; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(init_ctx); + } + + return ret; +} + +errno_t sssm_ldap_id_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ldap_init_ctx *init_ctx; + struct sdap_id_ctx *id_ctx; + + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + id_ctx = init_ctx->id_ctx; + + dp_set_method(dp_methods, DPM_ACCOUNT_HANDLER, + sdap_account_info_handler_send, sdap_account_info_handler_recv, id_ctx, + struct sdap_id_ctx, struct dp_id_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_CHECK_ONLINE, + sdap_online_check_handler_send, sdap_online_check_handler_recv, id_ctx, + struct sdap_id_ctx, void, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_ACCT_DOMAIN_HANDLER, + default_account_domain_send, default_account_domain_recv, NULL, + void, struct dp_get_acct_domain_data, struct dp_reply_std); + + return EOK; +} + +errno_t sssm_ldap_auth_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ldap_init_ctx *init_ctx; + struct sdap_auth_ctx *auth_ctx; + + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + auth_ctx = init_ctx->auth_ctx; + + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + sdap_pam_auth_handler_send, sdap_pam_auth_handler_recv, auth_ctx, + struct sdap_auth_ctx, struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_ldap_chpass_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ldap_init_ctx *init_ctx; + struct sdap_auth_ctx *auth_ctx; + errno_t ret; + + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + auth_ctx = init_ctx->auth_ctx; + + ret = init_chpass_service(auth_ctx, be_ctx, init_ctx->options, + &auth_ctx->chpass_service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize chpass service " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + sdap_pam_chpass_handler_send, sdap_pam_chpass_handler_recv, auth_ctx, + struct sdap_auth_ctx, struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_ldap_access_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ldap_init_ctx *init_ctx; + struct sdap_access_ctx *access_ctx; + errno_t ret; + + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + + access_ctx = talloc_zero(mem_ctx, struct sdap_access_ctx); + if(access_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + access_ctx->type = SDAP_TYPE_LDAP; + access_ctx->id_ctx = init_ctx->id_ctx; + + ret = sdap_set_access_rules(access_ctx, access_ctx, + access_ctx->id_ctx->opts->basic, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "set_access_rules failed: [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + dp_set_method(dp_methods, DPM_ACCESS_HANDLER, + sdap_pam_access_handler_send, sdap_pam_access_handler_recv, access_ctx, + struct sdap_access_ctx, struct pam_data, struct pam_data *); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(access_ctx); + } + + return ret; +} + +errno_t sssm_ldap_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_SSH + struct ldap_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing LDAP host handler\n"); + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + + return sdap_hostid_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); + +#else + DEBUG(SSSDBG_MINOR_FAILURE, "HostID init handler called but SSSD is " + "built without SSH support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ldap_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_AUTOFS + struct ldap_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing LDAP autofs handler\n"); + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + + return sdap_autofs_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); +#else + DEBUG(SSSDBG_MINOR_FAILURE, "Autofs init handler called but SSSD is " + "built without autofs support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ldap_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_SUDO + struct ldap_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing LDAP sudo handler\n"); + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + + return sdap_sudo_init(mem_ctx, + be_ctx, + init_ctx->id_ctx, + native_sudorule_map, + dp_methods); +#else + DEBUG(SSSDBG_MINOR_FAILURE, "Sudo init handler called but SSSD is " + "built without sudo support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ldap_resolver_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ldap_init_ctx *init_ctx; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing LDAP resolver handler\n"); + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + + ret = sdap_resolver_ctx_new(init_ctx, init_ctx->id_ctx, + &init_ctx->resolver_ctx); + if (ret != EOK) { + return ret; + } + + ret = ldap_resolver_setup_tasks(be_ctx, init_ctx->resolver_ctx, + ldap_resolver_enumeration_send, + ldap_resolver_enumeration_recv); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to setup resolver background tasks [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + dp_set_method(dp_methods, DPM_RESOLVER_HOSTS_HANDLER, + sdap_iphost_handler_send, sdap_iphost_handler_recv, + init_ctx->resolver_ctx, struct sdap_resolver_ctx, + struct dp_resolver_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_RESOLVER_IP_NETWORK_HANDLER, + sdap_ipnetwork_handler_send, sdap_ipnetwork_handler_recv, + init_ctx->resolver_ctx, struct sdap_resolver_ctx, + struct dp_resolver_data, struct dp_reply_std); + + return EOK; +} diff --git a/src/providers/ldap/ldap_options.c b/src/providers/ldap/ldap_options.c new file mode 100644 index 0000000..277bcb5 --- /dev/null +++ b/src/providers/ldap/ldap_options.c @@ -0,0 +1,827 @@ +/* + Authors: + Simo Sorce + + Copyright (C) 2008-2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/ldap_opts.h" +#include "providers/ldap/sdap_async_private.h" +#include "util/crypto/sss_crypto.h" + +int ldap_get_options(TALLOC_CTX *memctx, + struct sss_domain_info *dom, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sdap_options **_opts) +{ + struct sdap_attr_map *default_attr_map; + struct sdap_attr_map *default_user_map; + struct sdap_attr_map *default_group_map; + struct sdap_attr_map *default_netgroup_map; + struct sdap_attr_map *default_host_map; + struct sdap_attr_map *default_service_map; + struct sdap_attr_map *default_iphost_map; + struct sdap_attr_map *default_ipnetwork_map; + struct sdap_options *opts; + struct ldb_context *ldb; + char *schema; + char *pwmodify; + const char *search_base; + const char *pwd_policy; + int ret; + int account_cache_expiration; + int offline_credentials_expiration; + const char *ldap_deref; + int ldap_deref_val; + int o; + const char *authtok_type; + struct dp_opt_blob authtok_blob; + char *cleartext; + const int search_base_options[] = { SDAP_USER_SEARCH_BASE, + SDAP_GROUP_SEARCH_BASE, + SDAP_NETGROUP_SEARCH_BASE, + SDAP_HOST_SEARCH_BASE, + SDAP_SERVICE_SEARCH_BASE, + SDAP_IPHOST_SEARCH_BASE, + SDAP_IPNETWORK_SEARCH_BASE, + -1 }; + + opts = talloc_zero(memctx, struct sdap_options); + if (!opts) return ENOMEM; + opts->dp = dp; + + ret = sdap_domain_add(opts, dom, NULL); + if (ret != EOK) { + goto done; + } + + ret = dp_get_options(opts, cdb, conf_path, + default_basic_opts, + SDAP_OPTS_BASIC, + &opts->basic); + if (ret != EOK) { + goto done; + } + + /* Handle search bases */ + search_base = dp_opt_get_string(opts->basic, SDAP_SEARCH_BASE); + if (search_base != NULL) { + /* set user/group/netgroup search bases if they are not */ + for (o = 0; search_base_options[o] != -1; o++) { + if (NULL == dp_opt_get_string(opts->basic, search_base_options[o])) { + ret = dp_opt_set_string(opts->basic, search_base_options[o], + search_base); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + opts->basic[search_base_options[o]].opt_name, + dp_opt_get_string(opts->basic, + search_base_options[o])); + } + } + } else { + DEBUG(SSSDBG_FUNC_DATA, + "Search base not set, trying to discover it later when " + "connecting to the LDAP server.\n"); + } + + ldb = sysdb_ctx_get_ldb(dom->sysdb); + + /* Default search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_SEARCH_BASE, + &opts->sdom->search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* User search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_USER_SEARCH_BASE, + &opts->sdom->user_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Group search base */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_GROUP_SEARCH_BASE, + &opts->sdom->group_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Netgroup search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_NETGROUP_SEARCH_BASE, + &opts->sdom->netgroup_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Netgroup search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_HOST_SEARCH_BASE, + &opts->sdom->host_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Service search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_SERVICE_SEARCH_BASE, + &opts->sdom->service_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* IP host search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_IPHOST_SEARCH_BASE, + &opts->sdom->iphost_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* IP network search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_IPNETWORK_SEARCH_BASE, + &opts->sdom->ipnetwork_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + pwd_policy = dp_opt_get_string(opts->basic, SDAP_PWD_POLICY); + if (pwd_policy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing password policy, this may not happen.\n"); + ret = EINVAL; + goto done; + } + if (strcasecmp(pwd_policy, PWD_POL_OPT_NONE) != 0 && + strcasecmp(pwd_policy, PWD_POL_OPT_SHADOW) != 0 && + strcasecmp(pwd_policy, PWD_POL_OPT_MIT) != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported password policy [%s].\n", pwd_policy); + ret = EINVAL; + goto done; + } + + /* account_cache_expiration must be >= than offline_credentials_expiration */ + ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CRED_TIMEOUT, 0, + &offline_credentials_expiration); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get value of %s from confdb \n", + CONFDB_PAM_CRED_TIMEOUT); + goto done; + } + + account_cache_expiration = dp_opt_get_int(opts->basic, + SDAP_ACCOUNT_CACHE_EXPIRATION); + + /* account cache_expiration must not be smaller than + * offline_credentials_expiration to prevent deleting entries that + * still contain credentials valid for offline login. + * + * offline_credentials_expiration == 0 is a special case that says + * that the cached credentials are valid forever. Therefore, the cached + * entries must not be purged from cache. + */ + if (!offline_credentials_expiration && account_cache_expiration) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Conflicting values for options %s (unlimited) " + "and %s (%d)\n", + opts->basic[SDAP_ACCOUNT_CACHE_EXPIRATION].opt_name, + CONFDB_PAM_CRED_TIMEOUT, + offline_credentials_expiration); + ret = EINVAL; + goto done; + } + if (offline_credentials_expiration && account_cache_expiration && + offline_credentials_expiration > account_cache_expiration) { + DEBUG(SSSDBG_CRIT_FAILURE, "Value of %s (now %d) must be larger " + "than value of %s (now %d)\n", + opts->basic[SDAP_ACCOUNT_CACHE_EXPIRATION].opt_name, + account_cache_expiration, + CONFDB_PAM_CRED_TIMEOUT, + offline_credentials_expiration); + ret = EINVAL; + goto done; + } + + ldap_deref = dp_opt_get_string(opts->basic, SDAP_DEREF); + if (ldap_deref != NULL) { + ret = deref_string_to_val(ldap_deref, &ldap_deref_val); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to verify ldap_deref option.\n"); + goto done; + } + } + +#ifndef HAVE_LDAP_CONNCB + bool ldap_referrals; + + ldap_referrals = dp_opt_get_bool(opts->basic, SDAP_REFERRALS); + if (ldap_referrals) { + DEBUG(SSSDBG_CRIT_FAILURE, + "LDAP referrals are not supported, because the LDAP library " + "is too old, see sssd-ldap(5) for details.\n"); + ret = dp_opt_set_bool(opts->basic, SDAP_REFERRALS, false); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + } +#endif + + /* schema type */ + schema = dp_opt_get_string(opts->basic, SDAP_SCHEMA); + if (strcasecmp(schema, "rfc2307") == 0) { + opts->schema_type = SDAP_SCHEMA_RFC2307; + default_attr_map = generic_attr_map; + default_user_map = rfc2307_user_map; + default_group_map = rfc2307_group_map; + default_netgroup_map = netgroup_map; + default_host_map = host_map; + default_service_map = service_map; + default_iphost_map = iphost_map; + default_ipnetwork_map = ipnetwork_map; + } else + if (strcasecmp(schema, "rfc2307bis") == 0) { + opts->schema_type = SDAP_SCHEMA_RFC2307BIS; + default_attr_map = generic_attr_map; + default_user_map = rfc2307bis_user_map; + default_group_map = rfc2307bis_group_map; + default_netgroup_map = netgroup_map; + default_host_map = host_map; + default_service_map = service_map; + default_iphost_map = iphost_map; + default_ipnetwork_map = ipnetwork_map; + } else + if (strcasecmp(schema, "IPA") == 0) { + opts->schema_type = SDAP_SCHEMA_IPA_V1; + default_attr_map = gen_ipa_attr_map; + default_user_map = rfc2307bis_user_map; + default_group_map = rfc2307bis_group_map; + default_netgroup_map = netgroup_map; + default_host_map = host_map; + default_service_map = service_map; + default_iphost_map = iphost_map; + default_ipnetwork_map = ipnetwork_map; + } else + if (strcasecmp(schema, "AD") == 0) { + opts->schema_type = SDAP_SCHEMA_AD; + default_attr_map = gen_ad_attr_map; + default_user_map = gen_ad2008r2_user_map; + default_group_map = gen_ad2008r2_group_map; + default_netgroup_map = netgroup_map; + default_host_map = host_map; + default_service_map = service_map; + default_iphost_map = iphost_map; + default_ipnetwork_map = ipnetwork_map; + } else { + DEBUG(SSSDBG_FATAL_FAILURE, "Unrecognized schema type: %s\n", schema); + ret = EINVAL; + goto done; + } + + /* pwmodify mode */ + pwmodify = dp_opt_get_string(opts->basic, SDAP_PWMODIFY_MODE); + if (strcasecmp(pwmodify, "exop") == 0) { + opts->pwmodify_mode = SDAP_PWMODIFY_EXOP; + } else if (strcasecmp(pwmodify, "ldap_modify") == 0) { + opts->pwmodify_mode = SDAP_PWMODIFY_LDAP; + } else { + DEBUG(SSSDBG_FATAL_FAILURE, "Unrecognized pwmodify mode: %s\n", pwmodify); + ret = EINVAL; + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_attr_map, + SDAP_AT_GENERAL, + &opts->gen_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_user_map, + SDAP_OPTS_USER, + &opts->user_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_extend_map_with_list(opts, opts, SDAP_USER_EXTRA_ATTRS, + opts->user_map, SDAP_OPTS_USER, + &opts->user_map, &opts->user_map_cnt); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_group_map, + SDAP_OPTS_GROUP, + &opts->group_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_netgroup_map, + SDAP_OPTS_NETGROUP, + &opts->netgroup_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_host_map, + SDAP_OPTS_HOST, + &opts->host_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_service_map, + SDAP_OPTS_SERVICES, + &opts->service_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_iphost_map, + SDAP_OPTS_IPHOST, + &opts->iphost_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_ipnetwork_map, + SDAP_OPTS_IPNETWORK, + &opts->ipnetwork_map); + if (ret != EOK) { + goto done; + } + + /* If there is no KDC, try the deprecated krb5_kdcip option, too */ + /* FIXME - this can be removed in a future version */ + ret = krb5_try_kdcip(cdb, conf_path, opts->basic, SDAP_KRB5_KDC); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_try_kdcip failed.\n"); + goto done; + } + + authtok_type = dp_opt_get_string(opts->basic, SDAP_DEFAULT_AUTHTOK_TYPE); + if (authtok_type != NULL && + strcasecmp(authtok_type,"obfuscated_password") == 0) { + DEBUG(SSSDBG_TRACE_ALL, "Found obfuscated password, " + "trying to convert to cleartext.\n"); + + authtok_blob = dp_opt_get_blob(opts->basic, SDAP_DEFAULT_AUTHTOK); + if (authtok_blob.data == NULL || authtok_blob.length == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing obfuscated password string.\n"); + ret = EINVAL; + goto done; + } + + ret = sss_password_decrypt(memctx, (char *) authtok_blob.data, + &cleartext); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot convert the obfuscated " + "password back to cleartext\n"); + goto done; + } + + authtok_blob.data = (uint8_t *) cleartext; + authtok_blob.length = strlen(cleartext); + ret = dp_opt_set_blob(opts->basic, SDAP_DEFAULT_AUTHTOK, authtok_blob); + /* `cleartext` is erased only to reduce possible attack surface. + * Its copy will be kept in opts->basic[SDAP_DEFAULT_AUTHTOK] + * during program execution anyway. + * This option is never replaced so it doesn't make a sense to set + * a destructor. + */ + sss_erase_talloc_mem_securely(cleartext); + talloc_free(cleartext); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_blob(authtok) failed.\n"); + goto done; + } + + ret = dp_opt_set_string(opts->basic, SDAP_DEFAULT_AUTHTOK_TYPE, + "password"); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "dp_opt_set_string(authtok_type) failed.\n"); + goto done; + } + } + + ret = EOK; + *_opts = opts; + +done: + if (ret != EOK) { + talloc_zfree(opts); + } + return ret; +} + +int ldap_get_sudo_options(struct confdb_ctx *cdb, + struct ldb_context *ldb, + const char *conf_path, + struct sdap_options *opts, + struct sdap_attr_map *native_map, + bool *use_host_filter, + bool *include_regexp, + bool *include_netgroups) +{ + const char *search_base; + int ret; + + /* search base */ + search_base = dp_opt_get_string(opts->basic, SDAP_SEARCH_BASE); + if (search_base != NULL) { + /* set sudo search bases if they are not */ + if (dp_opt_get_string(opts->basic, SDAP_SUDO_SEARCH_BASE) == NULL) { + ret = dp_opt_set_string(opts->basic, SDAP_SUDO_SEARCH_BASE, + search_base); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set SUDO search base" + "to default value\n"); + return ret; + } + + DEBUG(SSSDBG_FUNC_DATA, "Option %s set to %s\n", + opts->basic[SDAP_SUDO_SEARCH_BASE].opt_name, + dp_opt_get_string(opts->basic, SDAP_SUDO_SEARCH_BASE)); + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Search base not set, trying to discover it later " + "connecting to the LDAP server.\n"); + } + + ret = sdap_parse_search_base(opts, ldb, + opts->basic, SDAP_SUDO_SEARCH_BASE, + &opts->sdom->sudo_search_bases); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Could not parse SUDO search base\n"); + return ret; + } + + /* attrs map */ + ret = sdap_get_map(opts, cdb, conf_path, + native_map, + SDAP_OPTS_SUDO, + &opts->sudorule_map); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get SUDO attribute map\n"); + return ret; + } + + /* host filter */ + *use_host_filter = dp_opt_get_bool(opts->basic, SDAP_SUDO_USE_HOST_FILTER); + *include_netgroups = dp_opt_get_bool(opts->basic, SDAP_SUDO_INCLUDE_NETGROUPS); + *include_regexp = dp_opt_get_bool(opts->basic, SDAP_SUDO_INCLUDE_REGEXP); + + return EOK; +} + +int ldap_get_autofs_options(TALLOC_CTX *memctx, + struct ldb_context *ldb, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options *opts) +{ + const char *search_base; + struct sdap_attr_map *default_entry_map; + struct sdap_attr_map *default_mobject_map; + int ret; + + /* search base */ + search_base = dp_opt_get_string(opts->basic, SDAP_SEARCH_BASE); + if (search_base != NULL) { + /* set autofs search bases if they are not */ + if (dp_opt_get_string(opts->basic, SDAP_AUTOFS_SEARCH_BASE) == NULL) { + ret = dp_opt_set_string(opts->basic, SDAP_AUTOFS_SEARCH_BASE, + search_base); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set autofs search base" + "to default value\n"); + return ret; + } + + DEBUG(SSSDBG_FUNC_DATA, "Option %s set to %s\n", + opts->basic[SDAP_AUTOFS_SEARCH_BASE].opt_name, + dp_opt_get_string(opts->basic, SDAP_AUTOFS_SEARCH_BASE)); + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Search base not set, trying to discover it later " + "connecting to the LDAP server.\n"); + } + + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_AUTOFS_SEARCH_BASE, + &opts->sdom->autofs_search_bases); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Could not parse autofs search base\n"); + return ret; + } + + /* attribute maps */ + switch (opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + default_mobject_map = rfc2307_autofs_mobject_map; + default_entry_map = rfc2307_autofs_entry_map; + break; + case SDAP_SCHEMA_RFC2307BIS: + case SDAP_SCHEMA_IPA_V1: + case SDAP_SCHEMA_AD: + default_mobject_map = rfc2307bis_autofs_mobject_map; + default_entry_map = rfc2307bis_autofs_entry_map; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown LDAP schema %d!\n", opts->schema_type); + return EINVAL; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_mobject_map, + SDAP_OPTS_AUTOFS_MAP, + &opts->autofs_mobject_map); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not get autofs map object attribute map\n"); + return ret; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_entry_map, + SDAP_OPTS_AUTOFS_ENTRY, + &opts->autofs_entry_map); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not get autofs entry object attribute map\n"); + return ret; + } + + return EOK; +} + +errno_t sdap_parse_search_base(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + struct dp_option *opts, int class, + struct sdap_search_base ***_search_bases) +{ + const char *class_name; + char *unparsed_base; + const char *old_filter = NULL; + + switch (class) { + case SDAP_SEARCH_BASE: + class_name = "DEFAULT"; + break; + case SDAP_USER_SEARCH_BASE: + class_name = "USER"; + old_filter = dp_opt_get_string(opts, SDAP_USER_SEARCH_FILTER); + break; + case SDAP_GROUP_SEARCH_BASE: + class_name = "GROUP"; + old_filter = dp_opt_get_string(opts, SDAP_GROUP_SEARCH_FILTER); + break; + case SDAP_NETGROUP_SEARCH_BASE: + class_name = "NETGROUP"; + break; + case SDAP_HOST_SEARCH_BASE: + class_name = "HOST"; + break; + case SDAP_SUDO_SEARCH_BASE: + class_name = "SUDO"; + break; + case SDAP_SERVICE_SEARCH_BASE: + class_name = "SERVICE"; + break; + case SDAP_AUTOFS_SEARCH_BASE: + class_name = "AUTOFS"; + break; + case SDAP_IPHOST_SEARCH_BASE: + class_name = "IPHOST"; + break; + case SDAP_IPNETWORK_SEARCH_BASE: + class_name = "IPNETWORK"; + break; + default: + DEBUG(SSSDBG_CONF_SETTINGS, + "Unknown search base type: [%d]\n", class); + class_name = "UNKNOWN"; + /* Non-fatal */ + break; + } + + unparsed_base = dp_opt_get_string(opts, class); + if (!unparsed_base || unparsed_base[0] == '\0') return ENOENT; + + return common_parse_search_base(mem_ctx, unparsed_base, ldb, + class_name, old_filter, + _search_bases); +} + +errno_t common_parse_search_base(TALLOC_CTX *mem_ctx, + const char *unparsed_base, + struct ldb_context *ldb, + const char *class_name, + const char *old_filter, + struct sdap_search_base ***_search_bases) +{ + errno_t ret; + struct sdap_search_base **search_bases; + TALLOC_CTX *tmp_ctx; + struct ldb_dn *ldn; + struct ldb_parse_tree *tree; + char **split_bases; + char *filter; + int count; + int i, c; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + ret = split_on_separator(tmp_ctx, unparsed_base, '?', false, false, + &split_bases, &count); + if (ret != EOK) goto done; + + /* The split must be either exactly one value or a multiple of + * three in order to be valid. + * One value: just a base, backwards-compatible with pre-1.7.0 versions + * Multiple: search_base?scope?filter[?search_base?scope?filter]* + */ + if (count > 1 && (count % 3)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unparseable search base: [%s][%d]\n", unparsed_base, count); + ret = EINVAL; + goto done; + } + + if (count == 1) { + search_bases = talloc_array(tmp_ctx, struct sdap_search_base *, 2); + if (!search_bases) { + ret = ENOMEM; + goto done; + } + + if (old_filter != NULL) { + /* Using a deprecated ldap_{user,group}_search_filter */ + DEBUG(SSSDBG_IMPORTANT_INFO, "WARNING: Using a deprecated filter " + "option for %s. Please see the documentation on LDAP search " + "bases to see how the obsolete option can be migrated\n", + class_name); + sss_log(SSS_LOG_NOTICE, "WARNING: Using a deprecated filter option" + "for %s. Please see the documentation on LDAP search bases " + "to see how the obsolete option can be migrated\n", + class_name); + } + + ret = sdap_create_search_base(search_bases, ldb, unparsed_base, + LDAP_SCOPE_SUBTREE, old_filter, + &search_bases[0]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot create new sdap search base\n"); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Search base added: [%s][%s][%s][%s]\n", + class_name, + search_bases[0]->basedn, + "SUBTREE", + search_bases[0]->filter ? search_bases[0]->filter : ""); + + search_bases[1] = NULL; + } else { + search_bases = talloc_array(tmp_ctx, struct sdap_search_base *, + (count / 3) + 1); + if (!search_bases) { + ret = ENOMEM; + goto done; + } + + i = 0; + for (c = 0; c < count; c += 3) { + search_bases[i] = talloc_zero(search_bases, + struct sdap_search_base); + if (!search_bases[i]) { + ret = ENOMEM; + goto done; + } + + if (split_bases[c][0] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Zero-length search base: [%s]\n", unparsed_base); + ret = EINVAL; + goto done; + } + + /* Validate the basedn */ + ldn = ldb_dn_new(tmp_ctx, ldb, split_bases[c]); + if (!ldn) { + ret = ENOMEM; + goto done; + } + + if (!ldb_dn_validate(ldn)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid base DN [%s]\n", + split_bases[c]); + ret = EINVAL; + goto done; + } + + /* Set the search base DN */ + search_bases[i]->ldb_basedn = talloc_steal(search_bases[i], ldn); + search_bases[i]->basedn = talloc_strdup(search_bases[i], + split_bases[c]); + if (!search_bases[i]->basedn) { + ret = ENOMEM; + goto done; + } + + /* Set the search scope for this base DN */ + if ((split_bases[c+1][0] == '\0') + || strcasecmp(split_bases[c+1], "sub") == 0 + || strcasecmp(split_bases[c+1], "subtree") == 0) { + /* If unspecified, default to subtree */ + search_bases[i]->scope = LDAP_SCOPE_SUBTREE; + } else if (strcasecmp(split_bases[c+1], "one") == 0 + || strcasecmp(split_bases[c+1], "onelevel") == 0) { + search_bases[i]->scope = LDAP_SCOPE_ONELEVEL; + } else if (strcasecmp(split_bases[c+1], "base") == 0) { + search_bases[i]->scope = LDAP_SCOPE_BASE; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown search scope: [%s]\n", split_bases[c+1]); + ret = EINVAL; + goto done; + } + + /* Get a specialized filter if provided */ + if (split_bases[c+2][0] == '\0') { + search_bases[i]->filter = NULL; + } else { + if (split_bases[c+2][0] != '(') { + /* Filters need to be enclosed in parentheses + * to be validated properly by ldb_parse_tree() + */ + filter = talloc_asprintf(tmp_ctx, "(%s)", + split_bases[c+2]); + } else { + filter = talloc_strdup(tmp_ctx, split_bases[c+2]); + } + if (!filter) { + ret = ENOMEM; + goto done; + } + + tree = ldb_parse_tree(tmp_ctx, filter); + if(!tree) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid search filter: [%s]\n", filter); + ret = EINVAL; + goto done; + } + talloc_zfree(tree); + + search_bases[i]->filter = talloc_steal(search_bases[i], + filter); + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Search base added: [%s][%s][%s][%s]\n", + class_name, + search_bases[i]->basedn, + split_bases[c+1][0] ? split_bases[c+1] : "SUBTREE", + search_bases[i]->filter ? search_bases[i]->filter : ""); + + i++; + } + search_bases[i] = NULL; + } + + *_search_bases = talloc_steal(mem_ctx, search_bases); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ldap/ldap_opts.c b/src/providers/ldap/ldap_opts.c new file mode 100644 index 0000000..bcf0299 --- /dev/null +++ b/src/providers/ldap/ldap_opts.c @@ -0,0 +1,425 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "src/providers/data_provider.h" +#include "db/sysdb.h" +#include "db/sysdb_sudo.h" +#include "db/sysdb_autofs.h" +#include "db/sysdb_services.h" +#include "db/sysdb_iphosts.h" +#include "db/sysdb_ipnetworks.h" +#include "providers/ldap/ldap_common.h" + +struct dp_option default_basic_opts[] = { + { "ldap_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_backup_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_authtok_type", DP_OPT_STRING, { "password" }, NULL_STRING}, + { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB }, + { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 8 }, NULL_NUMBER }, + { "ldap_tls_reqcert", DP_OPT_STRING, { "hard" }, NULL_STRING }, + { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_user_extra_attrs", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_host_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_service_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_full_refresh_interval", DP_OPT_NUMBER, { .number = 21600 }, NULL_NUMBER }, /* 360 mins */ + { "ldap_sudo_smart_refresh_interval", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, /* 15 mins */ + { "ldap_sudo_random_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, /* disabled */ + { "ldap_sudo_use_host_filter", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_sudo_hostnames", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_ip", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_include_netgroups", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_sudo_include_regexp", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_autofs_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_autofs_map_master_name", DP_OPT_STRING, { "auto.master" }, NULL_STRING }, + { "ldap_iphost_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_ipnetwork_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_schema", DP_OPT_STRING, { "rfc2307" }, NULL_STRING }, + { "ldap_pwmodify_mode", DP_OPT_STRING, { "exop" }, NULL_STRING }, + { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER }, + { "ldap_enumeration_refresh_offset", DP_OPT_NUMBER, { .number = 30 }, NULL_NUMBER }, + { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_purge_cache_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_key", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cipher_suite", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_id_use_start_tls", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_id_mapping", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_sasl_mech", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_authid", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_minssf", DP_OPT_NUMBER, { .number = -1 }, NULL_NUMBER }, + { "ldap_sasl_maxssf", DP_OPT_NUMBER, { .number = -1 }, NULL_NUMBER }, + { "ldap_krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_krb5_init_creds", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + /* use the same parm name as the krb5 module so we set it only once */ + { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_canonicalize", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kdcinfo_lookahead", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_pwd_policy", DP_OPT_STRING, { "none" }, NULL_STRING }, + { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "account_cache_expiration", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_dns_service_name", DP_OPT_STRING, { SSS_LDAP_SRV_NAME }, NULL_STRING }, + { "ldap_krb5_ticket_lifetime", DP_OPT_NUMBER, { .number = (24 * 60 * 60) }, NULL_NUMBER }, + { "ldap_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_netgroup_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_nesting_level", DP_OPT_NUMBER, { .number = 2 }, NULL_NUMBER }, + { "ldap_deref", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_account_expire_policy", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_access_order", DP_OPT_STRING, { "filter" }, NULL_STRING }, + { "ldap_chpass_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_backup_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_dns_service_name", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_update_last_change", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_enumeration_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + /* Do not include ldap_auth_disable_tls_never_use_in_production in the + * manpages or SSSDConfig API + */ + { "ldap_auth_disable_tls_never_use_in_production", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_page_size", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER }, + { "ldap_deref_threshold", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }, + { "ldap_ignore_unreadable_references", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_sasl_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_connection_expire_timeout", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, + { "ldap_connection_expire_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_connection_idle_timeout", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, + { "ldap_disable_paging", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_idmap_range_min", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, + { "ldap_idmap_range_max", DP_OPT_NUMBER, { .number = 2000200000LL }, NULL_NUMBER }, + { "ldap_idmap_range_size", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, + { "ldap_idmap_autorid_compat", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_idmap_default_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_idmap_default_domain_sid", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_idmap_helper_table_size", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }, + { "ldap_use_tokengroups", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE}, + { "ldap_rfc2307_fallback_to_local_users", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_disable_range_retrieval", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_min_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_max_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_pwdlockout_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "wildcard_limit", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER}, + { "ldap_library_debug_level", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + DP_OPTION_TERMINATOR +}; + +struct sdap_attr_map generic_attr_map[] = { + { "ldap_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", NULL, SYSDB_HIGH_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map gen_ipa_attr_map[] = { + { "ldap_entry_usn", SDAP_IPA_USN, SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", SDAP_IPA_LAST_USN, SYSDB_HIGH_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map gen_ad_attr_map[] = { + { "ldap_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", SDAP_AD_LAST_USN, SYSDB_HIGH_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307_user_map[] = { + { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL }, + { "ldap_user_name", "uid", SYSDB_NAME, NULL }, + { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL }, + { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL }, + { "ldap_user_member_of", NULL, SYSDB_MEMBEROF, NULL }, + { "ldap_user_uuid", NULL, SYSDB_UUID, NULL }, + { "ldap_user_objectsid", NULL, SYSDB_SID, NULL }, + { "ldap_user_primary_group", NULL, SYSDB_PRIMARY_GROUP, NULL }, + { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }, + { "ldap_user_authorized_service", "authorizedService", SYSDB_AUTHORIZED_SERVICE, NULL }, + { "ldap_user_ad_account_expires", "accountExpires", SYSDB_AD_ACCOUNT_EXPIRES, NULL}, + { "ldap_user_ad_user_account_control", "userAccountControl", SYSDB_AD_USER_ACCOUNT_CONTROL, NULL}, + { "ldap_ns_account_lock", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL}, + { "ldap_user_authorized_host", "host", SYSDB_AUTHORIZED_HOST, NULL }, + { "ldap_user_authorized_rhost", "rhost", SYSDB_AUTHORIZED_RHOST, NULL }, + { "ldap_user_nds_login_disabled", "loginDisabled", SYSDB_NDS_LOGIN_DISABLED, NULL }, + { "ldap_user_nds_login_expiration_time", "loginExpirationTime", SYSDB_NDS_LOGIN_EXPIRATION_TIME, NULL }, + { "ldap_user_nds_login_allowed_time_map", "loginAllowedTimeMap", SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP, NULL }, + { "ldap_user_ssh_public_key", "sshPublicKey", SYSDB_SSH_PUBKEY, NULL }, + { "ldap_user_auth_type", NULL, SYSDB_AUTH_TYPE, NULL }, + { "ldap_user_certificate", "userCertificate;binary", SYSDB_USER_CERT, NULL }, + { "ldap_user_email", "mail", SYSDB_USER_EMAIL, NULL }, + { "ldap_user_passkey", "passkey", SYSDB_USER_PASSKEY, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307_group_map[] = { + { "ldap_group_object_class", "posixGroup", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_object_class_alt", NULL, SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_name", "cn", SYSDB_NAME, NULL }, + { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_group_member", "memberuid", SYSDB_MEMBER, NULL }, + { "ldap_group_uuid", NULL, SYSDB_UUID, NULL }, + { "ldap_group_objectsid", NULL, SYSDB_SID, NULL }, + { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_group_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_group_type", NULL, SYSDB_GROUP_TYPE, NULL }, + { "ldap_group_external_member", NULL, SYSDB_EXTERNAL_MEMBER, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307bis_user_map[] = { + { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL }, + { "ldap_user_name", "uid", SYSDB_NAME, NULL }, + { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL }, + { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL }, + { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL }, + { "ldap_user_uuid", NULL, SYSDB_UUID, NULL }, + { "ldap_user_objectsid", NULL, SYSDB_SID, NULL }, + { "ldap_user_primary_group", NULL, SYSDB_PRIMARY_GROUP, NULL }, + { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }, + { "ldap_user_authorized_service", "authorizedService", SYSDB_AUTHORIZED_SERVICE, NULL }, + { "ldap_user_ad_account_expires", "accountExpires", SYSDB_AD_ACCOUNT_EXPIRES, NULL}, + { "ldap_user_ad_user_account_control", "userAccountControl", SYSDB_AD_USER_ACCOUNT_CONTROL, NULL}, + { "ldap_ns_account_lock", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL}, + { "ldap_user_authorized_host", "host", SYSDB_AUTHORIZED_HOST, NULL }, + { "ldap_user_authorized_rhost", "rhost", SYSDB_AUTHORIZED_RHOST, NULL }, + { "ldap_user_nds_login_disabled", "loginDisabled", SYSDB_NDS_LOGIN_DISABLED, NULL }, + { "ldap_user_nds_login_expiration_time", "loginExpirationTime", SYSDB_NDS_LOGIN_EXPIRATION_TIME, NULL }, + { "ldap_user_nds_login_allowed_time_map", "loginAllowedTimeMap", SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP, NULL }, + { "ldap_user_ssh_public_key", "sshPublicKey", SYSDB_SSH_PUBKEY, NULL }, + { "ldap_user_auth_type", NULL, SYSDB_AUTH_TYPE, NULL }, + { "ldap_user_certificate", "userCertificate;binary", SYSDB_USER_CERT, NULL }, + { "ldap_user_email", "mail", SYSDB_USER_EMAIL, NULL }, + { "ldap_user_passkey", "passkey", SYSDB_USER_PASSKEY, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307bis_group_map[] = { + { "ldap_group_object_class", "posixGroup", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_object_class_alt", NULL, SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_name", "cn", SYSDB_NAME, NULL }, + { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_group_member", "member", SYSDB_MEMBER, NULL }, + { "ldap_group_uuid", NULL, SYSDB_UUID, NULL }, + { "ldap_group_objectsid", NULL, SYSDB_SID, NULL }, + { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_group_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_group_type", NULL, SYSDB_GROUP_TYPE, NULL }, + { "ldap_group_external_member", NULL, SYSDB_EXTERNAL_MEMBER, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map gen_ad2008r2_user_map[] = { + { "ldap_user_object_class", "user", SYSDB_USER_CLASS, NULL }, + { "ldap_user_name", "sAMAccountName", SYSDB_NAME, NULL }, + { "ldap_user_pwd", "unixUserPassword", SYSDB_PWD, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "unixHomeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_user_principal", "userPrincipalName", SYSDB_UPN, NULL }, + { "ldap_user_fullname", "name", SYSDB_FULLNAME, NULL }, + { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL }, + { "ldap_user_uuid", "objectGUID", SYSDB_UUID, NULL }, + { "ldap_user_objectsid", "objectSID", SYSDB_SID, NULL }, + { "ldap_user_primary_group", "primaryGroupID", SYSDB_PRIMARY_GROUP, NULL }, + { "ldap_user_modify_timestamp", "whenChanged", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL }, + { "ldap_user_shadow_last_change", NULL, SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", NULL, SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", NULL, SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", NULL, SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", NULL, SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", NULL, SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", NULL, SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", NULL, SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", NULL, SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", NULL, SYSDB_PWD_ATTRIBUTE, NULL }, + { "ldap_user_authorized_service", NULL, SYSDB_AUTHORIZED_SERVICE, NULL }, + { "ldap_user_ad_account_expires", "accountExpires", SYSDB_AD_ACCOUNT_EXPIRES, NULL}, + { "ldap_user_ad_user_account_control", "userAccountControl", SYSDB_AD_USER_ACCOUNT_CONTROL, NULL}, + { "ldap_ns_account_lock", NULL, SYSDB_NS_ACCOUNT_LOCK, NULL}, + { "ldap_user_authorized_host", NULL, SYSDB_AUTHORIZED_HOST, NULL }, + { "ldap_user_authorized_rhost", NULL, SYSDB_AUTHORIZED_RHOST, NULL }, + { "ldap_user_nds_login_disabled", NULL, SYSDB_NDS_LOGIN_DISABLED, NULL }, + { "ldap_user_nds_login_expiration_time", NULL, SYSDB_NDS_LOGIN_EXPIRATION_TIME, NULL }, + { "ldap_user_nds_login_allowed_time_map", NULL, SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP, NULL }, + { "ldap_user_ssh_public_key", NULL, SYSDB_SSH_PUBKEY, NULL }, + { "ldap_user_auth_type", NULL, SYSDB_AUTH_TYPE, NULL }, + { "ldap_user_certificate", "userCertificate;binary", SYSDB_USER_CERT, NULL }, + { "ldap_user_email", "mail", SYSDB_USER_EMAIL, NULL }, + { "ldap_user_passkey", "passkey", SYSDB_USER_PASSKEY, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map gen_ad2008r2_group_map[] = { + { "ldap_group_object_class", "group", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_object_class_alt", NULL, SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_name", "sAMAccountName", SYSDB_NAME, NULL }, + { "ldap_group_pwd", NULL, SYSDB_PWD, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_group_member", "member", SYSDB_MEMBER, NULL }, + { "ldap_group_uuid", "objectGUID", SYSDB_UUID, NULL }, + { "ldap_group_objectsid", "objectSID", SYSDB_SID, NULL }, + { "ldap_group_modify_timestamp", "whenChanged", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_group_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL }, + { "ldap_group_type", "groupType", SYSDB_GROUP_TYPE, NULL }, + { "ldap_group_external_member", NULL, SYSDB_EXTERNAL_MEMBER, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map netgroup_map[] = { + { "ldap_netgroup_object_class", "nisNetgroup", SYSDB_NETGROUP_CLASS, NULL }, + { "ldap_netgroup_name", "cn", SYSDB_NAME, NULL }, + { "ldap_netgroup_member", "memberNisNetgroup", SYSDB_ORIG_NETGROUP_MEMBER, NULL }, + { "ldap_netgroup_triple", "nisNetgroupTriple", SYSDB_NETGROUP_TRIPLE, NULL }, + { "ldap_netgroup_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map host_map[] = { + { "ldap_host_object_class", "ipHost", SYSDB_HOST_CLASS, NULL }, + { "ldap_host_name", "cn", SYSDB_NAME, NULL }, + { "ldap_host_fqdn", "fqdn", SYSDB_FQDN, NULL }, + { "ldap_host_serverhostname", "serverHostname", SYSDB_SERVERHOSTNAME, NULL }, + { "ldap_host_member_of", NULL, SYSDB_ORIG_MEMBEROF, NULL }, + { "ldap_host_ssh_public_key", "sshPublicKey", SYSDB_SSH_PUBKEY, NULL }, + { "ldap_host_uuid", NULL, SYSDB_UUID, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map native_sudorule_map[] = { + { "ldap_sudorule_object_class", "sudoRole", SYSDB_SUDO_CACHE_OC, NULL }, + { "ldap_sudorule_object_class_attr", "objectClass", SYSDB_OBJECTCATEGORY, NULL }, + { "ldap_sudorule_name", "cn", SYSDB_SUDO_CACHE_AT_CN, NULL }, + { "ldap_sudorule_command", "sudoCommand", SYSDB_SUDO_CACHE_AT_COMMAND, NULL }, + { "ldap_sudorule_host", "sudoHost", SYSDB_SUDO_CACHE_AT_HOST, NULL }, + { "ldap_sudorule_user", "sudoUser", SYSDB_SUDO_CACHE_AT_USER, NULL }, + { "ldap_sudorule_option", "sudoOption", SYSDB_SUDO_CACHE_AT_OPTION, NULL }, + { "ldap_sudorule_runas", "sudoRunAs", SYSDB_SUDO_CACHE_AT_RUNAS, NULL }, + { "ldap_sudorule_runasuser", "sudoRunAsUser", SYSDB_SUDO_CACHE_AT_RUNASUSER, NULL }, + { "ldap_sudorule_runasgroup", "sudoRunAsGroup", SYSDB_SUDO_CACHE_AT_RUNASGROUP, NULL }, + { "ldap_sudorule_notbefore", "sudoNotBefore", SYSDB_SUDO_CACHE_AT_NOTBEFORE, NULL }, + { "ldap_sudorule_notafter", "sudoNotAfter", SYSDB_SUDO_CACHE_AT_NOTAFTER, NULL }, + { "ldap_sudorule_order", "sudoOrder", SYSDB_SUDO_CACHE_AT_ORDER, NULL }, + { "ldap_sudorule_entry_usn", NULL, SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map service_map[] = { + { "ldap_service_object_class", "ipService", SYSDB_SVC_CLASS, NULL }, + { "ldap_service_name", "cn", SYSDB_NAME, NULL }, + { "ldap_service_port", "ipServicePort", SYSDB_SVC_PORT, NULL }, + { "ldap_service_proto", "ipServiceProtocol", SYSDB_SVC_PROTO, NULL }, + { "ldap_service_entry_usn", NULL, SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map iphost_map[] = { + { "ldap_iphost_object_class", "ipHost", SYSDB_IP_HOST_CLASS, NULL }, + { "ldap_iphost_name", "cn", SYSDB_NAME, NULL }, + { "ldap_iphost_number", "ipHostNumber", SYSDB_IP_HOST_ATTR_ADDRESS, NULL }, + { "ldap_iphost_entry_usn", NULL, SYSDB_USN, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipnetwork_map[] = { + { "ldap_ipnetwork_object_class", "ipNetwork", SYSDB_IP_NETWORK_CLASS, NULL }, + { "ldap_ipnetwork_name", "cn", SYSDB_NAME, NULL }, + { "ldap_ipnetwork_number", "ipNetworkNumber", SYSDB_IP_NETWORK_ATTR_NUMBER, NULL }, + { "ldap_ipnetwork_entry_usn", NULL, SYSDB_USN, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307_autofs_mobject_map[] = { + { "ldap_autofs_map_object_class", "nisMap", SYSDB_AUTOFS_MAP_OC, NULL }, + { "ldap_autofs_map_name", "nisMapName", SYSDB_AUTOFS_MAP_NAME, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307_autofs_entry_map[] = { + { "ldap_autofs_entry_object_class", "nisObject", SYSDB_AUTOFS_ENTRY_OC, NULL }, + { "ldap_autofs_entry_key", "cn", SYSDB_AUTOFS_ENTRY_KEY, NULL }, + { "ldap_autofs_entry_value", "nisMapEntry", SYSDB_AUTOFS_ENTRY_VALUE, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307bis_autofs_mobject_map[] = { + { "ldap_autofs_map_object_class", "automountMap", SYSDB_AUTOFS_MAP_OC, NULL }, + { "ldap_autofs_map_name", "automountMapName", SYSDB_AUTOFS_MAP_NAME, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307bis_autofs_entry_map[] = { + { "ldap_autofs_entry_object_class", "automount", SYSDB_AUTOFS_ENTRY_OC, NULL }, + { "ldap_autofs_entry_key", "automountKey", SYSDB_AUTOFS_ENTRY_KEY, NULL }, + { "ldap_autofs_entry_value", "automountInformation", SYSDB_AUTOFS_ENTRY_VALUE, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; diff --git a/src/providers/ldap/ldap_opts.h b/src/providers/ldap/ldap_opts.h new file mode 100644 index 0000000..a1c80c3 --- /dev/null +++ b/src/providers/ldap/ldap_opts.h @@ -0,0 +1,69 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef LDAP_OPTS_H_ +#define LDAP_OPTS_H_ + +#include "src/providers/data_provider.h" +#include "providers/ldap/ldap_common.h" + +extern struct dp_option default_basic_opts[]; + +extern struct sdap_attr_map generic_attr_map[]; + +extern struct sdap_attr_map gen_ipa_attr_map[]; + +extern struct sdap_attr_map gen_ad_attr_map[]; + +extern struct sdap_attr_map rfc2307_user_map[]; + +extern struct sdap_attr_map rfc2307_group_map[]; + +extern struct sdap_attr_map rfc2307bis_user_map[]; + +extern struct sdap_attr_map rfc2307bis_group_map[]; + +extern struct sdap_attr_map gen_ad2008r2_user_map[]; + +extern struct sdap_attr_map gen_ad2008r2_group_map[]; + +extern struct sdap_attr_map netgroup_map[]; + +extern struct sdap_attr_map host_map[]; + +extern struct sdap_attr_map native_sudorule_map[]; + +extern struct sdap_attr_map service_map[]; + +extern struct sdap_attr_map iphost_map[]; + +extern struct sdap_attr_map ipnetwork_map[]; + +extern struct sdap_attr_map rfc2307_autofs_mobject_map[]; + +extern struct sdap_attr_map rfc2307_autofs_entry_map[]; + +extern struct sdap_attr_map rfc2307bis_autofs_mobject_map[]; + +extern struct sdap_attr_map rfc2307bis_autofs_entry_map[]; + +#endif /* LDAP_OPTS_H_ */ diff --git a/src/providers/ldap/ldap_resolver_cleanup.c b/src/providers/ldap/ldap_resolver_cleanup.c new file mode 100644 index 0000000..67b1c04 --- /dev/null +++ b/src/providers/ldap/ldap_resolver_cleanup.c @@ -0,0 +1,230 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/ldap_common.h" +#include "db/sysdb_iphosts.h" +#include "db/sysdb_ipnetworks.h" + +static errno_t +cleanup_iphosts(struct sdap_options *opts, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *attrs[] = { SYSDB_NAME, NULL }; + time_t now = time(NULL); + char *subfilter; + char *ts_subfilter; + struct ldb_message **msgs; + size_t count; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + subfilter = talloc_asprintf(tmp_ctx, "(!(%s=0))", SYSDB_CACHE_EXPIRE); + if (subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ts_subfilter = talloc_asprintf(tmp_ctx, "(&(!(%s=0))(%s<=%"SPRItime"))", + SYSDB_CACHE_EXPIRE, SYSDB_CACHE_EXPIRE, now); + if (ts_subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_hosts(tmp_ctx, domain, /* subfilter, */ + ts_subfilter, attrs, &count, &msgs); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to search ip hosts [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Found %zu expired ip host entries!\n", count); + + if (count == 0) { + ret = EOK; + goto done; + } + + for (i = 0; i < count; i++) { + const char *name; + + name = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(msgs[i]->dn)); + continue; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "About to delete ip host %s\n", name); + ret = sysdb_host_delete(domain, name, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IP host delete returned [%d]: (%s)\n", + ret, sss_strerror(ret)); + continue; + } + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +cleanup_ipnetworks(struct sdap_options *opts, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *attrs[] = { SYSDB_NAME, NULL }; + time_t now = time(NULL); + char *subfilter; + char *ts_subfilter; + struct ldb_message **msgs; + size_t count; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + subfilter = talloc_asprintf(tmp_ctx, "(!(%s=0))", SYSDB_CACHE_EXPIRE); + if (subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ts_subfilter = talloc_asprintf(tmp_ctx, "(&(!(%s=0))(%s<=%"SPRItime"))", + SYSDB_CACHE_EXPIRE, SYSDB_CACHE_EXPIRE, now); + if (ts_subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_ipnetworks(tmp_ctx, domain, /* subfilter, */ + ts_subfilter, attrs, &count, &msgs); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to search IP networks [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Found %zu expired IP network entries!\n", count); + + if (count == 0) { + ret = EOK; + goto done; + } + + for (i = 0; i < count; i++) { + const char *name; + + name = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(msgs[i]->dn)); + continue; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "About to delete IP network %s\n", name); + ret = sysdb_ipnetwork_delete(domain, name, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IP network delete returned [%d]: (%s)\n", + ret, sss_strerror(ret)); + continue; + } + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ldap_resolver_cleanup(struct sdap_resolver_ctx *ctx) +{ + TALLOC_CTX *tmp_ctx; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + bool in_transaction = false; + errno_t ret, tret; + + tmp_ctx = talloc_new(ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + id_ctx = ctx->id_ctx; + sdom = id_ctx->opts->sdom; + + ret = sysdb_transaction_start(sdom->dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = cleanup_iphosts(id_ctx->opts, sdom->dom); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + ret = cleanup_ipnetworks(id_ctx->opts, sdom->dom); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + ret = sysdb_transaction_commit(sdom->dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ctx->last_purge = tevent_timeval_current(); + ret = EOK; + +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(sdom->dom->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ldap/ldap_resolver_enum.c b/src/providers/ldap/ldap_resolver_enum.c new file mode 100644 index 0000000..3098255 --- /dev/null +++ b/src/providers/ldap/ldap_resolver_enum.c @@ -0,0 +1,299 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/ldap_resolver_enum.h" +#include "providers/ldap/sdap_async_resolver_enum.h" + +static errno_t +ldap_resolver_setup_enumeration(struct be_ctx *be_ctx, + struct sdap_resolver_ctx *resolver_ctx, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn) +{ + errno_t ret; + time_t first_delay; + time_t period; + time_t offset; + time_t cleanup; + bool has_enumerated; + char *name = NULL; + struct sdap_id_ctx *id_ctx = resolver_ctx->id_ctx; + + ret = sysdb_has_enumerated(id_ctx->opts->sdom->dom, + SYSDB_HAS_ENUMERATED_RESOLVER, + &has_enumerated); + if (ret == ENOENT) { + /* default value */ + has_enumerated = false; + } else if (ret != EOK) { + return ret; + } + + if (has_enumerated) { + /* At least one enumeration has previously run, + * so clients will get cached data. We will delay + * starting to enumerate by 10s so we don't slow + * down the startup process if this is happening + * during system boot. + */ + first_delay = 10; + } else { + /* This is our first startup. Schedule the + * enumeration to start immediately once we + * enter the mainloop. + */ + first_delay = 0; + } + + cleanup = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if (cleanup == 0) { + /* We need to cleanup the cache once in a while when enumerating, otherwise + * enumeration would only download deltas since the previous lastUSN and would + * not detect removed entries + */ + ret = dp_opt_set_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT, + LDAP_ENUM_PURGE_TIMEOUT); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot set cleanup timeout, enumeration wouldn't " + "detect removed entries!\n"); + return ret; + } + } + + period = dp_opt_get_int(id_ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + offset = dp_opt_get_int(id_ctx->opts->basic, SDAP_ENUM_REFRESH_OFFSET); + + name = talloc_asprintf(resolver_ctx, "Enumeration [resolver] of %s", + id_ctx->opts->sdom->dom->name); + if (name == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = be_ptask_create(resolver_ctx, be_ctx, + period, /* period */ + first_delay, /* first_delay */ + 5, /* enabled delay */ + offset, /* random offset */ + period, /* timeout */ + 0, /* max_backoff */ + send_fn, recv_fn, + resolver_ctx, name, + BE_PTASK_OFFLINE_SKIP | BE_PTASK_SCHEDULE_FROM_LAST, + &resolver_ctx->task); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to initialize enumeration periodic task\n"); + goto fail; + } + + talloc_free(name); + + return EOK; + +fail: + if (name != NULL) { + talloc_free(name); + } + return ret; +} + +static errno_t +ldap_resolver_cleanup_task(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct sdap_resolver_ctx *resolver_ctx = NULL; + + resolver_ctx = talloc_get_type(pvt, struct sdap_resolver_ctx); + if (resolver_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve sdap_resolver_ctx!\n"); + return EINVAL; + } + + return ldap_resolver_cleanup(resolver_ctx); +} + +static errno_t +ldap_resolver_setup_cleanup(struct sdap_resolver_ctx *resolver_ctx) +{ + errno_t ret; + time_t first_delay; + time_t period; + time_t offset; + char *name = NULL; + struct sdap_id_ctx *id_ctx = resolver_ctx->id_ctx; + + period = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if (period == 0) { + /* Cleanup has been explicitly disabled, so we won't + * create any cleanup tasks. */ + ret = EOK; + goto done; + } + offset = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_OFFSET); + + /* Run the first one in a couple of seconds so that we have time to + * finish initializations first. */ + first_delay = 10; + + name = talloc_asprintf(resolver_ctx, "Cleanup [resolver] of %s", + id_ctx->opts->sdom->dom->name); + if (name == NULL) { + return ENOMEM; + } + + ret = be_ptask_create_sync(resolver_ctx, id_ctx->be, period, first_delay, + 5 /* enabled delay */, offset /* random offset */, + period /* timeout */, 0, + ldap_resolver_cleanup_task, resolver_ctx, name, + BE_PTASK_OFFLINE_SKIP, + &resolver_ctx->task); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to initialize cleanup periodic task for %s\n", + id_ctx->opts->sdom->dom->name); + goto done; + } + + ret = EOK; + +done: + if (name != NULL) { + talloc_free(name); + } + + return ret; +} + +errno_t +ldap_resolver_setup_tasks(struct be_ctx *be_ctx, + struct sdap_resolver_ctx *resolver_ctx, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn) +{ + errno_t ret; + struct sdap_id_ctx *id_ctx = resolver_ctx->id_ctx; + struct sdap_domain *sdom = id_ctx->opts->sdom; + + /* set up enumeration task */ + if (sdom->dom->enumerate) { + DEBUG(SSSDBG_TRACE_FUNC, "Setting up resolver enumeration for %s\n", + sdom->dom->name); + ret = ldap_resolver_setup_enumeration(be_ctx, resolver_ctx, + send_fn, recv_fn); + } else { + /* the enumeration task, runs the cleanup process by itself, + * but if enumeration is not running we need to schedule it */ + DEBUG(SSSDBG_TRACE_FUNC, "Setting up resolver cleanup task for %s\n", + sdom->dom->name); + ret = ldap_resolver_setup_cleanup(resolver_ctx); + } + + return ret; +} + +struct ldap_resolver_enum_state { + struct sdap_resolver_ctx *resolver_ctx; +}; + +static void ldap_resolver_enumeration_done(struct tevent_req *subreq); + +struct tevent_req * +ldap_resolver_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ldap_resolver_enum_state *state; + struct sdap_resolver_ctx *resolver_ctx; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ldap_resolver_enum_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + resolver_ctx = talloc_get_type(pvt, struct sdap_resolver_ctx); + if (resolver_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve sdap_resolver_ctx!\n"); + ret = EFAULT; + goto fail; + } + + state->resolver_ctx = resolver_ctx; + + subreq = sdap_dom_resolver_enum_send(state, ev, state->resolver_ctx, + state->resolver_ctx->id_ctx, + state->resolver_ctx->id_ctx->opts->sdom, + state->resolver_ctx->id_ctx->conn); + if (subreq == NULL) { + /* The ptask API will reschedule the enumeration on its own on + * failure */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to schedule enumeration, retrying later!\n"); + ret = EIO; + goto fail; + } + + tevent_req_set_callback(subreq, ldap_resolver_enumeration_done, req); + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +ldap_resolver_enumeration_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = sdap_dom_resolver_enum_recv(subreq); + + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not enumerate domain\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +ldap_resolver_enumeration_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} diff --git a/src/providers/ldap/ldap_resolver_enum.h b/src/providers/ldap/ldap_resolver_enum.h new file mode 100644 index 0000000..0c8063a --- /dev/null +++ b/src/providers/ldap/ldap_resolver_enum.h @@ -0,0 +1,44 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#ifndef LDAP_RESOLVER_ENUM_H_ +#define LDAP_RESOLVER_ENUM_H_ + +errno_t ldap_resolver_setup_tasks(struct be_ctx *be_ctx, + struct sdap_resolver_ctx *ctx, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn); + +struct tevent_req * +ldap_resolver_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); + +errno_t +ldap_resolver_enumeration_recv(struct tevent_req *req); + +errno_t +ldap_resolver_cleanup(struct sdap_resolver_ctx *ctx); + +#endif /* LDAP_RESOLVER_ENUM_H_ */ diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c new file mode 100644 index 0000000..f5637c5 --- /dev/null +++ b/src/providers/ldap/sdap.c @@ -0,0 +1,2129 @@ +/* + SSSD + + LDAP Helper routines + + Copyright (C) Simo Sorce + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "confdb/confdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_range.h" +#include "util/probes.h" + +/* =Retrieve-Options====================================================== */ + +errno_t sdap_copy_map_entry(const struct sdap_attr_map *src_map, + struct sdap_attr_map *dst_map, + int entry_index) +{ + if (src_map[entry_index].name != NULL) { + dst_map[entry_index].name = talloc_strdup(dst_map, + src_map[entry_index].name); + if (dst_map[entry_index].name == NULL) { + return ENOMEM; + } + } else { + dst_map->name = NULL; + } + + return EOK; +} + +int sdap_copy_map(TALLOC_CTX *memctx, + struct sdap_attr_map *src_map, + int num_entries, + struct sdap_attr_map **_map) +{ + struct sdap_attr_map *map; + int i; + + map = talloc_array(memctx, struct sdap_attr_map, num_entries + 1); + if (!map) { + return ENOMEM; + } + + for (i = 0; i < num_entries; i++) { + map[i].opt_name = talloc_strdup(map, src_map[i].opt_name); + map[i].sys_name = talloc_strdup(map, src_map[i].sys_name); + if (map[i].opt_name == NULL || map[i].sys_name == NULL) { + return ENOMEM; + } + + if (src_map[i].def_name != NULL) { + map[i].def_name = talloc_strdup(map, src_map[i].def_name); + map[i].name = talloc_strdup(map, src_map[i].def_name); + if (map[i].def_name == NULL || map[i].name == NULL) { + return ENOMEM; + } + } else { + map[i].def_name = NULL; + map[i].name = NULL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s has%s value %s\n", + map[i].opt_name, map[i].name ? "" : " no", + map[i].name ? map[i].name : ""); + } + + /* Include the sentinel */ + memset(&map[num_entries], 0, sizeof(struct sdap_attr_map)); + + *_map = map; + return EOK; +} + +static errno_t split_extra_attr(TALLOC_CTX *mem_ctx, + const char *conf_attr, + char **_sysdb_attr, + char **_ldap_attr) +{ + char *ldap_attr; + char *sysdb_attr; + char *sep; + + sep = strchr(conf_attr, ':'); + if (sep == NULL) { + sysdb_attr = talloc_strdup(mem_ctx, conf_attr); + ldap_attr = talloc_strdup(mem_ctx, conf_attr); + } else { + if (sep == conf_attr || *(sep + 1) == '\0') { + return ERR_INVALID_EXTRA_ATTR; + } + + sysdb_attr = talloc_strndup(mem_ctx, conf_attr, + sep - conf_attr); + ldap_attr = talloc_strdup(mem_ctx, sep+1); + } + + if (sysdb_attr == NULL || ldap_attr == NULL) { + return ENOMEM; + } + + *_sysdb_attr = sysdb_attr; + *_ldap_attr = ldap_attr; + return EOK; +} + +enum duplicate_t { + NOT_FOUND = 0, + ALREADY_IN_MAP, /* nothing to add */ + CONFLICT_WITH_MAP /* attempt to redefine attribute */ +}; + +static enum duplicate_t check_duplicate(struct sdap_attr_map *map, + int num_entries, + const char *sysdb_attr, + const char *ldap_attr) +{ + int i; + + for (i = 0; i < num_entries; i++) { + if (strcmp(map[i].sys_name, sysdb_attr) == 0) { + if (map[i].name != NULL && strcmp(map[i].name, ldap_attr) == 0) { + return ALREADY_IN_MAP; + } else { + return CONFLICT_WITH_MAP; + } + } + } + + return NOT_FOUND; +} + +int sdap_extend_map(TALLOC_CTX *memctx, + struct sdap_attr_map *src_map, + size_t num_entries, + char **extra_attrs, + struct sdap_attr_map **_map, + size_t *_new_size) +{ + struct sdap_attr_map *map; + size_t nextra = 0; + size_t i; + char *ldap_attr; + char *sysdb_attr; + errno_t ret; + + *_map = src_map; + if (extra_attrs == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "No extra attributes\n"); + *_new_size = num_entries; + return EOK; + } + + for (nextra = 0; extra_attrs[nextra]; nextra++) ; + DEBUG(SSSDBG_FUNC_DATA, "%zu extra attributes\n", nextra); + + map = talloc_realloc(memctx, src_map, struct sdap_attr_map, + num_entries + nextra + 1); + if (map == NULL) { + return ENOMEM; + } + *_map = map; + + for (i = 0; *extra_attrs != NULL; extra_attrs++) { + ret = split_extra_attr(map, *extra_attrs, &sysdb_attr, &ldap_attr); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot split %s\n", *extra_attrs); + continue; + } + + ret = check_duplicate(map, num_entries, sysdb_attr, ldap_attr); + if (ret == ALREADY_IN_MAP) { + DEBUG(SSSDBG_TRACE_FUNC, + "Attribute %s (%s in LDAP) is already in map.\n", + sysdb_attr, ldap_attr); + continue; + } else if (ret == CONFLICT_WITH_MAP) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Attribute %s (%s in LDAP) is already used by SSSD, please " + "choose a different cache name\n", sysdb_attr, ldap_attr); + return ERR_DUP_EXTRA_ATTR; + } + + map[num_entries+i].name = ldap_attr; + map[num_entries+i].sys_name = sysdb_attr; + map[num_entries+i].opt_name = talloc_strdup(map, + map[num_entries+i].name); + map[num_entries+i].def_name = talloc_strdup(map, + map[num_entries+i].name); + if (map[num_entries+i].opt_name == NULL || + map[num_entries+i].sys_name == NULL || + map[num_entries+i].name == NULL || + map[num_entries+i].def_name == NULL) { + return ENOMEM; + } + DEBUG(SSSDBG_TRACE_FUNC, "Extending map with %s\n", *extra_attrs); + + /* index must be incremented only for appended entry. */ + i++; + } + + nextra = i; + + /* Sentinel */ + memset(&map[num_entries+nextra], 0, sizeof(struct sdap_attr_map)); + + *_new_size = num_entries + nextra; + return EOK; +} + +int sdap_extend_map_with_list(TALLOC_CTX *mem_ctx, + const struct sdap_options *opts, + int extra_attr_index, + struct sdap_attr_map *src_map, + size_t num_entries, + struct sdap_attr_map **_map, + size_t *_new_size) +{ + const char *extra_attrs; + char **extra_attrs_list; + errno_t ret; + + *_map = src_map; + extra_attrs = dp_opt_get_string(opts->basic, extra_attr_index); + if (extra_attrs == NULL) { + *_new_size = num_entries; + return EOK; + } + + /* split server parm into a list */ + ret = split_on_separator(mem_ctx, extra_attrs, ',', true, true, + &extra_attrs_list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to parse server list!\n"); + return ret; + } + + + ret = sdap_extend_map(mem_ctx, src_map, + num_entries, extra_attrs_list, + _map, _new_size); + talloc_free(extra_attrs_list); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static void sdap_inherit_basic_options(char **inherit_opt_list, + struct dp_option *parent_opts, + struct dp_option *subdom_opts) +{ + int inherit_options[] = { + SDAP_SEARCH_TIMEOUT, + SDAP_NETWORK_TIMEOUT, + SDAP_OPT_TIMEOUT, + SDAP_OFFLINE_TIMEOUT, + SDAP_ENUM_REFRESH_TIMEOUT, + SDAP_ENUM_REFRESH_OFFSET, + SDAP_PURGE_CACHE_TIMEOUT, + SDAP_PURGE_CACHE_OFFSET, + SDAP_KRB5_KEYTAB, + SDAP_KRB5_TICKET_LIFETIME, + SDAP_ENUM_SEARCH_TIMEOUT, + SDAP_EXPIRE_TIMEOUT, + SDAP_EXPIRE_OFFSET, + SDAP_IDLE_TIMEOUT, + SDAP_AD_USE_TOKENGROUPS, + SDAP_OPTS_BASIC /* sentinel */ + }; + int i; + + for (i = 0; inherit_options[i] != SDAP_OPTS_BASIC; i++) { + dp_option_inherit_match(inherit_opt_list, + inherit_options[i], + parent_opts, + subdom_opts); + } +} + +static void sdap_inherit_user_options(char **inherit_opt_list, + struct sdap_attr_map *parent_user_map, + struct sdap_attr_map *child_user_map) +{ + int inherit_options[] = { + SDAP_AT_USER_PRINC, + SDAP_OPTS_USER /* sentinel */ + }; + int i; + int opt_index; + bool inherit_option; + + for (i = 0; inherit_options[i] != SDAP_OPTS_USER; i++) { + opt_index = inherit_options[i]; + + inherit_option = string_in_list(parent_user_map[opt_index].opt_name, + inherit_opt_list, + false); + if (inherit_option == false) { + continue; + } + + sdap_copy_map_entry(parent_user_map, child_user_map, opt_index); + } +} + +void sdap_inherit_options(char **inherit_opt_list, + struct sdap_options *parent_sdap_opts, + struct sdap_options *child_sdap_opts) +{ + sdap_inherit_basic_options(inherit_opt_list, + parent_sdap_opts->basic, + child_sdap_opts->basic); + + sdap_inherit_user_options(inherit_opt_list, + parent_sdap_opts->user_map, + child_sdap_opts->user_map); +} + +int sdap_get_map(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_attr_map *def_map, + int num_entries, + struct sdap_attr_map **_map) +{ + struct sdap_attr_map *map; + char *name; + int i, ret; + + map = talloc_zero_array(memctx, struct sdap_attr_map, num_entries + 1); + if (!map) { + return ENOMEM; + } + + for (i = 0; i < num_entries; i++) { + + map[i].opt_name = def_map[i].opt_name; + map[i].def_name = def_map[i].def_name; + map[i].sys_name = def_map[i].sys_name; + + ret = confdb_get_string(cdb, map, conf_path, + map[i].opt_name, + map[i].def_name, + &name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to retrieve value for %s\n", map[i].opt_name); + talloc_zfree(map); + return EINVAL; + } + + if (name) { + ret = sss_filter_sanitize(map, name, &map[i].name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not sanitize attribute [%s]\n", name); + talloc_zfree(map); + return EINVAL; + } + talloc_zfree(name); + } else { + map[i].name = NULL; + } + + if (map[i].def_name && !map[i].name) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to process value for %s\n", map[i].opt_name); + talloc_zfree(map); + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s has%s value %s\n", + map[i].opt_name, map[i].name ? "" : " no", + map[i].name ? map[i].name : ""); + } + + *_map = map; + return EOK; +} + +/* =Parse-msg============================================================= */ + +static bool objectclass_matched(struct sdap_attr_map *map, + const char *objcl, int len); +int sdap_parse_entry(TALLOC_CTX *memctx, + struct sdap_handle *sh, struct sdap_msg *sm, + struct sdap_attr_map *map, int attrs_num, + struct sysdb_attrs **_attrs, + bool disable_range_retrieval) +{ + struct sysdb_attrs *attrs; + BerElement *ber = NULL; + struct berval **vals; + struct ldb_val v; + char *str; + int lerrno; + int i, ret, ai; + int base_attr_idx = 0; + const char *name = NULL; + bool store; + bool base64; + char *base_attr; + uint32_t range_offset; + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + lerrno = 0; + ret = ldap_set_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_set_option failed [%s], ignored.\n", + sss_ldap_err2string(ret)); + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + str = ldap_get_dn(sh->ldap, sm->msg); + if (!str) { + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_get_dn failed: %d(%s)\n", + lerrno, sss_ldap_err2string(lerrno)); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "OriginalDN: [%s].\n", str); + PROBE(SDAP_PARSE_ENTRY, "OriginalDN", str, strlen(str)); + ret = sysdb_attrs_add_string(attrs, SYSDB_ORIG_DN, str); + ldap_memfree(str); + if (ret) goto done; + + if (map) { + vals = ldap_get_values_len(sh->ldap, sm->msg, "objectClass"); + if (!vals) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown entry type, no objectClasses found!\n"); + ret = EINVAL; + goto done; + } + + for (i = 0; vals[i]; i++) { + if (objectclass_matched(map, vals[i]->bv_val, vals[i]->bv_len)) { + /* ok it's an entry of the right type */ + break; + } + } + if (!vals[i]) { + DEBUG(SSSDBG_CRIT_FAILURE, "objectClass not matching: %s\n", + map[0].name); + ldap_value_free_len(vals); + ret = EINVAL; + goto done; + } + ldap_value_free_len(vals); + } + + str = ldap_first_attribute(sh->ldap, sm->msg, &ber); + if (!str) { + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + DEBUG(lerrno == LDAP_SUCCESS + ? SSSDBG_TRACE_LIBS + : SSSDBG_MINOR_FAILURE, + "Entry has no attributes [%d(%s)]!?\n", + lerrno, sss_ldap_err2string(lerrno)); + if (map) { + ret = EINVAL; + goto done; + } + } + while (str) { + base64 = false; + + ret = sdap_parse_range(tmp_ctx, str, &base_attr, &range_offset, + disable_range_retrieval); + switch(ret) { + case EAGAIN: + /* This attribute contained range values and needs more to + * be retrieved + */ + /* TODO: return the set of attributes that need additional retrieval + * For now, we'll continue below and treat it as regular values. + */ + /* FALLTHROUGH */ + case ECANCELED: + /* FALLTHROUGH */ + case EOK: + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not determine if attribute [%s] was ranged\n", str); + goto done; + } + + if (ret == ECANCELED) { + store = false; + } else if (map) { + for (i = 1; i < attrs_num; i++) { + /* check if this attr is valid with the chosen schema */ + if (!map[i].name) continue; + /* check if it is an attr we are interested in */ + if (strcasecmp(base_attr, map[i].name) == 0) break; + } + /* interesting attr */ + if (i < attrs_num) { + store = true; + name = map[i].sys_name; + base_attr_idx = i; + if (strcmp(name, SYSDB_SSH_PUBKEY) == 0) { + base64 = true; + } + } else { + store = false; + } + } else { + name = base_attr; + store = true; + } + + if (store) { + vals = ldap_get_values_len(sh->ldap, sm->msg, str); + if (!vals) { + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + if (lerrno != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_get_values_len() failed: %d(%s)\n", + lerrno, sss_ldap_err2string(lerrno)); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Attribute [%s] has no values, skipping.\n", str); + + } else { + if (!vals[0]) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing value after ldap_get_values() ??\n"); + ldap_value_free_len(vals); + ret = EINVAL; + goto done; + } + for (i = 0; vals[i]; i++) { + if (vals[i]->bv_len == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Value of attribute [%s] is empty. " + "Skipping this value.\n", str); + continue; + } + if (base64) { + v.data = (uint8_t *) sss_base64_encode(attrs, + (uint8_t *) vals[i]->bv_val, vals[i]->bv_len); + if (!v.data) { + ldap_value_free_len(vals); + ret = ENOMEM; + goto done; + } + v.length = strlen((const char *)v.data); + } else { + v.data = (uint8_t *)vals[i]->bv_val; + v.length = vals[i]->bv_len; + } + PROBE(SDAP_PARSE_ENTRY, str, v.data, v.length); + + if (map) { + /* The same LDAP attr might be used for more sysdb + * attrs in case there is a map. Find all that match + * and copy the value + */ + for (ai = base_attr_idx; ai < attrs_num; ai++) { + /* check if this attr is valid with the chosen + * schema */ + if (!map[ai].name) continue; + + /* check if it is an attr we are interested in */ + if (strcasecmp(base_attr, map[ai].name) == 0) { + ret = sysdb_attrs_add_val(attrs, + map[ai].sys_name, + &v); + if (ret) { + ldap_value_free_len(vals); + goto done; + } + } + } + } else { + /* No map, just store the attribute */ + ret = sysdb_attrs_add_val(attrs, name, &v); + if (ret) { + ldap_value_free_len(vals); + goto done; + } + } + } + ldap_value_free_len(vals); + } + } + + ldap_memfree(str); + str = ldap_next_attribute(sh->ldap, sm->msg, ber); + } + ber_free(ber, 0); + ber = NULL; + + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + if (lerrno) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_get_option() failed: %d(%s)\n", + lerrno, sss_ldap_err2string(lerrno)); + ret = EIO; + goto done; + } + + PROBE(SDAP_PARSE_ENTRY_DONE); + *_attrs = talloc_steal(memctx, attrs); + ret = EOK; + +done: + if (ber) ber_free(ber, 0); + talloc_free(tmp_ctx); + return ret; +} + +static bool objectclass_matched(struct sdap_attr_map *map, + const char *objcl, int len) +{ + if (len == 0) { + len = strlen(objcl) + 1; + } + + if (strncasecmp(map[SDAP_OC_GROUP].name, objcl, len) == 0) { + return true; + } + + if (map[SDAP_OC_GROUP_ALT].name != NULL + && strncasecmp(map[SDAP_OC_GROUP_ALT].name, objcl, len) == 0) { + return true; + } + + return false; +} + +/* Parses an LDAPDerefRes into sdap_deref_attrs structure */ +errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx, + struct sdap_attr_map_info *minfo, + size_t num_maps, + LDAPDerefRes *dref, + struct sdap_deref_attrs ***_deref_res) +{ + TALLOC_CTX *tmp_ctx; + LDAPDerefVal *dval; + const char *orig_dn; + const char **ocs; + struct sdap_attr_map *map; + int num_attrs = 0; + int ret, i, a, mi; + const char *name; + size_t len; + struct sdap_deref_attrs **res; + + if (!dref || !minfo) return EINVAL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + res = talloc_array(tmp_ctx, struct sdap_deref_attrs *, num_maps); + if (!res) { + ret = ENOMEM; + goto done; + } + + for (i=0; i < num_maps; i++) { + res[i] = talloc_zero(res, struct sdap_deref_attrs); + if (!res[i]) { + ret = ENOMEM; + goto done; + } + + res[i]->map = minfo[i].map; + } + + if (!dref->derefVal.bv_val) { + DEBUG(SSSDBG_OP_FAILURE, "Entry has no DN?\n"); + ret = EINVAL; + goto done; + } + + orig_dn = dref->derefVal.bv_val; + DEBUG(SSSDBG_TRACE_LIBS, + "Dereferenced DN: %s\n", orig_dn); + + if (!dref->attrVals) { + DEBUG(SSSDBG_FUNC_DATA, + "Dereferenced entry [%s] has no attributes, skipping\n", + orig_dn); + *_deref_res = NULL; + ret = EOK; + goto done; + } + + ocs = NULL; + for (dval = dref->attrVals; dval != NULL; dval = dval->next) { + if (strcasecmp("objectClass", dval->type) == 0) { + if (dval->vals == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No value for objectClass, skipping\n"); + continue; + } + + for(len=0; dval->vals[len].bv_val; len++); + + ocs = talloc_array(tmp_ctx, const char *, len+1); + if (!ocs) { + ret = ENOMEM; + goto done; + } + + for (i=0; ivals[i].bv_val); + ocs[i] = talloc_strdup(ocs, dval->vals[i].bv_val); + if (!ocs[i]) { + ret = ENOMEM; + goto done; + } + } + ocs[i] = NULL; + break; + } + } + if (!ocs) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown entry type, no objectClasses found!\n"); + ret = EINVAL; + goto done; + } + + for (mi = 0; mi < num_maps; mi++) { + map = NULL; + + for (i=0; ocs[i]; i++) { + /* the objectclass is always the first name in the map */ + if (objectclass_matched(minfo[mi].map, ocs[i], 0)) { + DEBUG(SSSDBG_TRACE_ALL, + "Found map for objectclass '%s'\n", ocs[i]); + map = minfo[mi].map; + num_attrs = minfo[mi].num_attrs; + break; + } + } + if (!map) continue; + + res[mi]->attrs = sysdb_new_attrs(res[mi]); + if (!res[mi]->attrs) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(res[mi]->attrs, SYSDB_ORIG_DN, + orig_dn); + if (ret) { + goto done; + } + + /* The dereference control seems to return the DN from the dereference + * attribute (e.g. member) so we can use it as key for the hash table + * later. */ + ret = sysdb_attrs_add_string(res[mi]->attrs, + SYSDB_DN_FOR_MEMBER_HASH_TABLE, orig_dn); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string failed.\n"); + goto done; + } + + for (dval = dref->attrVals; dval != NULL; dval = dval->next) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Dereferenced attribute: %s\n", dval->type); + + for (a = 1; a < num_attrs; a++) { + /* check if this attr is valid with the chosen schema */ + if (!map[a].name) continue; + /* check if it is an attr we are interested in */ + if (strcasecmp(dval->type, map[a].name) == 0) break; + } + + /* interesting attr */ + if (a < num_attrs) { + name = map[a].sys_name; + } else { + continue; + } + + if (dval->vals == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No value for attribute %s, skipping\n", name); + continue; + } + + for (i=0; dval->vals[i].bv_val; i++) { + DEBUG(SSSDBG_TRACE_ALL, "Dereferenced attribute value: %s\n", + dval->vals[i].bv_val); + ret = sysdb_attrs_add_mem(res[mi]->attrs, name, + dval->vals[i].bv_val, + dval->vals[i].bv_len); + if (ret) goto done; + } + } + } + + + *_deref_res = talloc_steal(mem_ctx, res); + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + +static void sss_ldap_debug(const char *buf) +{ + sss_debug_fn(__FILE__, __LINE__, __FUNCTION__, SSSDBG_TRACE_ALL, + "libldap: %s", buf); +} + +void setup_ldap_debug(struct dp_option *basic_opts) +{ + int ret; + int ldap_debug_level; + + ldap_debug_level = dp_opt_get_int(basic_opts, SDAP_LIBRARY_DEBUG_LEVEL); + if (ldap_debug_level == 0) { + return; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Setting LDAP library debug level [%d].\n", + ldap_debug_level); + + ret = ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &ldap_debug_level); + if (ret != LBER_OPT_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to set LBER_OPT_DEBUG_LEVEL, ignored .\n"); + } + + ret = ber_set_option(NULL, LBER_OPT_LOG_PRINT_FN, sss_ldap_debug); + if (ret != LBER_OPT_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to set LBER_OPT_LOG_PRINT_FN, ignored .\n"); + } + + ret = ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &ldap_debug_level); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to set LDAP_OPT_DEBUG_LEVEL, ignored .\n"); + } +} + +errno_t setup_tls_config(struct dp_option *basic_opts) +{ + int ret; + int ldap_opt_x_tls_require_cert; + const char *tls_opt; + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_REQCERT); + if (tls_opt) { + if (strcasecmp(tls_opt, "never") == 0) { + ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_NEVER; + } + else if (strcasecmp(tls_opt, "allow") == 0) { + ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_ALLOW; + } + else if (strcasecmp(tls_opt, "try") == 0) { + ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_TRY; + } + else if (strcasecmp(tls_opt, "demand") == 0) { + ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_DEMAND; + } + else if (strcasecmp(tls_opt, "hard") == 0) { + ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_HARD; + } + else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown value for tls_reqcert '%s'.\n", tls_opt); + return EINVAL; + } + /* LDAP_OPT_X_TLS_REQUIRE_CERT has to be set as a global option, + * because the SSL/TLS context is initialized from this value. */ + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, + &ldap_opt_x_tls_require_cert); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(req_cert) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CACERT); + if (tls_opt) { + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, tls_opt); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(cacertfile) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CACERTDIR); + if (tls_opt) { + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTDIR, tls_opt); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(cacertdir) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CERT); + if (tls_opt) { + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CERTFILE, tls_opt); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(certfile) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_KEY); + if (tls_opt) { + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_KEYFILE, tls_opt); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(keyfile) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CIPHER_SUITE); + if (tls_opt) { + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CIPHER_SUITE, tls_opt); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(cipher) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + return EOK; +} + +bool sdap_sasl_mech_needs_kinit(const char *sasl_mech) +{ + if (strcasecmp(sasl_mech, "GSSAPI") == 0 + || strcasecmp(sasl_mech, "GSS-SPNEGO") == 0) { + return true; + } + + return false; +} + +bool sdap_check_sup_list(struct sup_list *l, const char *val) +{ + int i; + + if (!val) { + return false; + } + + for (i = 0; i < l->num_vals; i++) { + if (strcasecmp(val, (char *)l->vals[i])) { + continue; + } + return true; + } + + return false; +} + +static int sdap_init_sup_list(TALLOC_CTX *memctx, + struct sup_list *list, + int num, struct ldb_val *vals) +{ + int i; + + list->vals = talloc_array(memctx, char *, num); + if (!list->vals) { + return ENOMEM; + } + + for (i = 0; i < num; i++) { + list->vals[i] = talloc_strndup(list->vals, + (char *)vals[i].data, vals[i].length); + if (!list->vals[i]) { + return ENOMEM; + } + } + + list->num_vals = num; + + return EOK; +} + +int sdap_set_rootdse_supported_lists(struct sysdb_attrs *rootdse, + struct sdap_handle *sh) +{ + struct ldb_message_element *el = NULL; + int ret; + int i; + + for (i = 0; i < rootdse->num; i++) { + el = &rootdse->a[i]; + if (strcasecmp(el->name, "supportedControl") == 0) { + + ret = sdap_init_sup_list(sh, &sh->supported_controls, + el->num_values, el->values); + if (ret) { + return ret; + } + } else if (strcasecmp(el->name, "supportedExtension") == 0) { + + ret = sdap_init_sup_list(sh, &sh->supported_extensions, + el->num_values, el->values); + if (ret) { + return ret; + } + } else if (strcasecmp(el->name, "supportedSASLMechanisms") == 0) { + + ret = sdap_init_sup_list(sh, &sh->supported_saslmechs, + el->num_values, el->values); + if (ret) { + return ret; + } + } + } + + return EOK; + +} + +static char *get_single_value_as_string(TALLOC_CTX *mem_ctx, + struct ldb_message_element *el) +{ + char *str = NULL; + + if (el->num_values == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "Missing value.\n"); + } else if (el->num_values == 1) { + str = talloc_strndup(mem_ctx, (char *) el->values[0].data, + el->values[0].length); + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "More than one value found.\n"); + } + + return str; +} + +static char *get_naming_context(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *rootdse) +{ + struct ldb_message_element *nc = NULL; + struct ldb_message_element *dnc = NULL; + int i; + char *naming_context = NULL; + + for (i = 0; i < rootdse->num; i++) { + if (strcasecmp(rootdse->a[i].name, + SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS) == 0) { + nc = &rootdse->a[i]; + } else if (strcasecmp(rootdse->a[i].name, + SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT) == 0) { + dnc = &rootdse->a[i]; + } + } + + if (dnc == NULL && nc == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No attributes [%s] or [%s] found in rootDSE.\n", + SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS, + SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT); + } else { + if (dnc != NULL) { + DEBUG(SSSDBG_FUNC_DATA, + "Using value from [%s] as naming context.\n", + SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT); + naming_context = get_single_value_as_string(mem_ctx, dnc); + } + + if (naming_context == NULL && nc != NULL) { + DEBUG(SSSDBG_FUNC_DATA, + "Using value from [%s] as naming context.\n", + SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS); + naming_context = get_single_value_as_string(mem_ctx, nc); + } + } + + /* Some directory servers such as Novell eDirectory will return + * a zero-length namingContexts value in some situations. In this + * case, we should return it as NULL so things fail gracefully. + */ + if (naming_context && naming_context[0] == '\0') { + talloc_zfree(naming_context); + } + + return naming_context; +} + +errno_t +sdap_create_search_base(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *unparsed_base, + int scope, + const char *filter, + struct sdap_search_base **_base) +{ + struct sdap_search_base *base; + TALLOC_CTX *tmp_ctx; + errno_t ret; + struct ldb_dn *ldn; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + base = talloc_zero(tmp_ctx, struct sdap_search_base); + if (base == NULL) { + ret = ENOMEM; + goto done; + } + + base->basedn = talloc_strdup(base, unparsed_base); + if (base->basedn == NULL) { + ret = ENOMEM; + goto done; + } + + /* Validate the basedn */ + ldn = ldb_dn_new(base, ldb, unparsed_base); + if (!ldn) { + ret = ENOMEM; + goto done; + } + + if (!ldb_dn_validate(ldn)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid base DN [%s]\n", unparsed_base); + ret = EINVAL; + goto done; + } + + base->ldb = ldb; + base->ldb_basedn = ldn; + base->scope = scope; + base->filter = filter; + + *_base = talloc_steal(mem_ctx, base); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sdap_set_search_base(struct sdap_options *opts, + struct sdap_domain *sdom, + enum sdap_basic_opt class, + char *naming_context) +{ + errno_t ret; + struct sdap_search_base ***bases; + + switch(class) { + case SDAP_SEARCH_BASE: + bases = &sdom->search_bases; + break; + case SDAP_USER_SEARCH_BASE: + bases = &sdom->user_search_bases; + break; + case SDAP_GROUP_SEARCH_BASE: + bases = &sdom->group_search_bases; + break; + case SDAP_NETGROUP_SEARCH_BASE: + bases = &sdom->netgroup_search_bases; + break; + case SDAP_HOST_SEARCH_BASE: + bases = &sdom->host_search_bases; + break; + case SDAP_SUDO_SEARCH_BASE: + bases = &sdom->sudo_search_bases; + break; + case SDAP_SERVICE_SEARCH_BASE: + bases = &sdom->service_search_bases; + break; + case SDAP_AUTOFS_SEARCH_BASE: + bases = &sdom->autofs_search_bases; + break; + case SDAP_IPHOST_SEARCH_BASE: + bases = &sdom->iphost_search_bases; + break; + case SDAP_IPNETWORK_SEARCH_BASE: + bases = &sdom->ipnetwork_search_bases; + break; + default: + return EINVAL; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Setting option [%s] to [%s].\n", + opts->basic[class].opt_name, naming_context); + + ret = dp_opt_set_string(opts->basic, class, naming_context); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + + ret = sdap_parse_search_base(opts, sysdb_ctx_get_ldb(sdom->dom->sysdb), + opts->basic, class, bases); + if (ret != EOK) goto done; + + ret = EOK; +done: + return ret; +} + +errno_t sdap_set_config_options_with_rootdse(struct sysdb_attrs *rootdse, + struct sdap_options *opts, + struct sdap_domain *sdom) +{ + int ret; + char *naming_context = NULL; + + if (!sdom->search_bases + || !sdom->user_search_bases + || !sdom->group_search_bases + || !sdom->netgroup_search_bases + || !sdom->host_search_bases + || !sdom->sudo_search_bases + || !sdom->iphost_search_bases + || !sdom->ipnetwork_search_bases + || !sdom->autofs_search_bases) { + naming_context = get_naming_context(opts->basic, rootdse); + if (naming_context == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "get_naming_context failed.\n"); + + /* This has to be non-fatal, since some servers offer + * multiple namingContexts entries. We will just + * add NULL checks for the search bases in the lookups. + */ + ret = EOK; + goto done; + } + } + + /* Default */ + if (!sdom->search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Users */ + if (!sdom->user_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_USER_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Groups */ + if (!sdom->group_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_GROUP_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Netgroups */ + if (!sdom->netgroup_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_NETGROUP_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Hosts */ + if (!sdom->host_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_HOST_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Sudo */ + if (!sdom->sudo_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_SUDO_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Services */ + if (!sdom->service_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_SERVICE_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* autofs */ + if (!sdom->autofs_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_AUTOFS_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* IP host */ + if (!sdom->iphost_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_IPHOST_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* IP network */ + if (!sdom->ipnetwork_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_IPNETWORK_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + ret = EOK; + +done: + talloc_free(naming_context); + return ret; +} + +int sdap_get_server_opts_from_rootdse(TALLOC_CTX *memctx, + const char *server, + struct sysdb_attrs *rootdse, + struct sdap_options *opts, + struct sdap_server_opts **srv_opts) +{ + struct sdap_server_opts *so; + struct { + const char *last_name; + const char *entry_name; + } usn_attrs[] = { { SDAP_IPA_LAST_USN, SDAP_IPA_USN }, + { SDAP_AD_LAST_USN, SDAP_AD_USN }, + { NULL, NULL } }; + const char *last_usn_name; + const char *last_usn_value; + const char *entry_usn_name; + const char *schema_nc = NULL; + char *endptr = NULL; + int ret; + int i; + uint32_t dc_level; + + so = talloc_zero(memctx, struct sdap_server_opts); + if (!so) { + return ENOMEM; + } + so->server_id = talloc_strdup(so, server); + if (!so->server_id) { + talloc_zfree(so); + return ENOMEM; + } + + last_usn_name = opts->gen_map[SDAP_AT_LAST_USN].name; + entry_usn_name = opts->gen_map[SDAP_AT_ENTRY_USN].name; + if (rootdse) { + if (last_usn_name) { + ret = sysdb_attrs_get_string(rootdse, + last_usn_name, &last_usn_value); + if (ret != EOK) { + switch (ret) { + case ENOENT: + DEBUG(SSSDBG_CRIT_FAILURE, + "%s configured but not found in rootdse!\n", + opts->gen_map[SDAP_AT_LAST_USN].opt_name); + break; + case ERANGE: + DEBUG(SSSDBG_CRIT_FAILURE, + "Multiple values of %s found in rootdse!\n", + opts->gen_map[SDAP_AT_LAST_USN].opt_name); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown error (%d) checking rootdse!\n", ret); + } + } else { + if (!entry_usn_name) { + DEBUG(SSSDBG_CRIT_FAILURE, + "%s found in rootdse but %s is not set!\n", + last_usn_name, + opts->gen_map[SDAP_AT_ENTRY_USN].opt_name); + } else { + so->supports_usn = true; + errno = 0; + so->last_usn = strtoul(last_usn_value, &endptr, 10); + if (errno || !endptr || *endptr || (endptr == last_usn_value)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "USN is not valid (value: %s)\n", last_usn_value); + so->last_usn = 0; + } else { + DEBUG(SSSDBG_TRACE_ALL, + "USN value: %s (int: %lu)\n", last_usn_value, so->last_usn); + } + } + } + } else { + /* no usn option configure, let's try to autodetect. */ + for (i = 0; usn_attrs[i].last_name; i++) { + ret = sysdb_attrs_get_string(rootdse, + usn_attrs[i].last_name, + &last_usn_value); + if (ret == EOK) { + /* Fixate discovered configuration */ + opts->gen_map[SDAP_AT_LAST_USN].name = + talloc_strdup(opts->gen_map, usn_attrs[i].last_name); + opts->gen_map[SDAP_AT_ENTRY_USN].name = + talloc_strdup(opts->gen_map, usn_attrs[i].entry_name); + so->supports_usn = true; + errno = 0; + so->last_usn = strtoul(last_usn_value, &endptr, 10); + if (errno || !endptr || *endptr || (endptr == last_usn_value)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "USN is not valid (value: %s)\n", last_usn_value); + so->last_usn = 0; + } else { + DEBUG(SSSDBG_TRACE_ALL, + "USN value: %s (int: %lu)\n", last_usn_value, so->last_usn); + } + last_usn_name = usn_attrs[i].last_name; + break; + } + } + } + + /* Detect Active Directory version if available */ + ret = sysdb_attrs_get_uint32_t(rootdse, + SDAP_ROOTDSE_ATTR_AD_VERSION, + &dc_level); + if (ret == EOK) { + /* Validate that the DC level matches an expected value */ + switch(dc_level) { + case DS_BEHAVIOR_WIN2000: + case DS_BEHAVIOR_WIN2003: + case DS_BEHAVIOR_WIN2008: + case DS_BEHAVIOR_WIN2008R2: + case DS_BEHAVIOR_WIN2012: + case DS_BEHAVIOR_WIN2012R2: + case DS_BEHAVIOR_WIN2016: + opts->dc_functional_level = dc_level; + DEBUG(SSSDBG_CONF_SETTINGS, + "Setting AD compatibility level to [%d]\n", + opts->dc_functional_level); + break; + default: + DEBUG(SSSDBG_MINOR_FAILURE, + "Received invalid value [%d] for AD compatibility level. " + "Using the lowest-common compatibility level\n", + dc_level); + opts->dc_functional_level = DS_BEHAVIOR_WIN2003; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error detecting Active Directory compatibility level " + "(%s). Continuing without AD performance enhancements\n", + strerror(ret)); + } + + ret = sysdb_attrs_get_string(rootdse, + SDAP_ROOTDSE_ATTR_AD_SCHEMA_NC, + &schema_nc); + if (ret == EOK) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Will look for schema at [%s]\n", schema_nc); + opts->schema_basedn = talloc_strdup(opts, schema_nc); + } + } + + if (!last_usn_name) { + DEBUG(SSSDBG_FUNC_DATA, + "No known USN scheme is supported by this server!\n"); + if (!entry_usn_name) { + DEBUG(SSSDBG_FUNC_DATA, + "Will use modification timestamp as usn!\n"); + opts->gen_map[SDAP_AT_ENTRY_USN].name = + talloc_strdup(opts->gen_map, "modifyTimestamp"); + } + } + + if (!opts->user_map[SDAP_AT_USER_USN].name) { + opts->user_map[SDAP_AT_USER_USN].name = + talloc_strdup(opts->user_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + if (!opts->group_map[SDAP_AT_GROUP_USN].name) { + opts->group_map[SDAP_AT_GROUP_USN].name = + talloc_strdup(opts->group_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + if (!opts->service_map[SDAP_AT_SERVICE_USN].name) { + opts->service_map[SDAP_AT_SERVICE_USN].name = + talloc_strdup(opts->service_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + if (opts->sudorule_map && + !opts->sudorule_map[SDAP_AT_SUDO_USN].name) { + opts->sudorule_map[SDAP_AT_SUDO_USN].name = + talloc_strdup(opts->sudorule_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + if (opts->iphost_map && + !opts->iphost_map[SDAP_AT_IPHOST_USN].name) { + opts->iphost_map[SDAP_AT_IPHOST_USN].name = + talloc_strdup(opts->iphost_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + if (opts->ipnetwork_map && + !opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].name) { + opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].name = + talloc_strdup(opts->ipnetwork_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + + *srv_opts = so; + return EOK; +} + +void sdap_steal_server_opts(struct sdap_id_ctx *id_ctx, + struct sdap_server_opts **srv_opts) +{ + if (!id_ctx || !srv_opts || !*srv_opts) { + return; + } + + if (!id_ctx->srv_opts) { + id_ctx->srv_opts = talloc_move(id_ctx, srv_opts); + return; + } + + /* discard if same as previous so we do not reset max usn values + * unnecessarily, only update last_usn. */ + if (strcmp(id_ctx->srv_opts->server_id, (*srv_opts)->server_id) == 0) { + id_ctx->srv_opts->last_usn = (*srv_opts)->last_usn; + talloc_zfree(*srv_opts); + return; + } + + talloc_zfree(id_ctx->srv_opts); + id_ctx->srv_opts = talloc_move(id_ctx, srv_opts); +} + +static bool attr_is_filtered(const char *attr, const char **filter) +{ + int i; + + if (filter) { + i = 0; + while (filter[i]) { + if (filter[i] == attr || + strcasecmp(filter[i], attr) == 0) { + return true; + } + i++; + } + } + + return false; +} + +int build_attrs_from_map(TALLOC_CTX *memctx, + struct sdap_attr_map *map, + size_t size, + const char **filter, + const char ***_attrs, + size_t *attr_count) +{ + errno_t ret; + const char **attrs; + int i, j; + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + /* Assume that all entries in the map have values */ + attrs = talloc_zero_array(tmp_ctx, const char *, size + 1); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + /* first attribute is "objectclass" not the specific one */ + attrs[0] = talloc_strdup(memctx, "objectClass"); + if (!attrs[0]) return ENOMEM; + + /* add the others */ + for (i = j = 1; i < size; i++) { + if (map[i].name && !attr_is_filtered(map[i].name, filter)) { + attrs[j] = map[i].name; + j++; + } + } + attrs[j] = NULL; + + /* Trim down the used memory if some attributes were NULL */ + attrs = talloc_realloc(tmp_ctx, attrs, const char *, j + 1); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + *_attrs = talloc_steal(memctx, attrs); + if (attr_count) *attr_count = j; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int sdap_control_create(struct sdap_handle *sh, const char *oid, int iscritical, + struct berval *value, int dupval, LDAPControl **ctrlp) +{ + int ret; + + if (sdap_is_control_supported(sh, oid)) { + ret = sss_ldap_control_create(oid, iscritical, value, dupval, ctrlp); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_ldap_control_create failed [%d][%s].\n", + ret, sss_ldap_err2string(ret)); + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Server does not support the requested control [%s].\n", oid); + ret = LDAP_NOT_SUPPORTED; + } + + return ret; +} + +int sdap_replace_id(struct sysdb_attrs *entry, const char *attr, id_t val) +{ + char *str; + errno_t ret; + struct ldb_message_element *el; + + ret = sysdb_attrs_get_el_ext(entry, attr, false, &el); + if (ret == ENOENT) { + return sysdb_attrs_add_uint32(entry, attr, val); + } else if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get attribute [%s]\n", attr); + return ret; + } + + if (el->num_values != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Expected 1 value for %s, got %d\n", attr, el->num_values); + return EINVAL; + } + + str = talloc_asprintf(entry, "%llu", (unsigned long long) val); + if (!str) { + return ENOMEM; + } + + el->values[0].data = (uint8_t *) str; + el->values[0].length = strlen(str); + + return EOK; +} + +static errno_t sdap_get_rdn_multi(TALLOC_CTX *mem_ctx, const char *dn, + const char *name, char **_val) +{ + int ret; + size_t c; + LDAPDN ldapdn = NULL; + + ret = ldap_str2dn(dn, &ldapdn, LDAP_DN_FORMAT_LDAPV3); + if (ret != LDAP_SUCCESS || ldapdn == NULL || ldapdn[0] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to parse DN [%s].\n", dn); + ret = EINVAL; + goto done; + } + + ret = ENOENT; + for (c = 0; ldapdn[0][c] != NULL; c++) { + if (strncasecmp(name, ldapdn[0][c]->la_attr.bv_val, + ldapdn[0][c]->la_attr.bv_len) == 0) { + *_val = talloc_strndup(mem_ctx, ldapdn[0][c]->la_value.bv_val, + ldapdn[0][c]->la_value.bv_len); + if (*_val == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy AVA value.\n"); + ret = ENOMEM; + goto done; + } + ret = EOK; + break; + } + } + +done: + ldap_dnfree(ldapdn); + + return ret; +} + +errno_t sdap_get_primary_name(const char *attr_name, + struct sysdb_attrs *attrs, + const char **_primary_name) +{ + errno_t ret; + const char *orig_name = NULL; + char *rdn_val = NULL; + struct ldb_message_element *sysdb_name_el; + struct ldb_message_element *orig_dn_el; + size_t i; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = sysdb_attrs_get_el(attrs, + SYSDB_NAME, + &sysdb_name_el); + if (ret != EOK || sysdb_name_el->num_values == 0) { + ret = EINVAL; + goto done; + } + + if (sysdb_name_el->num_values == 1) { + /* Entry contains only one name. Just return that */ + orig_name = (const char *)sysdb_name_el->values[0].data; + ret = EOK; + goto done; + } + + /* Multiple values for name. Check whether one matches the RDN */ + + ret = sysdb_attrs_get_el(attrs, SYSDB_ORIG_DN, &orig_dn_el); + if (ret) { + goto done; + } + if (orig_dn_el->num_values == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Original DN is not available.\n"); + ret = EINVAL; + goto done; + } else if (orig_dn_el->num_values == 1) { + ret = sdap_get_rdn_multi(tmp_ctx, + (const char *) orig_dn_el->values[0].data, + attr_name, &rdn_val); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The entry has multiple names and the RDN attribute does " + "not match. Will use the first value [%s] as fallback.\n", + (const char *)sysdb_name_el->values[0].data); + orig_name = (const char *)sysdb_name_el->values[0].data; + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not get rdn from [%s]\n", + (const char *) orig_dn_el->values[0].data); + goto done; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Should not have more than one origDN\n"); + ret = EINVAL; + goto done; + } + + for (i = 0; i < sysdb_name_el->num_values; i++) { + if (strcasecmp(rdn_val, + (const char *)sysdb_name_el->values[i].data) == 0) { + /* This name matches the RDN. Use it */ + break; + } + } + if (i < sysdb_name_el->num_values) { + /* Match was found */ + orig_name = (const char *)sysdb_name_el->values[i].data; + } else { + /* If we can't match the name to the RDN, we just have to + * throw up our hands. There's no deterministic way to + * decide which name is correct. + */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Can't match the name to the RDN\n"); + ret = EINVAL; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not determine primary name: [%d][%s]\n", + ret, strerror(ret)); + } + talloc_free(tmp_ctx); + + DEBUG(SSSDBG_TRACE_FUNC, "Processing object %s\n", orig_name); + + *_primary_name = orig_name; + + return ret; +} + +static errno_t +sdap_get_primary_fqdn(TALLOC_CTX *mem_ctx, + struct sdap_idmap_ctx *idmap_ctx, + const char *attr_name, + const char *sid_attr_name, + struct sysdb_attrs *attrs, + struct sss_domain_info *dom, + const char **_primary_fqdn) +{ + errno_t ret; + const char *shortname = NULL; + const char *primary_fqdn = NULL; + TALLOC_CTX *tmp_ctx; + char *sid_str = NULL; + struct sss_domain_info *subdomain = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sdap_get_primary_name(attr_name, attrs, &shortname); + if (ret != EOK) { + goto done; + } + + /* In AD scenarion, the object can be from subdomain - identify it by SID */ + if (sid_attr_name != NULL) { + ret = sdap_attrs_get_sid_str(tmp_ctx, + idmap_ctx, + attrs, + sid_attr_name, + &sid_str); + + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Group has objectSID [%s]\n", sid_str); + subdomain = find_domain_by_sid(dom, sid_str); + talloc_free(sid_str); + if (subdomain != NULL) { + dom = subdomain; + } + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Group has name [%s]\n", dom->name); + } + + primary_fqdn = sss_create_internal_fqname(tmp_ctx, shortname, dom->name); + if (primary_fqdn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + *_primary_fqdn = talloc_steal(mem_ctx, primary_fqdn); +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sdap_get_user_primary_name(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sysdb_attrs *attrs, + struct sss_domain_info *dom, + const char **_user_name) +{ + return sdap_get_primary_fqdn(memctx, + opts->idmap_ctx, + opts->user_map[SDAP_AT_USER_NAME].name, + opts->group_map[SDAP_AT_USER_OBJECTSID].name, + attrs, dom, _user_name); +} + +errno_t sdap_get_group_primary_name(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sysdb_attrs *attrs, + struct sss_domain_info *dom, + const char **_group_name) +{ + return sdap_get_primary_fqdn(memctx, + opts->idmap_ctx, + opts->group_map[SDAP_AT_GROUP_NAME].name, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + attrs, dom, _group_name); +} + +errno_t sdap_get_netgroup_primary_name(struct sdap_options *opts, + struct sysdb_attrs *attrs, + const char **_netgroup_name) +{ + return sdap_get_primary_name(opts->netgroup_map[SDAP_AT_NETGROUP_NAME].name, + attrs, _netgroup_name); +} + +static errno_t +_sdap_get_primary_name_list(struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attr_list, + size_t attr_count, + const char *ldap_attr, + bool qualify_names, + const char *sid_attr, + struct sdap_idmap_ctx *idmap_ctx, + char ***name_list) +{ + errno_t ret; + size_t i, j; + char **list; + const char *name; + + /* Assume that every entry has a primary name */ + list = talloc_array(mem_ctx, char *, attr_count+1); + if (!list) { + return ENOMEM; + } + + j = 0; + for (i = 0; i < attr_count; i++) { + if (qualify_names == false) { + ret = sdap_get_primary_name(ldap_attr, attr_list[i], &name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine primary name\n"); + /* Skip and continue. Don't advance 'j' */ + continue; + } + list[j] = talloc_strdup(list, name); + } else { + ret = sdap_get_primary_fqdn(mem_ctx, + idmap_ctx, + ldap_attr, + sid_attr, + attr_list[i], + domain, + &name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine primary fqdn name\n"); + /* Skip and continue. Don't advance 'j' */ + continue; + } + list[j] = talloc_strdup(list, name); + } + if (!list[j]) { + ret = ENOMEM; + goto done; + } + + j++; + } + + /* NULL-terminate the list */ + list[j] = NULL; + + *name_list = list; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(list); + } + return ret; +} + +errno_t sdap_get_primary_name_list(struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attr_list, + size_t attr_count, + const char *ldap_attr, + char ***name_list) +{ + return _sdap_get_primary_name_list(domain, mem_ctx, attr_list, attr_count, + ldap_attr, false, NULL, NULL, name_list); +} + +errno_t sdap_get_primary_fqdn_list(struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attr_list, + size_t attr_count, + const char *ldap_attr, + const char *sid_attr, + struct sdap_idmap_ctx *idmap_ctx, + char ***name_list) +{ + return _sdap_get_primary_name_list(domain, mem_ctx, attr_list, attr_count, + ldap_attr, true, sid_attr, idmap_ctx, name_list); +} + + +char *sdap_make_oc_list(TALLOC_CTX *mem_ctx, struct sdap_attr_map *map) +{ + if (map[SDAP_OC_GROUP_ALT].name == NULL) { + return talloc_asprintf(mem_ctx, "objectClass=%s", + map[SDAP_OC_GROUP].name); + } else { + return talloc_asprintf(mem_ctx, + "|(objectClass=%s)(objectClass=%s)", + map[SDAP_OC_GROUP].name, + map[SDAP_OC_GROUP_ALT].name); + } +} + +struct sss_domain_info *sdap_get_object_domain(struct sdap_options *opts, + struct sysdb_attrs *obj, + struct sss_domain_info *dom) +{ + errno_t ret; + const char *original_dn = NULL; + struct sdap_domain *sdmatch = NULL; + + ret = sysdb_attrs_get_string(obj, SYSDB_ORIG_DN, &original_dn); + if (ret) { + DEBUG(SSSDBG_FUNC_DATA, + "The group has no original DN, assuming our domain\n"); + return dom; + } + + sdmatch = sdap_domain_get_by_dn(opts, original_dn); + if (sdmatch == NULL) { + DEBUG(SSSDBG_FUNC_DATA, + "The original DN of the group cannot " + "be related to any search base\n"); + return dom; + } + + return sdmatch->dom; +} + +bool sdap_object_in_domain(struct sdap_options *opts, + struct sysdb_attrs *obj, + struct sss_domain_info *dom) +{ + struct sss_domain_info *obj_dom; + + obj_dom = sdap_get_object_domain(opts, obj, dom); + if (obj_dom == NULL) { + return false; + } + + return (obj_dom == dom); +} + +size_t sdap_steal_objects_in_dom(struct sdap_options *opts, + struct sysdb_attrs **dom_objects, + size_t offset, + struct sss_domain_info *dom, + struct sysdb_attrs **all_objects, + size_t count, + bool filter) +{ + size_t copied = 0; + + /* Own objects from all_objects by dom_objects in case they belong + * to domain dom. + * + * Don't copy objects from other domains in case + * the search was for parent domain but a child domain would match, + * too, such as: + * dc=example,dc=com + * dc=child,dc=example,dc=com + * while searching for an object from dc=example. + */ + for (size_t i = 0; i < count; i++) { + if (filter && + sdap_object_in_domain(opts, all_objects[i], dom) == false) { + continue; + } + + dom_objects[offset + copied] = + talloc_steal(dom_objects, all_objects[i]); + copied++; + } + + return copied; +} + +void sdap_domain_copy_search_bases(struct sdap_domain *to, + struct sdap_domain *from) +{ + to->search_bases = from->search_bases; + to->user_search_bases = from->user_search_bases; + to->group_search_bases = from->group_search_bases; + to->netgroup_search_bases = from->netgroup_search_bases; + to->sudo_search_bases = from->sudo_search_bases; + to->service_search_bases = from->service_search_bases; + to->iphost_search_bases = from->iphost_search_bases; + to->ipnetwork_search_bases = from->ipnetwork_search_bases; + to->autofs_search_bases = from->autofs_search_bases; +} diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h new file mode 100644 index 0000000..161bc5c --- /dev/null +++ b/src/providers/ldap/sdap.h @@ -0,0 +1,746 @@ +/* + SSSD + + LDAP Helper routines + + Copyright (C) Simo Sorce + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_H_ +#define _SDAP_H_ + +#include +#include "providers/backend.h" +#include +#include "util/sss_ldap.h" +#include "lib/certmap/sss_certmap.h" + +struct sdap_msg { + struct sdap_msg *next; + LDAPMessage *msg; +}; + +struct sdap_op; + +typedef void (sdap_op_callback_t)(struct sdap_op *op, + struct sdap_msg *, int, void *); + +struct sdap_handle; + +struct fd_event_item { + struct fd_event_item *prev; + struct fd_event_item *next; + + int fd; + struct tevent_fd *fde; +}; + +struct ldap_cb_data { + struct sdap_handle *sh; + struct tevent_context *ev; + struct fd_event_item *fd_list; +}; + +struct sup_list { + int num_vals; + char **vals; +}; + +struct sdap_handle { + LDAP *ldap; + bool connected; + /* Authentication ticket expiration time (if any) */ + time_t expire_time; + /* Time when the connection became idle (if any) */ + time_t idle_time; + /* Configured idle timeout */ + int idle_timeout; + ber_int_t page_size; + bool disable_deref; + + struct sdap_fd_events *sdap_fd_events; + + struct sup_list supported_saslmechs; + struct sup_list supported_controls; + struct sup_list supported_extensions; + + struct sdap_op *ops; + + /* during release we need to lock access to the handler + * from the destructor to avoid recursion */ + bool destructor_lock; + /* mark when it is safe to finally release the handler memory */ + bool release_memory; +}; + +struct sdap_service { + char *name; + char *uri; + char *kinit_service_name; + struct sockaddr *sockaddr; + socklen_t sockaddr_len; +}; + +struct sdap_ppolicy_data { + int grace; + int expire; +}; + +#define SYSDB_SHADOWPW_LASTCHANGE "shadowLastChange" +#define SYSDB_SHADOWPW_MIN "shadowMin" +#define SYSDB_SHADOWPW_MAX "shadowMax" +#define SYSDB_SHADOWPW_WARNING "shadowWarning" +#define SYSDB_SHADOWPW_INACTIVE "shadowInactive" +#define SYSDB_SHADOWPW_EXPIRE "shadowExpire" +#define SYSDB_SHADOWPW_FLAG "shadowFlag" + +#define SYSDB_NS_ACCOUNT_LOCK "nsAccountLock" + +#define SYSDB_KRBPW_LASTCHANGE "krbLastPwdChange" +#define SYSDB_KRBPW_EXPIRATION "krbPasswordExpiration" + +#define SYSDB_PWD_ATTRIBUTE "pwdAttribute" + +#define SYSDB_NDS_LOGIN_DISABLED "ndsLoginDisabled" +#define SYSDB_NDS_LOGIN_EXPIRATION_TIME "ndsLoginExpirationTime" +#define SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP "ndsLoginAllowedTimeMap" + +#define SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS "namingContexts" +#define SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT "defaultNamingContext" +#define SDAP_ROOTDSE_ATTR_AD_VERSION "domainControllerFunctionality" +#define SDAP_ROOTDSE_ATTR_AD_SCHEMA_NC "schemaNamingContext" + +#define SDAP_IPA_USN "entryUSN" +#define SDAP_IPA_LAST_USN "lastUSN" +#define SDAP_AD_USN "uSNChanged" +#define SDAP_AD_LAST_USN "highestCommittedUSN" + +#define SDAP_AD_GROUP_TYPE_BUILTIN 0x00000001 +#define SDAP_AD_GROUP_TYPE_GLOBAL 0x00000002 +#define SDAP_AD_GROUP_TYPE_DOMAIN_LOCAL 0x00000004 +#define SDAP_AD_GROUP_TYPE_UNIVERSAL 0x00000008 +#define SDAP_AD_GROUP_TYPE_APP_BASIC 0x00000010 +#define SDAP_AD_GROUP_TYPE_APP_QUERY 0x00000020 +#define SDAP_AD_GROUP_TYPE_SECURITY 0x80000000 + +enum sdap_basic_opt { + SDAP_URI = 0, + SDAP_BACKUP_URI, + SDAP_SEARCH_BASE, + SDAP_DEFAULT_BIND_DN, + SDAP_DEFAULT_AUTHTOK_TYPE, + SDAP_DEFAULT_AUTHTOK, + SDAP_SEARCH_TIMEOUT, + SDAP_NETWORK_TIMEOUT, + SDAP_OPT_TIMEOUT, + SDAP_TLS_REQCERT, + SDAP_USER_SEARCH_BASE, + SDAP_USER_SEARCH_SCOPE, + SDAP_USER_SEARCH_FILTER, + SDAP_USER_EXTRA_ATTRS, + SDAP_GROUP_SEARCH_BASE, + SDAP_GROUP_SEARCH_SCOPE, + SDAP_GROUP_SEARCH_FILTER, + SDAP_HOST_SEARCH_BASE, + SDAP_SERVICE_SEARCH_BASE, + SDAP_SUDO_SEARCH_BASE, + SDAP_SUDO_FULL_REFRESH_INTERVAL, + SDAP_SUDO_SMART_REFRESH_INTERVAL, + SDAP_SUDO_RANDOM_OFFSET, + SDAP_SUDO_USE_HOST_FILTER, + SDAP_SUDO_HOSTNAMES, + SDAP_SUDO_IP, + SDAP_SUDO_INCLUDE_NETGROUPS, + SDAP_SUDO_INCLUDE_REGEXP, + SDAP_AUTOFS_SEARCH_BASE, + SDAP_AUTOFS_MAP_MASTER_NAME, + SDAP_IPHOST_SEARCH_BASE, + SDAP_IPNETWORK_SEARCH_BASE, + SDAP_SCHEMA, + SDAP_PWMODIFY_MODE, + SDAP_OFFLINE_TIMEOUT, + SDAP_FORCE_UPPER_CASE_REALM, + SDAP_ENUM_REFRESH_TIMEOUT, + SDAP_ENUM_REFRESH_OFFSET, + SDAP_PURGE_CACHE_TIMEOUT, + SDAP_PURGE_CACHE_OFFSET, + SDAP_TLS_CACERT, + SDAP_TLS_CACERTDIR, + SDAP_TLS_CERT, + SDAP_TLS_KEY, + SDAP_TLS_CIPHER_SUITE, + SDAP_ID_TLS, + SDAP_ID_MAPPING, + SDAP_SASL_MECH, + SDAP_SASL_AUTHID, + SDAP_SASL_REALM, + SDAP_SASL_MINSSF, + SDAP_SASL_MAXSSF, + SDAP_KRB5_KEYTAB, + SDAP_KRB5_KINIT, + SDAP_KRB5_KDC, + SDAP_KRB5_BACKUP_KDC, + SDAP_KRB5_REALM, + SDAP_KRB5_CANONICALIZE, + SDAP_KRB5_USE_KDCINFO, + SDAP_KRB5_KDCINFO_LOOKAHEAD, + SDAP_PWD_POLICY, + SDAP_REFERRALS, + SDAP_ACCOUNT_CACHE_EXPIRATION, + SDAP_DNS_SERVICE_NAME, + SDAP_KRB5_TICKET_LIFETIME, + SDAP_ACCESS_FILTER, + SDAP_NETGROUP_SEARCH_BASE, + SDAP_NESTING_LEVEL, + SDAP_DEREF, + SDAP_ACCOUNT_EXPIRE_POLICY, + SDAP_ACCESS_ORDER, + SDAP_CHPASS_URI, + SDAP_CHPASS_BACKUP_URI, + SDAP_CHPASS_DNS_SERVICE_NAME, + SDAP_CHPASS_UPDATE_LAST_CHANGE, + SDAP_ENUM_SEARCH_TIMEOUT, + SDAP_DISABLE_AUTH_TLS, + SDAP_PAGE_SIZE, + SDAP_DEREF_THRESHOLD, + SDAP_IGNORE_UNREADABLE_REFERENCES, + SDAP_SASL_CANONICALIZE, + SDAP_EXPIRE_TIMEOUT, + SDAP_EXPIRE_OFFSET, + SDAP_IDLE_TIMEOUT, + SDAP_DISABLE_PAGING, + SDAP_IDMAP_LOWER, + SDAP_IDMAP_UPPER, + SDAP_IDMAP_RANGESIZE, + SDAP_IDMAP_AUTORID_COMPAT, + SDAP_IDMAP_DEFAULT_DOMAIN, + SDAP_IDMAP_DEFAULT_DOMAIN_SID, + SDAP_IDMAP_EXTRA_SLICE_INIT, + SDAP_AD_USE_TOKENGROUPS, + SDAP_RFC2307_FALLBACK_TO_LOCAL_USERS, + SDAP_DISABLE_RANGE_RETRIEVAL, + SDAP_MIN_ID, + SDAP_MAX_ID, + SDAP_PWDLOCKOUT_DN, + SDAP_WILDCARD_LIMIT, + SDAP_LIBRARY_DEBUG_LEVEL, + + SDAP_OPTS_BASIC /* opts counter */ +}; + +enum sdap_gen_attrs { + SDAP_AT_ENTRY_USN = 0, + SDAP_AT_LAST_USN, + + SDAP_AT_GENERAL /* attrs counter */ +}; + +/* the objectclass must be the first attribute. + * Functions depend on this */ +enum sdap_user_attrs { + SDAP_OC_USER = 0, + SDAP_AT_USER_NAME, + SDAP_AT_USER_PWD, + SDAP_AT_USER_UID, + SDAP_AT_USER_GID, + SDAP_AT_USER_GECOS, + SDAP_AT_USER_HOME, + SDAP_AT_USER_SHELL, + SDAP_AT_USER_PRINC, + SDAP_AT_USER_FULLNAME, + SDAP_AT_USER_MEMBEROF, + SDAP_AT_USER_UUID, + SDAP_AT_USER_OBJECTSID, + SDAP_AT_USER_PRIMARY_GROUP, + SDAP_AT_USER_MODSTAMP, + SDAP_AT_USER_USN, + SDAP_AT_SP_LSTCHG, + SDAP_AT_SP_MIN, + SDAP_AT_SP_MAX, + SDAP_AT_SP_WARN, + SDAP_AT_SP_INACT, + SDAP_AT_SP_EXPIRE, + SDAP_AT_SP_FLAG, + SDAP_AT_KP_LASTCHANGE, + SDAP_AT_KP_EXPIRATION, + SDAP_AT_PWD_ATTRIBUTE, + SDAP_AT_AUTH_SVC, + SDAP_AT_AD_ACCOUNT_EXPIRES, + SDAP_AT_AD_USER_ACCOUNT_CONTROL, + SDAP_AT_NS_ACCOUNT_LOCK, + SDAP_AT_AUTHORIZED_HOST, + SDAP_AT_AUTHORIZED_RHOST, + SDAP_AT_NDS_LOGIN_DISABLED, + SDAP_AT_NDS_LOGIN_EXPIRATION_TIME, + SDAP_AT_NDS_LOGIN_ALLOWED_TIME_MAP, + SDAP_AT_USER_SSH_PUBLIC_KEY, + SDAP_AT_USER_AUTH_TYPE, + SDAP_AT_USER_CERT, + SDAP_AT_USER_EMAIL, + SDAP_AT_USER_PASSKEY, + + SDAP_OPTS_USER /* attrs counter */ +}; + +#define SDAP_FIRST_EXTRA_USER_AT SDAP_AT_SP_LSTCHG + +/* the objectclass must be the first attribute. + * Functions depend on this */ +enum sdap_group_attrs { + SDAP_OC_GROUP = 0, + SDAP_OC_GROUP_ALT, + SDAP_AT_GROUP_NAME, + SDAP_AT_GROUP_PWD, + SDAP_AT_GROUP_GID, + SDAP_AT_GROUP_MEMBER, + SDAP_AT_GROUP_UUID, + SDAP_AT_GROUP_OBJECTSID, + SDAP_AT_GROUP_MODSTAMP, + SDAP_AT_GROUP_USN, + SDAP_AT_GROUP_TYPE, + SDAP_AT_GROUP_EXT_MEMBER, + + SDAP_OPTS_GROUP /* attrs counter */ +}; + +enum sdap_netgroup_attrs { + SDAP_OC_NETGROUP = 0, + SDAP_AT_NETGROUP_NAME, + SDAP_AT_NETGROUP_MEMBER, + SDAP_AT_NETGROUP_TRIPLE, + SDAP_AT_NETGROUP_MODSTAMP, + + SDAP_OPTS_NETGROUP /* attrs counter */ +}; + +enum sdap_sudorule_attrs { + SDAP_OC_SUDORULE = 0, + SDAP_AT_SUDO_OC, + SDAP_AT_SUDO_NAME, + SDAP_AT_SUDO_COMMAND, + SDAP_AT_SUDO_HOST, + SDAP_AT_SUDO_USER, + SDAP_AT_SUDO_OPTION, + SDAP_AT_SUDO_RUNAS, + SDAP_AT_SUDO_RUNASUSER, + SDAP_AT_SUDO_RUNASGROUP, + SDAP_AT_SUDO_NOTBEFORE, + SDAP_AT_SUDO_NOTAFTER, + SDAP_AT_SUDO_ORDER, + SDAP_AT_SUDO_USN, + + SDAP_OPTS_SUDO /* attrs counter */ +}; + +enum sdap_host_attrs { + SDAP_OC_HOST = 0, + SDAP_AT_HOST_NAME, + SDAP_AT_HOST_FQDN, + SDAP_AT_HOST_SERVERHOSTNAME, + SDAP_AT_HOST_MEMBER_OF, + SDAP_AT_HOST_SSH_PUBLIC_KEY, + SDAP_AT_HOST_UUID, + + SDAP_OPTS_HOST /* attrs counter */ +}; + +enum sdap_service_attrs { + SDAP_OC_SERVICE = 0, + SDAP_AT_SERVICE_NAME, + SDAP_AT_SERVICE_PORT, + SDAP_AT_SERVICE_PROTOCOL, + SDAP_AT_SERVICE_USN, + SDAP_OPTS_SERVICES /* attrs counter */ +}; + +enum sdap_iphost_entry_attrs { + SDAP_OC_IPHOST = 0, + SDAP_AT_IPHOST_NAME, + SDAP_AT_IPHOST_NUMBER, + SDAP_AT_IPHOST_USN, + + SDAP_OPTS_IPHOST /* attrs counter */ +}; + +enum sdap_ipnetwork_entry_attrs { + SDAP_OC_IPNETWORK = 0, + SDAP_AT_IPNETWORK_NAME, + SDAP_AT_IPNETWORK_NUMBER, + SDAP_AT_IPNETWORK_USN, + + SDAP_OPTS_IPNETWORK /* attrs counter */ +}; + +#ifdef BUILD_SUBID +enum sdap_subid_range_attrs { + SDAP_OC_SUBID_RANGE = 0, + SDAP_AT_SUBID_RANGE_UID_COUNT, + SDAP_AT_SUBID_RANGE_GID_COUNT, + SDAP_AT_SUBID_RANGE_UID_NUMBER, + SDAP_AT_SUBID_RANGE_GID_NUMBER, + SDAP_AT_SUBID_RANGE_OWNER, + + SDAP_OPTS_SUBID_RANGE /* attrs counter */ +}; +#endif + +enum sdap_autofs_map_attrs { + SDAP_OC_AUTOFS_MAP, + SDAP_AT_AUTOFS_MAP_NAME, + + SDAP_OPTS_AUTOFS_MAP /* attrs counter */ +}; + +enum sdap_autofs_entry_attrs { + SDAP_OC_AUTOFS_ENTRY, + SDAP_AT_AUTOFS_ENTRY_KEY, + SDAP_AT_AUTOFS_ENTRY_VALUE, + + SDAP_OPTS_AUTOFS_ENTRY /* attrs counter */ +}; + +struct sdap_attr_map { + const char *opt_name; + const char *def_name; + const char *sys_name; + char *name; +}; +#define SDAP_ATTR_MAP_TERMINATOR { NULL, NULL, NULL, NULL } + +struct sdap_search_base { + const char *basedn; + struct ldb_context *ldb; + struct ldb_dn *ldb_basedn; + int scope; + const char *filter; +}; + +errno_t +sdap_create_search_base(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *unparsed_base, + int scope, + const char *filter, + struct sdap_search_base **_base); + +/* Values from + * http://msdn.microsoft.com/en-us/library/cc223272%28v=prot.13%29.aspx + */ +enum dc_functional_level { + DS_BEHAVIOR_WIN2000 = 0, + DS_BEHAVIOR_WIN2003 = 2, + DS_BEHAVIOR_WIN2008 = 3, + DS_BEHAVIOR_WIN2008R2 = 4, + DS_BEHAVIOR_WIN2012 = 5, + DS_BEHAVIOR_WIN2012R2 = 6, + DS_BEHAVIOR_WIN2016 = 7, +}; + +struct sdap_domain { + struct sss_domain_info *dom; + + char *basedn; + + struct sdap_search_base **search_bases; + struct sdap_search_base **user_search_bases; + struct sdap_search_base **group_search_bases; + struct sdap_search_base **netgroup_search_bases; + struct sdap_search_base **host_search_bases; + struct sdap_search_base **sudo_search_bases; + struct sdap_search_base **service_search_bases; + struct sdap_search_base **iphost_search_bases; + struct sdap_search_base **ipnetwork_search_bases; + struct sdap_search_base **autofs_search_bases; + struct sdap_search_base **ignore_user_search_bases; +#ifdef BUILD_SUBID + struct sdap_search_base **subid_ranges_search_bases; +#endif + + struct sdap_domain *next, *prev; + /* Need to modify the list from a talloc destructor */ + struct sdap_domain **head; + + void *pvt; +}; + +typedef struct tevent_req * +(*ext_member_send_fn_t)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ext_member, + void *pvt); +typedef errno_t +(*ext_member_recv_fn_t)(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + enum sysdb_member_type *member_type, + struct sss_domain_info **_dom, + struct sysdb_attrs **_member); + +struct sdap_ext_member_ctx { + /* Typically ID context of the external ID provider */ + void *pvt; + + ext_member_send_fn_t ext_member_resolve_send; + ext_member_recv_fn_t ext_member_resolve_recv; +}; + +struct sdap_certmap_ctx; + +struct sdap_options { + struct dp_option *basic; + struct data_provider *dp; + struct sdap_attr_map *gen_map; + struct sdap_attr_map *user_map; + size_t user_map_cnt; + struct sdap_attr_map *group_map; + struct sdap_attr_map *netgroup_map; + struct sdap_attr_map *host_map; + struct sdap_attr_map *service_map; + struct sdap_attr_map *iphost_map; + struct sdap_attr_map *ipnetwork_map; +#ifdef BUILD_SUBID + struct sdap_attr_map *subid_map; +#endif + + /* ID-mapping support */ + struct sdap_idmap_ctx *idmap_ctx; + + /* Resolving external members */ + struct sdap_ext_member_ctx *ext_ctx; + + /* FIXME - should this go to a special struct to avoid mixing with name-service-switch maps? */ + struct sdap_attr_map *sudorule_map; + struct sdap_attr_map *autofs_mobject_map; + struct sdap_attr_map *autofs_entry_map; + + /* supported schema types */ + enum schema_type { + SDAP_SCHEMA_RFC2307 = 1, /* memberUid = uid */ + SDAP_SCHEMA_RFC2307BIS = 2, /* member = dn */ + SDAP_SCHEMA_IPA_V1 = 3, /* member/memberof */ + SDAP_SCHEMA_AD = 4 /* AD's member/memberof */ + } schema_type; + + /* password modify mode */ + enum pwmodify_mode { + SDAP_PWMODIFY_EXOP = 1, /* pwmodify extended operation */ + SDAP_PWMODIFY_LDAP = 2 /* ldap_modify of userPassword */ + } pwmodify_mode; + + /* The search bases for the domain or its subdomain */ + struct sdap_domain *sdom; + + /* The options below are normally only used with AD */ + bool support_matching_rule; + enum dc_functional_level dc_functional_level; + const char *schema_basedn; + bool allow_remote_domain_local_groups; + + /* Certificate mapping support */ + struct sdap_certmap_ctx *sdap_certmap_ctx; +}; + +struct sdap_server_opts { + char *server_id; + bool supports_usn; + unsigned long last_usn; + char *max_user_value; + char *max_group_value; + char *max_service_value; + char *max_sudo_value; + char *max_iphost_value; + char *max_ipnetwork_value; +}; + +struct sdap_id_ctx; + +struct sdap_attr_map_info { + struct sdap_attr_map *map; + int num_attrs; +}; + +struct sdap_deref_attrs { + struct sdap_attr_map *map; + struct sysdb_attrs *attrs; +}; + +errno_t sdap_copy_map_entry(const struct sdap_attr_map *src_map, + struct sdap_attr_map *dst_map, + int entry_index); + +int sdap_copy_map(TALLOC_CTX *memctx, + struct sdap_attr_map *src_map, + int num_entries, + struct sdap_attr_map **_map); + +/** + * @brief Add attributes to a map + * + * sdap_extend_map() will call talloc_realloc() on the second argument so the + * original storage location might change. The return value _map will always + * contain the current memory location which can be used with talloc_free() + * even if there is an error. + * + * @param[in] memctx Talloc memory context + * @param[in] src_map Original map, should not be accessed anymore + * @param[in] num_entries Number of entries in the original map + * @param[in] extra_attrs NULL-terminated array of extra attribute pairs + * sysdb_attr:ldap_attr + * @param[out] _map New map + * @param[out] _new_size Number of entries in the new map + * + * @return + * - EOK success + * - ENOMEM memory allocation failed + * - ERR_DUP_EXTRA_ATTR sysdb attribute is already used + */ +int sdap_extend_map(TALLOC_CTX *memctx, + struct sdap_attr_map *src_map, + size_t num_entries, + char **extra_attrs, + struct sdap_attr_map **_map, + size_t *_new_size); + +int sdap_extend_map_with_list(TALLOC_CTX *mem_ctx, + const struct sdap_options *opts, + int extra_attr_index, + struct sdap_attr_map *src_map, + size_t num_entries, + struct sdap_attr_map **_map, + size_t *_new_size); + +void sdap_inherit_options(char **inherit_opt_list, + struct sdap_options *parent_sdap_opts, + struct sdap_options *child_sdap_opts); + +int sdap_get_map(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_attr_map *def_map, + int num_entries, + struct sdap_attr_map **_map); + +int sdap_parse_entry(TALLOC_CTX *memctx, + struct sdap_handle *sh, struct sdap_msg *sm, + struct sdap_attr_map *map, int attrs_num, + struct sysdb_attrs **_attrs, + bool disable_range_retrieval); + +errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx, + struct sdap_attr_map_info *minfo, + size_t num_maps, + LDAPDerefRes *dref, + struct sdap_deref_attrs ***_deref_res); + +void setup_ldap_debug(struct dp_option *basic_opts); + +errno_t setup_tls_config(struct dp_option *basic_opts); + +int sdap_set_rootdse_supported_lists(struct sysdb_attrs *rootdse, + struct sdap_handle *sh); +bool sdap_check_sup_list(struct sup_list *l, const char *val); + +#define sdap_is_sasl_mech_supported(sh, sasl_mech) \ + sdap_check_sup_list(&((sh)->supported_saslmechs), sasl_mech) + +#define sdap_is_control_supported(sh, ctrl_oid) \ + sdap_check_sup_list(&((sh)->supported_controls), ctrl_oid) + +#define sdap_is_extension_supported(sh, ext_oid) \ + sdap_check_sup_list(&((sh)->supported_extensions), ext_oid) + +bool sdap_sasl_mech_needs_kinit(const char *mech); + +int build_attrs_from_map(TALLOC_CTX *memctx, + struct sdap_attr_map *map, + size_t size, + const char **filter, + const char ***_attrs, + size_t *attr_count); + +int sdap_control_create(struct sdap_handle *sh, const char *oid, int iscritical, + struct berval *value, int dupval, LDAPControl **ctrlp); + +int sdap_replace_id(struct sysdb_attrs *entry, const char *attr, id_t val); + +errno_t sdap_get_group_primary_name(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sysdb_attrs *attrs, + struct sss_domain_info *dom, + const char **_group_name); + +errno_t sdap_get_user_primary_name(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sysdb_attrs *attrs, + struct sss_domain_info *dom, + const char **_user_name); + +errno_t sdap_get_netgroup_primary_name(struct sdap_options *opts, + struct sysdb_attrs *attrs, + const char **_netgroup_name); + +errno_t sdap_get_primary_name(const char *attr_name, + struct sysdb_attrs *attrs, + const char **_primary_name); + +errno_t sdap_get_primary_name_list(struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attr_list, + size_t attr_count, + const char *ldap_attr, + char ***name_list); + +errno_t sdap_get_primary_fqdn_list(struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attr_list, + size_t attr_count, + const char *ldap_attr, + const char *sid_attr, + struct sdap_idmap_ctx *idmap_ctx, + char ***name_list); + +errno_t sdap_set_config_options_with_rootdse(struct sysdb_attrs *rootdse, + struct sdap_options *opts, + struct sdap_domain *sdom); +int sdap_get_server_opts_from_rootdse(TALLOC_CTX *memctx, + const char *server, + struct sysdb_attrs *rootdse, + struct sdap_options *opts, + struct sdap_server_opts **srv_opts); +void sdap_steal_server_opts(struct sdap_id_ctx *id_ctx, + struct sdap_server_opts **srv_opts); + +char *sdap_make_oc_list(TALLOC_CTX *mem_ctx, struct sdap_attr_map *map); + +size_t sdap_steal_objects_in_dom(struct sdap_options *opts, + struct sysdb_attrs **dom_objects, + size_t offset, + struct sss_domain_info *dom, + struct sysdb_attrs **all_objects, + size_t count, + bool filter); + +struct sss_domain_info *sdap_get_object_domain(struct sdap_options *opts, + struct sysdb_attrs *obj, + struct sss_domain_info *dom); + +bool sdap_object_in_domain(struct sdap_options *opts, + struct sysdb_attrs *obj, + struct sss_domain_info *dom); + +void sdap_domain_copy_search_bases(struct sdap_domain *to, + struct sdap_domain *from); + +#endif /* _SDAP_H_ */ diff --git a/src/providers/ldap/sdap_access.c b/src/providers/ldap/sdap_access.c new file mode 100644 index 0000000..c7e48d9 --- /dev/null +++ b/src/providers/ldap/sdap_access.c @@ -0,0 +1,2402 @@ +/* + SSSD + + sdap_access.c + + Authors: + Stephen Gallagher + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "util/strtonum.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_access.h" +#include "providers/ldap/sdap_async.h" +#include "providers/data_provider.h" +#include "providers/backend.h" +#include "providers/ldap/ldap_auth.h" +#include "providers/ipa/ipa_common.h" + +#define PERMANENTLY_LOCKED_ACCOUNT "000001010000Z" +#define MALFORMED_FILTER "Malformed access control filter [%s]\n" + +enum sdap_pwpolicy_mode { + PWP_LOCKOUT_ONLY, + PWP_LOCKOUT_EXPIRE, + PWP_SENTINEL, +}; + +static errno_t perform_pwexpire_policy(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct pam_data *pd, + enum sdap_access_type access_type, + struct sdap_options *opts); + +static errno_t sdap_save_user_cache_bool(struct sss_domain_info *domain, + const char *username, + const char *attr_name, + bool value); + +static errno_t sdap_get_basedn_user_entry(struct ldb_message *user_entry, + const char *username, + const char **_basedn); + +static struct tevent_req * +sdap_access_ppolicy_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + struct sdap_access_ctx *access_ctx, + struct sdap_id_conn_ctx *conn, + const char *username, + struct ldb_message *user_entry, + enum sdap_pwpolicy_mode pwpol_mod); + +static struct tevent_req *sdap_access_filter_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + struct sdap_access_ctx *access_ctx, + struct sdap_id_conn_ctx *conn, + const char *username, + struct ldb_message *user_entry); + +static errno_t sdap_access_filter_recv(struct tevent_req *req); + +static errno_t sdap_access_ppolicy_recv(struct tevent_req *req); + +static errno_t sdap_account_expired(struct sdap_access_ctx *access_ctx, + struct pam_data *pd, + struct ldb_message *user_entry); + +static errno_t sdap_access_service(struct pam_data *pd, + struct ldb_message *user_entry); + +static errno_t sdap_access_host(struct ldb_message *user_entry); + +errno_t sdap_access_rhost(struct ldb_message *user_entry, char *rhost); + +enum sdap_access_control_type { + SDAP_ACCESS_CONTROL_FILTER, + SDAP_ACCESS_CONTROL_PPOLICY_LOCK, +}; + +struct sdap_access_req_ctx { + struct pam_data *pd; + struct tevent_context *ev; + struct sdap_access_ctx *access_ctx; + struct sdap_id_conn_ctx *conn; + struct be_ctx *be_ctx; + struct sss_domain_info *domain; + struct ldb_message *user_entry; + size_t current_rule; + enum sdap_access_control_type ac_type; +}; + +static errno_t sdap_access_check_next_rule(struct sdap_access_req_ctx *state, + struct tevent_req *req); +static void sdap_access_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_access_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + struct sdap_access_ctx *access_ctx, + struct sdap_id_conn_ctx *conn, + struct pam_data *pd) +{ + errno_t ret; + struct sdap_access_req_ctx *state; + struct tevent_req *req; + struct ldb_result *res; + const char *attrs[] = { "*", NULL }; + + req = tevent_req_create(mem_ctx, &state, struct sdap_access_req_ctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->be_ctx = be_ctx; + state->domain = domain; + state->pd = pd; + state->ev = ev; + state->access_ctx = access_ctx; + state->conn = conn; + state->current_rule = 0; + + DEBUG(SSSDBG_TRACE_FUNC, + "Performing access check for user [%s]\n", pd->user); + + if (access_ctx->access_rule[0] == LDAP_ACCESS_EMPTY) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No access rules defined, access denied.\n"); + ret = ERR_ACCESS_DENIED; + goto done; + } + + /* Get original user DN, domain already points to the right (sub)domain */ + ret = sysdb_get_user_attr(state, domain, pd->user, attrs, &res); + if (ret != EOK) { + if (ret == ENOENT) { + /* If we can't find the user, return access denied */ + ret = ERR_ACCESS_DENIED; + goto done; + } + goto done; + } + else { + if (res->count == 0) { + /* If we can't find the user, return access denied */ + ret = ERR_ACCESS_DENIED; + goto done; + } + + if (res->count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid response from sysdb_get_user_attr\n"); + ret = EINVAL; + goto done; + } + } + + state->user_entry = res->msgs[0]; + + ret = sdap_access_check_next_rule(state, req); + if (ret == EAGAIN) { + return req; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t sdap_access_check_next_rule(struct sdap_access_req_ctx *state, + struct tevent_req *req) +{ + struct tevent_req *subreq; + int ret = EOK; + + while (ret == EOK) { + switch (state->access_ctx->access_rule[state->current_rule]) { + case LDAP_ACCESS_EMPTY: + /* we are done with no errors */ + return EOK; + + /* This option is deprecated by LDAP_ACCESS_PPOLICY */ + case LDAP_ACCESS_LOCKOUT: + DEBUG(SSSDBG_MINOR_FAILURE, + "WARNING: %s option is deprecated and might be removed in " + "a future release. Please migrate to %s option instead.\n", + LDAP_ACCESS_LOCK_NAME, LDAP_ACCESS_PPOLICY_NAME); + + subreq = sdap_access_ppolicy_send(state, state->ev, state->be_ctx, + state->domain, + state->access_ctx, + state->conn, + state->pd->user, + state->user_entry, + PWP_LOCKOUT_ONLY); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_access_ppolicy_send failed.\n"); + return ENOMEM; + } + + state->ac_type = SDAP_ACCESS_CONTROL_PPOLICY_LOCK; + + tevent_req_set_callback(subreq, sdap_access_done, req); + return EAGAIN; + + case LDAP_ACCESS_PPOLICY: + subreq = sdap_access_ppolicy_send(state, state->ev, state->be_ctx, + state->domain, + state->access_ctx, + state->conn, + state->pd->user, + state->user_entry, + PWP_LOCKOUT_EXPIRE); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_access_ppolicy_send failed.\n"); + return ENOMEM; + } + + state->ac_type = SDAP_ACCESS_CONTROL_PPOLICY_LOCK; + + tevent_req_set_callback(subreq, sdap_access_done, req); + return EAGAIN; + + case LDAP_ACCESS_FILTER: + subreq = sdap_access_filter_send(state, state->ev, state->be_ctx, + state->domain, + state->access_ctx, + state->conn, + state->pd->user, + state->user_entry); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_access_filter_send failed.\n"); + return ENOMEM; + } + + state->ac_type = SDAP_ACCESS_CONTROL_FILTER; + + tevent_req_set_callback(subreq, sdap_access_done, req); + return EAGAIN; + + case LDAP_ACCESS_EXPIRE: + ret = sdap_account_expired(state->access_ctx, + state->pd, state->user_entry); + break; + + case LDAP_ACCESS_EXPIRE_POLICY_REJECT: + ret = perform_pwexpire_policy(state, state->domain, state->pd, + state->access_ctx->type, + state->access_ctx->id_ctx->opts); + if (ret == ERR_PASSWORD_EXPIRED) { + ret = ERR_PASSWORD_EXPIRED_REJECT; + } + break; + + case LDAP_ACCESS_EXPIRE_POLICY_WARN: + ret = perform_pwexpire_policy(state, state->domain, state->pd, + state->access_ctx->type, + state->access_ctx->id_ctx->opts); + if (ret == ERR_PASSWORD_EXPIRED) { + ret = ERR_PASSWORD_EXPIRED_WARN; + } + break; + + case LDAP_ACCESS_EXPIRE_POLICY_RENEW: + ret = perform_pwexpire_policy(state, state->domain, state->pd, + state->access_ctx->type, + state->access_ctx->id_ctx->opts); + if (ret == ERR_PASSWORD_EXPIRED) { + ret = ERR_PASSWORD_EXPIRED_RENEW; + } + break; + + case LDAP_ACCESS_SERVICE: + ret = sdap_access_service( state->pd, state->user_entry); + break; + + case LDAP_ACCESS_HOST: + ret = sdap_access_host(state->user_entry); + break; + + case LDAP_ACCESS_RHOST: + ret = sdap_access_rhost(state->user_entry, state->pd->rhost); + break; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected access rule type %d. Access denied.\n", + state->access_ctx->access_rule[state->current_rule]); + ret = ERR_ACCESS_DENIED; + } + + state->current_rule++; + } + + return ret; +} + +static void sdap_access_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_access_req_ctx *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_access_req_ctx); + + /* process subrequest */ + switch(state->ac_type) { + case SDAP_ACCESS_CONTROL_FILTER: + ret = sdap_access_filter_recv(subreq); + break; + case SDAP_ACCESS_CONTROL_PPOLICY_LOCK: + ret = sdap_access_ppolicy_recv(subreq); + break; + default: + ret = EINVAL; + DEBUG(SSSDBG_MINOR_FAILURE, "Unknown access control type: %d.\n", + state->ac_type); + break; + } + + talloc_zfree(subreq); + if (ret != EOK) { + if (ret == ERR_ACCESS_DENIED) { + DEBUG(SSSDBG_TRACE_FUNC, "Access was denied.\n"); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error retrieving access check result.\n"); + } + tevent_req_error(req, ret); + return; + } + + state->current_rule++; + + ret = sdap_access_check_next_rule(state, req); + switch (ret) { + case EAGAIN: + return; + case EOK: + tevent_req_done(req); + return; + default: + tevent_req_error(req, ret); + return; + } +} + +errno_t sdap_access_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +#define SHADOW_EXPIRE_MSG "Account expired according to shadow attributes" + +static errno_t sdap_account_expired_shadow(struct pam_data *pd, + struct ldb_message *user_entry) +{ + int ret; + const char *val; + long sp_expire; + long today; + + DEBUG(SSSDBG_TRACE_FUNC, + "Performing access shadow check for user [%s]\n", pd->user); + + val = ldb_msg_find_attr_as_string(user_entry, SYSDB_SHADOWPW_EXPIRE, NULL); + if (val == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Shadow expire attribute not found. " + "Access will be granted.\n"); + return EOK; + } + ret = string_to_shadowpw_days(val, &sp_expire); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to retrieve shadow expire date.\n"); + return ret; + } + + today = (long) (time(NULL) / (60 * 60 * 24)); + if (sp_expire > 0 && today >= sp_expire) { + + ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO, + sizeof(SHADOW_EXPIRE_MSG), + (const uint8_t *) SHADOW_EXPIRE_MSG); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return ERR_ACCOUNT_EXPIRED; + } + + return EOK; +} + +#define UAC_ACCOUNTDISABLE 0x00000002 +#define AD_NEVER_EXP 0x7fffffffffffffffLL +#define AD_TO_UNIX_TIME_CONST 11644473600LL +#define AD_DISABLE_MESSAGE "The user account is disabled on the AD server" +#define AD_EXPIRED_MESSAGE "The user account is expired on the AD server" + +static bool ad_account_expired(uint64_t expiration_time) +{ + time_t now; + int err; + uint64_t nt_now; + + if (expiration_time == 0 || expiration_time == AD_NEVER_EXP) { + return false; + } + + now = time(NULL); + if (now == ((time_t) -1)) { + err = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "time failed [%d][%s].\n", err, strerror(err)); + return true; + } + + /* NT timestamps start at 1601-01-01 and use a 100ns base */ + nt_now = (now + AD_TO_UNIX_TIME_CONST) * 1000 * 1000 * 10; + + if (nt_now > expiration_time) { + return true; + } + + return false; +} + +static errno_t sdap_account_expired_ad(struct pam_data *pd, + struct ldb_message *user_entry) +{ + uint32_t uac; + uint64_t expiration_time; + int ret; + + DEBUG(SSSDBG_TRACE_FUNC, + "Performing AD access check for user [%s]\n", pd->user); + + uac = ldb_msg_find_attr_as_uint(user_entry, SYSDB_AD_USER_ACCOUNT_CONTROL, + 0); + DEBUG(SSSDBG_TRACE_ALL, "User account control for user [%s] is [%X].\n", + pd->user, uac); + + expiration_time = ldb_msg_find_attr_as_uint64(user_entry, + SYSDB_AD_ACCOUNT_EXPIRES, 0); + DEBUG(SSSDBG_TRACE_ALL, + "Expiration time for user [%s] is [%"PRIu64"].\n", + pd->user, expiration_time); + + if (uac & UAC_ACCOUNTDISABLE) { + + ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO, + sizeof(AD_DISABLE_MESSAGE), + (const uint8_t *) AD_DISABLE_MESSAGE); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return ERR_ACCESS_DENIED; + + } else if (ad_account_expired(expiration_time)) { + + ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO, + sizeof(AD_EXPIRED_MESSAGE), + (const uint8_t *) AD_EXPIRED_MESSAGE); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return ERR_ACCOUNT_EXPIRED; + } + + return EOK; +} + +#define RHDS_LOCK_MSG "The user account is locked on the server" + +static errno_t sdap_account_expired_rhds(struct pam_data *pd, + struct ldb_message *user_entry) +{ + bool locked; + int ret; + + DEBUG(SSSDBG_TRACE_FUNC, + "Performing RHDS access check for user [%s]\n", pd->user); + + locked = ldb_msg_find_attr_as_bool(user_entry, SYSDB_NS_ACCOUNT_LOCK, false); + DEBUG(SSSDBG_TRACE_ALL, "Account for user [%s] is%s locked.\n", pd->user, + locked ? "" : " not" ); + + if (locked) { + ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO, + sizeof(RHDS_LOCK_MSG), + (const uint8_t *) RHDS_LOCK_MSG); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return ERR_ACCESS_DENIED; + } + + return EOK; +} + +#define NDS_DISABLE_MSG "The user account is disabled on the server" +#define NDS_EXPIRED_MSG "The user account is expired" +#define NDS_TIME_MAP_MSG "The user account is not allowed at this time" + +bool nds_check_expired(const char *exp_time_str) +{ + time_t expire_time; + time_t now; + errno_t ret; + + if (exp_time_str == NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "ndsLoginExpirationTime is not set, access granted.\n"); + return false; + } + + ret = sss_utc_to_time_t(exp_time_str, "%Y%m%d%H%M%SZ", + &expire_time); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "sss_utc_to_time_t failed with %d:%s.\n", + ret, sss_strerror(ret)); + return true; + } + + now = time(NULL); + DEBUG(SSSDBG_TRACE_ALL, + "Time info: tzname[0] [%s] tzname[1] [%s] timezone [%ld] " + "daylight [%d] now [%"SPRItime"] expire_time [%"SPRItime"].\n", + tzname[0], tzname[1], timezone, daylight, now, expire_time); + + if (difftime(now, expire_time) > 0.0) { + DEBUG(SSSDBG_CONF_SETTINGS, "NDS account expired.\n"); + return true; + } + + return false; +} + +/* There is no real documentation of the byte string value of + * loginAllowedTimeMap, but some good example code in + * http://http://developer.novell.com/documentation/samplecode/extjndi_sample/CheckBind.java.html + */ +static bool nds_check_time_map(const struct ldb_val *time_map) +{ + time_t now; + struct tm *tm_now; + size_t map_index; + div_t q; + uint8_t mask = 0; + + if (time_map == NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "loginAllowedTimeMap is missing, access granted.\n"); + return false; + } + + if (time_map->length != 42) { + DEBUG(SSSDBG_FUNC_DATA, + "Allowed time map has the wrong size, " + "got [%zu], expected 42.\n", time_map->length); + return true; + } + + now = time(NULL); + tm_now = gmtime(&now); + + map_index = tm_now->tm_wday * 48 + tm_now->tm_hour * 2 + + (tm_now->tm_min < 30 ? 0 : 1); + + if (map_index > 335) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected index value [%zu] for time map.\n", map_index); + return true; + } + + q = div(map_index, 8); + + if (q.quot > 41 || q.quot < 0 || q.rem > 7 || q.rem < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected result of div(), [%zu][%d][%d].\n", + map_index, q.quot, q.rem); + return true; + } + + if (q.rem > 0) { + mask = 1 << q.rem; + } + + if (time_map->data[q.quot] & mask) { + DEBUG(SSSDBG_CONF_SETTINGS, "Access allowed by time map.\n"); + return false; + } + + return true; +} + +static errno_t sdap_account_expired_nds(struct pam_data *pd, + struct ldb_message *user_entry) +{ + bool locked = true; + int ret; + const char *exp_time_str; + const struct ldb_val *time_map; + + DEBUG(SSSDBG_TRACE_FUNC, + "Performing NDS access check for user [%s]\n", pd->user); + + locked = ldb_msg_find_attr_as_bool(user_entry, SYSDB_NDS_LOGIN_DISABLED, + false); + DEBUG(SSSDBG_TRACE_ALL, "Account for user [%s] is%s disabled.\n", pd->user, + locked ? "" : " not"); + + if (locked) { + ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO, + sizeof(NDS_DISABLE_MSG), + (const uint8_t *) NDS_DISABLE_MSG); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return ERR_ACCESS_DENIED; + + } else { + exp_time_str = ldb_msg_find_attr_as_string(user_entry, + SYSDB_NDS_LOGIN_EXPIRATION_TIME, + NULL); + locked = nds_check_expired(exp_time_str); + + DEBUG(SSSDBG_TRACE_ALL, + "Account for user [%s] is%s expired.\n", pd->user, + locked ? "" : " not"); + + if (locked) { + ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO, + sizeof(NDS_EXPIRED_MSG), + (const uint8_t *) NDS_EXPIRED_MSG); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return ERR_ACCESS_DENIED; + + } else { + time_map = ldb_msg_find_ldb_val(user_entry, + SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP); + + locked = nds_check_time_map(time_map); + + DEBUG(SSSDBG_TRACE_ALL, + "Account for user [%s] is%s locked at this time.\n", + pd->user, locked ? "" : " not"); + + if (locked) { + ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO, + sizeof(NDS_TIME_MAP_MSG), + (const uint8_t *) NDS_TIME_MAP_MSG); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return ERR_ACCESS_DENIED; + } + } + } + + return EOK; +} + +static errno_t sdap_account_expired(struct sdap_access_ctx *access_ctx, + struct pam_data *pd, + struct ldb_message *user_entry) +{ + const char *expire; + int ret; + + expire = dp_opt_get_cstring(access_ctx->id_ctx->opts->basic, + SDAP_ACCOUNT_EXPIRE_POLICY); + if (expire == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing account expire policy. Access denied\n"); + return ERR_ACCESS_DENIED; + } else { + if (strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_SHADOW) == 0) { + ret = sdap_account_expired_shadow(pd, user_entry); + if (ret == ERR_ACCOUNT_EXPIRED) { + DEBUG(SSSDBG_TRACE_FUNC, + "sdap_account_expired_shadow: %s.\n", sss_strerror(ret)); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_account_expired_shadow failed.\n"); + } + } else if (strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_AD) == 0) { + ret = sdap_account_expired_ad(pd, user_entry); + if (ret == ERR_ACCOUNT_EXPIRED || ret == ERR_ACCESS_DENIED) { + DEBUG(SSSDBG_TRACE_FUNC, + "sdap_account_expired_ad: %s.\n", sss_strerror(ret)); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_account_expired_ad failed.\n"); + } + } else if (strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_RHDS) == 0 || + strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_IPA) == 0 || + strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_389DS) == 0) { + ret = sdap_account_expired_rhds(pd, user_entry); + if (ret == ERR_ACCESS_DENIED) { + DEBUG(SSSDBG_TRACE_FUNC, + "sdap_account_expired_rhds: %s.\n", sss_strerror(ret)); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_account_expired_rhds failed.\n"); + } + + if (ret == EOK && + strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_IPA) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "IPA access control succeeded, checking AD " + "access control\n"); + ret = sdap_account_expired_ad(pd, user_entry); + if (ret == ERR_ACCOUNT_EXPIRED || ret == ERR_ACCESS_DENIED) { + DEBUG(SSSDBG_TRACE_FUNC, + "sdap_account_expired_ad: %s.\n", sss_strerror(ret)); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_account_expired_ad failed.\n"); + } + } + } else if (strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_NDS) == 0) { + ret = sdap_account_expired_nds(pd, user_entry); + if (ret == ERR_ACCESS_DENIED) { + DEBUG(SSSDBG_TRACE_FUNC, + "sdap_account_expired_nds: %s.\n", sss_strerror(ret)); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_account_expired_nds failed.\n"); + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported LDAP account expire policy [%s]. " + "Access denied.\n", expire); + ret = ERR_ACCESS_DENIED; + } + } + + return ret; +} + +static errno_t perform_pwexpire_policy(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct pam_data *pd, + enum sdap_access_type access_type, + struct sdap_options *opts) +{ + enum pwexpire pw_expire_type; + void *pw_expire_data; + errno_t ret; + char *dn; + + ret = get_user_dn(mem_ctx, domain, access_type, opts, pd->user, &dn, + &pw_expire_type, &pw_expire_data); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "get_user_dn returned %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = check_pwexpire_policy(pw_expire_type, pw_expire_data, pd, + domain->pwd_expiration_warning); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "check_pwexpire_policy returned %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + return ret; +} + +struct sdap_access_filter_req_ctx { + const char *username; + const char *filter; + struct tevent_context *ev; + struct sdap_access_ctx *access_ctx; + struct sdap_options *opts; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *sdap_op; + struct sysdb_handle *handle; + struct sss_domain_info *domain; + /* cached result of access control checks */ + bool cached_access; + const char *basedn; +}; + +static errno_t sdap_access_decide_offline(bool cached_ac); +static int sdap_access_filter_retry(struct tevent_req *req); +static void sdap_access_ppolicy_connect_done(struct tevent_req *subreq); +static errno_t sdap_access_ppolicy_get_lockout_step(struct tevent_req *req); +static void sdap_access_filter_connect_done(struct tevent_req *subreq); +static void sdap_access_filter_done(struct tevent_req *req); +static struct tevent_req *sdap_access_filter_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + struct sdap_access_ctx *access_ctx, + struct sdap_id_conn_ctx *conn, + const char *username, + struct ldb_message *user_entry) +{ + struct sdap_access_filter_req_ctx *state; + struct tevent_req *req; + char *clean_username; + errno_t ret = ERR_INTERNAL; + char *name; + + req = tevent_req_create(mem_ctx, &state, struct sdap_access_filter_req_ctx); + if (req == NULL) { + return NULL; + } + + if (access_ctx->filter == NULL || *access_ctx->filter == '\0') { + /* If no filter is set, default to restrictive */ + DEBUG(SSSDBG_TRACE_FUNC, "No filter set. Access is denied.\n"); + ret = ERR_ACCESS_DENIED; + goto done; + } + + state->filter = NULL; + state->username = username; + state->opts = access_ctx->id_ctx->opts; + state->conn = conn; + state->ev = ev; + state->access_ctx = access_ctx; + state->domain = domain; + + DEBUG(SSSDBG_TRACE_FUNC, + "Performing access filter check for user [%s]\n", username); + + state->cached_access = ldb_msg_find_attr_as_bool(user_entry, + SYSDB_LDAP_ACCESS_FILTER, + false); + + /* Ok, we have one result, check if we are online or offline */ + if (be_is_offline(be_ctx)) { + /* Ok, we're offline. Return from the cache */ + ret = sdap_access_decide_offline(state->cached_access); + goto done; + } + + ret = sdap_get_basedn_user_entry(user_entry, state->username, + &state->basedn); + if (ret != EOK) { + goto done; + } + + /* Construct the filter */ + ret = sss_parse_internal_fqname(state, username, &name, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not parse [%s] into name and " + "domain components, access might fail\n", username); + name = discard_const(username); + } + + ret = sss_filter_sanitize(state, name, &clean_username); + if (ret != EOK) { + goto done; + } + + state->filter = talloc_asprintf( + state, + "(&(%s=%s)(objectclass=%s)%s)", + state->opts->user_map[SDAP_AT_USER_NAME].name, + clean_username, + state->opts->user_map[SDAP_OC_USER].name, + state->access_ctx->filter); + if (state->filter == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not construct access filter\n"); + ret = ENOMEM; + goto done; + } + talloc_zfree(clean_username); + + DEBUG(SSSDBG_TRACE_FUNC, "Checking filter against LDAP\n"); + + state->sdap_op = sdap_id_op_create(state, + state->conn->conn_cache); + if (!state->sdap_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + ret = sdap_access_filter_retry(req); + if (ret != EOK) { + goto done; + } + + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +/* Helper function, + * cached_ac => access granted + * !cached_ac => access denied + */ +static errno_t sdap_access_decide_offline(bool cached_ac) +{ + if (cached_ac) { + DEBUG(SSSDBG_TRACE_FUNC, "Access granted by cached credentials\n"); + return EOK; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Access denied by cached credentials\n"); + return ERR_ACCESS_DENIED; + } +} + +static int sdap_access_filter_retry(struct tevent_req *req) +{ + struct sdap_access_filter_req_ctx *state = + tevent_req_data(req, struct sdap_access_filter_req_ctx); + struct tevent_req *subreq; + int ret; + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_id_op_connect_send failed: %d (%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, sdap_access_filter_connect_done, req); + return EOK; +} + +static void sdap_access_filter_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_access_filter_req_ctx *state = + tevent_req_data(req, struct sdap_access_filter_req_ctx); + int ret, dp_error; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + ret = sdap_access_decide_offline(state->cached_access); + if (ret == EOK) { + tevent_req_done(req); + return; + } + } + + tevent_req_error(req, ret); + return; + } + + /* Connection to LDAP succeeded + * Send filter request + */ + subreq = sdap_get_generic_send(state, + state->ev, + state->opts, + sdap_id_op_handle(state->sdap_op), + state->basedn, + LDAP_SCOPE_BASE, + state->filter, NULL, + NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not start LDAP communication\n"); + tevent_req_error(req, EIO); + return; + } + + tevent_req_set_callback(subreq, sdap_access_filter_done, req); +} + +static void sdap_access_filter_done(struct tevent_req *subreq) +{ + int ret, tret, dp_error; + size_t num_results; + bool found = false; + struct sysdb_attrs **results; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_access_filter_req_ctx *state = + tevent_req_data(req, struct sdap_access_filter_req_ctx); + + ret = sdap_get_generic_recv(subreq, state, + &num_results, &results); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (ret != EOK) { + if (dp_error == DP_ERR_OK) { + /* retry */ + tret = sdap_access_filter_retry(req); + if (tret == EOK) { + return; + } + } else if (dp_error == DP_ERR_OFFLINE) { + ret = sdap_access_decide_offline(state->cached_access); + } else if (ret == ERR_INVALID_FILTER) { + sss_log(SSS_LOG_ERR, MALFORMED_FILTER, state->filter); + DEBUG(SSSDBG_CRIT_FAILURE, MALFORMED_FILTER, state->filter); + ret = ERR_ACCESS_DENIED; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_send() returned error [%d][%s]\n", + ret, sss_strerror(ret)); + } + + goto done; + } + + /* Check the number of responses we got + * If it's exactly 1, we passed the check + * If it's < 1, we failed the check + * Anything else is an error + */ + if (num_results < 1) { + DEBUG(SSSDBG_CONF_SETTINGS, + "User [%s] was not found with the specified filter. " + "Denying access.\n", state->username); + found = false; + } + else if (results == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "num_results > 0, but results is NULL\n"); + ret = ERR_INTERNAL; + goto done; + } + else if (num_results > 1) { + /* It should not be possible to get more than one reply + * here, since we're doing a base-scoped search + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Received multiple replies\n"); + ret = ERR_INTERNAL; + goto done; + } + else { /* Ok, we got a single reply */ + found = true; + } + + if (found) { + /* Save "allow" to the cache for future offline access checks. */ + DEBUG(SSSDBG_TRACE_FUNC, "Access granted by online lookup\n"); + ret = EOK; + } + else { + /* Save "disallow" to the cache for future offline + * access checks. + */ + DEBUG(SSSDBG_TRACE_FUNC, "Access denied by online lookup\n"); + ret = ERR_ACCESS_DENIED; + } + + tret = sdap_save_user_cache_bool(state->domain, state->username, + SYSDB_LDAP_ACCESS_FILTER, found); + if (tret != EOK) { + /* Failing to save to the cache is non-fatal. + * Just return the result. + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set user access attribute\n"); + goto done; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } + else { + tevent_req_error(req, ret); + } +} + +static errno_t sdap_access_filter_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +#define AUTHR_SRV_MISSING_MSG "Authorized service attribute missing, " \ + "access denied" +#define AUTHR_SRV_DENY_MSG "Access denied by authorized service attribute" +#define AUTHR_SRV_NO_MATCH_MSG "Authorized service attribute has " \ + "no matching rule, access denied" + +static errno_t sdap_access_service(struct pam_data *pd, + struct ldb_message *user_entry) +{ + errno_t ret, tret; + struct ldb_message_element *el; + unsigned int i; + char *service; + + el = ldb_msg_find_element(user_entry, SYSDB_AUTHORIZED_SERVICE); + if (!el || el->num_values == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing authorized services. Access denied\n"); + + tret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO, + sizeof(AUTHR_SRV_MISSING_MSG), + (const uint8_t *) AUTHR_SRV_MISSING_MSG); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return ERR_ACCESS_DENIED; + } + + ret = ENOENT; + + for (i = 0; i < el->num_values; i++) { + service = (char *)el->values[i].data; + if (service[0] == '!' && + strcasecmp(pd->service, service+1) == 0) { + /* This service is explicitly denied */ + DEBUG(SSSDBG_CONF_SETTINGS, "Access denied by [%s]\n", service); + + tret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO, + sizeof(AUTHR_SRV_DENY_MSG), + (const uint8_t *) AUTHR_SRV_DENY_MSG); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + /* A denial trumps all. Break here */ + return ERR_ACCESS_DENIED; + + } else if (strcasecmp(pd->service, service) == 0) { + /* This service is explicitly allowed */ + DEBUG(SSSDBG_CONF_SETTINGS, "Access granted for [%s]\n", service); + /* We still need to loop through to make sure + * that it's not also explicitly denied + */ + ret = EOK; + } else if (strcmp("*", service) == 0) { + /* This user has access to all services */ + DEBUG(SSSDBG_CONF_SETTINGS, "Access granted to all services\n"); + /* We still need to loop through to make sure + * that it's not also explicitly denied + */ + ret = EOK; + } + } + + if (ret == ENOENT) { + DEBUG(SSSDBG_CONF_SETTINGS, "No matching service rule found\n"); + + tret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO, + sizeof(AUTHR_SRV_NO_MATCH_MSG), + (const uint8_t *) AUTHR_SRV_NO_MATCH_MSG); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + ret = ERR_ACCESS_DENIED; + } + + return ret; +} + +static errno_t sdap_save_user_cache_bool(struct sss_domain_info *domain, + const char *username, + const char *attr_name, + bool value) +{ + errno_t ret; + struct sysdb_attrs *attrs; + + attrs = sysdb_new_attrs(NULL); + if (attrs == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Could not create attrs\n"); + goto done; + } + + ret = sysdb_attrs_add_bool(attrs, attr_name, value); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not set up attr value\n"); + goto done; + } + + ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set user access attribute\n"); + goto done; + } + +done: + talloc_free(attrs); + return ret; +} + +static errno_t sdap_access_host_comp(struct ldb_message_element *el, char *hostname) +{ + errno_t ret = ENOENT; + unsigned int i; + char *host; + + for (i = 0; i < el->num_values; i++) { + host = (char *)el->values[i].data; + if (host[0] == '!' && + strcasecmp(hostname, host+1) == 0) { + /* This host is explicitly denied */ + DEBUG(SSSDBG_CONF_SETTINGS, "Access denied by [%s]\n", host); + /* A denial trumps all. Break here */ + return ERR_ACCESS_DENIED; + + } else if (strcasecmp(hostname, host) == 0) { + /* This host is explicitly allowed */ + DEBUG(SSSDBG_CONF_SETTINGS, "Access granted for [%s]\n", host); + /* We still need to loop through to make sure + * that it's not also explicitly denied + */ + ret = EOK; + } else if (strcmp("*", host) == 0) { + /* This user has access to all hosts */ + DEBUG(SSSDBG_CONF_SETTINGS, "Access granted to all hosts\n"); + /* We still need to loop through to make sure + * that it's not also explicitly denied + */ + ret = EOK; + } + } + return ret; +} + +static errno_t sdap_access_host(struct ldb_message *user_entry) +{ + errno_t ret; + struct ldb_message_element *el; + char hostname[HOST_NAME_MAX + 1]; + struct addrinfo *res = NULL; + struct addrinfo hints; + + el = ldb_msg_find_element(user_entry, SYSDB_AUTHORIZED_HOST); + if (!el || el->num_values == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing hosts. Access denied\n"); + return ERR_ACCESS_DENIED; + } + + if (gethostname(hostname, sizeof(hostname)) == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to get system hostname. Access denied\n"); + return ERR_ACCESS_DENIED; + } + hostname[HOST_NAME_MAX] = '\0'; + + /* Canonicalize the hostname */ + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_CANONNAME; + ret = getaddrinfo(hostname, NULL, &hints, &res); + if (ret != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to canonicalize hostname\n"); + freeaddrinfo(res); + res = NULL; + } + + ret = sdap_access_host_comp(el, hostname); + if (ret == ENOENT && res != NULL && res->ai_canonname != NULL) { + ret = sdap_access_host_comp(el, res->ai_canonname); + } + freeaddrinfo(res); + + if (ret == ENOENT) { + DEBUG(SSSDBG_CONF_SETTINGS, "No matching host rule found\n"); + ret = ERR_ACCESS_DENIED; + } + + return ret; +} + +errno_t sdap_access_rhost(struct ldb_message *user_entry, char *pam_rhost) +{ + errno_t ret; + struct ldb_message_element *el; + char *be_rhost_rule; + unsigned int i; + + /* If user_entry is NULL do not perform any checks */ + if (user_entry == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "user_entry is NULL, that is not possible, " + "so we just reject access\n"); + return ERR_ACCESS_DENIED; + } + + /* If pam_rhost is NULL do not perform any checks */ + if (pam_rhost == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pam_rhost is NULL, no rhost check is possible\n"); + return EOK; + } + + /* When the access is local we get empty string as pam_rhost + in which case we should not evaluate rhost access rules */ + /* FIXME: I think ideally should have LDAP to define what to do in + * this case */ + if (pam_rhost[0] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "pam_rhost is empty, possible local access, " + "no rhost check possible\n"); + return EOK; + } + + /* If rhost validation is enabled and entry has no relevant attribute - + * deny access */ + el = ldb_msg_find_element(user_entry, SYSDB_AUTHORIZED_RHOST); + if (!el || el->num_values == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "Missing rhost entries. Access denied\n"); + return ERR_ACCESS_DENIED; + } + + ret = ENOENT; + + for (i = 0; i < el->num_values; i++) { + be_rhost_rule = (char *)el->values[i].data; + if (be_rhost_rule[0] == '!' + && strcasecmp(pam_rhost, be_rhost_rule+1) == 0) { + /* This rhost is explicitly denied */ + DEBUG(SSSDBG_CONF_SETTINGS, + "Access from [%s] denied by [%s]\n", + pam_rhost, be_rhost_rule); + /* A denial trumps all. Break here */ + return ERR_ACCESS_DENIED; + } else if (strcasecmp(pam_rhost, be_rhost_rule) == 0) { + /* This rhost is explicitly allowed */ + DEBUG(SSSDBG_CONF_SETTINGS, + "Access from [%s] granted by [%s]\n", + pam_rhost, be_rhost_rule); + /* We still need to loop through to make sure + * that it's not also explicitly denied + */ + ret = EOK; + } else if (strcmp("*", be_rhost_rule) == 0) { + /* This user has access from anywhere */ + DEBUG(SSSDBG_CONF_SETTINGS, + "Access from [%s] granted by [*]\n", pam_rhost); + /* We still need to loop through to make sure + * that it's not also explicitly denied + */ + ret = EOK; + } + } + + if (ret == ENOENT) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No matching rhost rules found\n"); + ret = ERR_ACCESS_DENIED; + } + + return ret; +} + +static void sdap_access_ppolicy_get_lockout_done(struct tevent_req *subreq); +static int sdap_access_ppolicy_retry(struct tevent_req *req); +static errno_t sdap_access_ppolicy_step(struct tevent_req *req); +static void sdap_access_ppolicy_step_done(struct tevent_req *subreq); + +struct sdap_access_ppolicy_req_ctx { + const char *username; + const char *filter; + struct tevent_context *ev; + struct sdap_access_ctx *access_ctx; + struct sdap_options *opts; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *sdap_op; + struct sysdb_handle *handle; + struct sss_domain_info *domain; + /* cached results of access control checks */ + bool cached_access; + const char *basedn; + /* default DNs to ppolicy */ + const char **ppolicy_dns; + unsigned int ppolicy_dns_index; + enum sdap_pwpolicy_mode pwpol_mode; +}; + +static struct tevent_req * +sdap_access_ppolicy_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + struct sdap_access_ctx *access_ctx, + struct sdap_id_conn_ctx *conn, + const char *username, + struct ldb_message *user_entry, + enum sdap_pwpolicy_mode pwpol_mode) +{ + struct sdap_access_ppolicy_req_ctx *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, + &state, struct sdap_access_ppolicy_req_ctx); + if (req == NULL) { + return NULL; + } + + state->filter = NULL; + state->username = username; + state->opts = access_ctx->id_ctx->opts; + state->conn = conn; + state->ev = ev; + state->access_ctx = access_ctx; + state->domain = domain; + state->ppolicy_dns_index = 0; + state->pwpol_mode = pwpol_mode; + + DEBUG(SSSDBG_TRACE_FUNC, + "Performing access ppolicy check for user [%s]\n", username); + + state->cached_access = ldb_msg_find_attr_as_bool( + user_entry, SYSDB_LDAP_ACCESS_CACHED_LOCKOUT, false); + + /* Ok, we have one result, check if we are online or offline */ + if (be_is_offline(be_ctx)) { + /* Ok, we're offline. Return from the cache */ + ret = sdap_access_decide_offline(state->cached_access); + goto done; + } + + ret = sdap_get_basedn_user_entry(user_entry, state->username, + &state->basedn); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Checking ppolicy against LDAP\n"); + + state->sdap_op = sdap_id_op_create(state, + state->conn->conn_cache); + if (!state->sdap_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + ret = sdap_access_ppolicy_retry(req); + if (ret != EOK) { + goto done; + } + + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static int sdap_access_ppolicy_retry(struct tevent_req *req) +{ + struct sdap_access_ppolicy_req_ctx *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx); + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_id_op_connect_send failed: %d (%s)\n", + ret, sss_strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, sdap_access_ppolicy_connect_done, req); + return EOK; +} + +static const char** +get_default_ppolicy_dns(TALLOC_CTX *mem_ctx, struct sdap_domain *sdom) +{ + const char **ppolicy_dns; + int count = 0; + int i; + + while(sdom->search_bases[count] != NULL) { + count++; + } + + /* +1 to have space for final NULL */ + ppolicy_dns = talloc_array(mem_ctx, const char*, count + 1); + + for(i = 0; i < count; i++) { + ppolicy_dns[i] = talloc_asprintf(mem_ctx, "cn=ppolicy,ou=policies,%s", + sdom->search_bases[i]->basedn); + } + + ppolicy_dns[count] = NULL; + return ppolicy_dns; +} + +static void sdap_access_ppolicy_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_access_ppolicy_req_ctx *state; + int ret, dp_error; + const char *ppolicy_dn; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + ret = sdap_access_decide_offline(state->cached_access); + if (ret == EOK) { + tevent_req_done(req); + return; + } + } + + tevent_req_error(req, ret); + return; + } + + ppolicy_dn = dp_opt_get_string(state->opts->basic, + SDAP_PWDLOCKOUT_DN); + + /* option was configured */ + if (ppolicy_dn != NULL) { + state->ppolicy_dns = talloc_array(state, const char*, 2); + if (state->ppolicy_dns == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not allocate ppolicy_dns.\n"); + tevent_req_error(req, ERR_INTERNAL); + return; + } + + state->ppolicy_dns[0] = ppolicy_dn; + state->ppolicy_dns[1] = NULL; + + } else { + /* try to determine default value */ + DEBUG(SSSDBG_CONF_SETTINGS, + "ldap_pwdlockout_dn was not defined in configuration file.\n"); + + state->ppolicy_dns = get_default_ppolicy_dns(state, state->opts->sdom); + if (state->ppolicy_dns == NULL) { + tevent_req_error(req, ERR_INTERNAL); + return; + } + } + + /* Connection to LDAP succeeded + * Send 'pwdLockout' request + */ + ret = sdap_access_ppolicy_get_lockout_step(req); + if (ret != EOK && ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_access_ppolicy_get_lockout_step failed: [%d][%s]\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ERR_INTERNAL); + return; + } + + if (ret == EOK) { + tevent_req_done(req); + } +} + +static errno_t +sdap_access_ppolicy_get_lockout_step(struct tevent_req *req) +{ + const char *attrs[] = { SYSDB_LDAP_ACCESS_LOCKOUT, NULL }; + struct sdap_access_ppolicy_req_ctx *state; + struct tevent_req *subreq; + errno_t ret; + + state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx); + + /* no more DNs to try */ + if (state->ppolicy_dns[state->ppolicy_dns_index] == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No more DNs to try.\n"); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Trying to find out if ppolicy is enabled using the DN: %s\n", + state->ppolicy_dns[state->ppolicy_dns_index]); + + subreq = sdap_get_generic_send(state, + state->ev, + state->opts, + sdap_id_op_handle(state->sdap_op), + state->ppolicy_dns[state->ppolicy_dns_index], + LDAP_SCOPE_BASE, + NULL, attrs, + NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not start LDAP communication\n"); + ret = EIO; + goto done; + } + + /* try next basedn */ + state->ppolicy_dns_index++; + tevent_req_set_callback(subreq, sdap_access_ppolicy_get_lockout_done, req); + + ret = EAGAIN; + +done: + return ret; +} + +static void sdap_access_ppolicy_get_lockout_done(struct tevent_req *subreq) +{ + int ret, tret, dp_error; + size_t num_results; + bool pwdLockout = false; + struct sysdb_attrs **results; + struct tevent_req *req; + struct sdap_access_ppolicy_req_ctx *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx); + + ret = sdap_get_generic_recv(subreq, state, &num_results, &results); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot retrieve ppolicy\n"); + ret = ERR_NETWORK_IO; + goto done; + } + + /* Check the number of responses we got + * If it's exactly 1, we passed the check + * If it's < 1, we failed the check + * Anything else is an error + */ + /* Didn't find ppolicy attribute */ + if (num_results < 1) { + /* Try using next $search_base */ + ret = sdap_access_ppolicy_get_lockout_step(req); + if (ret == EOK) { + /* No more search bases to try */ + DEBUG(SSSDBG_CONF_SETTINGS, + "[%s] was not found. Granting access.\n", + SYSDB_LDAP_ACCESS_LOCKOUT); + } else { + if (ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_access_ppolicy_get_lockout_step failed: " + "[%d][%s]\n", + ret, sss_strerror(ret)); + } + goto done; + } + } else if (results == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "num_results > 0, but results is NULL\n"); + ret = ERR_INTERNAL; + goto done; + } else if (num_results > 1) { + /* It should not be possible to get more than one reply + * here, since we're doing a base-scoped search + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Received multiple replies\n"); + ret = ERR_INTERNAL; + goto done; + } else { /* Ok, we got a single reply */ + ret = sysdb_attrs_get_bool(results[0], SYSDB_LDAP_ACCESS_LOCKOUT, + &pwdLockout); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error reading %s: [%s]\n", SYSDB_LDAP_ACCESS_LOCKOUT, + sss_strerror(ret)); + ret = ERR_INTERNAL; + goto done; + } + } + + if (pwdLockout) { + DEBUG(SSSDBG_TRACE_FUNC, + "Password policy is enabled on LDAP server.\n"); + + /* ppolicy is enabled => find out if account is locked */ + ret = sdap_access_ppolicy_step(req); + if (ret != EOK && ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_access_ppolicy_step failed: [%d][%s].\n", + ret, sss_strerror(ret)); + } + goto done; + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Password policy is disabled on LDAP server " + "- storing 'access granted' in sysdb.\n"); + tret = sdap_save_user_cache_bool(state->domain, state->username, + SYSDB_LDAP_ACCESS_CACHED_LOCKOUT, + true); + if (tret != EOK) { + /* Failing to save to the cache is non-fatal. + * Just return the result. + */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set user locked attribute\n"); + goto done; + } + + ret = EOK; + goto done; + } + +done: + if (ret != EAGAIN) { + /* release connection */ + tret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_send() returned error [%d][%s]\n", + ret, sss_strerror(ret)); + } + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + } +} + +errno_t sdap_access_ppolicy_step(struct tevent_req *req) +{ + errno_t ret; + struct tevent_req *subreq; + struct sdap_access_ppolicy_req_ctx *state; + const char *attrs[] = { SYSDB_LDAP_ACCESS_LOCKED_TIME, + SYSDB_LDAP_ACESS_LOCKOUT_DURATION, + NULL }; + + state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx); + + subreq = sdap_get_generic_send(state, + state->ev, + state->opts, + sdap_id_op_handle(state->sdap_op), + state->basedn, + LDAP_SCOPE_BASE, + NULL, attrs, + NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_access_ppolicy_step_done, req); + ret = EAGAIN; + +done: + return ret; +} + +static errno_t +is_account_locked(const char *pwdAccountLockedTime, + const char *pwdAccountLockedDurationTime, + enum sdap_pwpolicy_mode pwpol_mode, + const char *username, + bool *_locked) +{ + errno_t ret; + time_t lock_time; + time_t duration; + time_t now; + bool locked; + char *endptr; + + /* Default action is to consider account to be locked. */ + locked = true; + + /* account is permanently locked */ + if (strcasecmp(pwdAccountLockedTime, + PERMANENTLY_LOCKED_ACCOUNT) == 0) { + ret = EOK; + goto done; + } + + switch(pwpol_mode) { + case PWP_LOCKOUT_ONLY: + /* We do *not* care about exact value of account locked time, we + * only *do* care if the value is equal to + * PERMANENTLY_LOCKED_ACCOUNT, which means that account is locked + * permanently. + */ + DEBUG(SSSDBG_TRACE_FUNC, + "Account of: %s is being blocked by password policy, " + "but value: [%s] value is ignored by SSSD.\n", + username, pwdAccountLockedTime); + locked = false; + break; + case PWP_LOCKOUT_EXPIRE: + /* Account may be locked out from natural reasons (too many attempts, + * expired password). In this case, pwdAccountLockedTime is also set, + * to the time of lock out. + */ + ret = sss_utc_to_time_t(pwdAccountLockedTime, "%Y%m%d%H%M%SZ", + &lock_time); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "sss_utc_to_time_t failed with %d:%s.\n", + ret, sss_strerror(ret)); + goto done; + } + + now = time(NULL); + + /* Account was NOT locked in past. */ + if (difftime(lock_time, now) > 0.0) { + locked = false; + } else if (pwdAccountLockedDurationTime != NULL) { + duration = strtouint32(pwdAccountLockedDurationTime, &endptr, 0); + if (errno || *endptr) { + ret = errno ? errno : EINVAL; + goto done; + } + /* Lockout has expired */ + if (duration != 0 && difftime(now, lock_time) > duration) { + locked = false; + } + } + break; + case PWP_SENTINEL: + default: + DEBUG(SSSDBG_MINOR_FAILURE, + "Unexpected value of password policy mode: %d.\n", pwpol_mode); + ret = EINVAL; + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *_locked = locked; + } + + return ret; +} + +static void sdap_access_ppolicy_step_done(struct tevent_req *subreq) +{ + int ret, tret, dp_error; + size_t num_results; + bool locked = false; + const char *pwdAccountLockedTime; + const char *pwdAccountLockedDurationTime; + struct sysdb_attrs **results; + struct tevent_req *req; + struct sdap_access_ppolicy_req_ctx *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx); + + ret = sdap_get_generic_recv(subreq, state, &num_results, &results); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (ret != EOK) { + if (dp_error == DP_ERR_OK) { + /* retry */ + tret = sdap_access_ppolicy_retry(req); + if (tret == EOK) { + return; + } + } else if (dp_error == DP_ERR_OFFLINE) { + ret = sdap_access_decide_offline(state->cached_access); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_id_op_done() returned error [%d][%s]\n", + ret, sss_strerror(ret)); + } + + goto done; + } + + /* Check the number of responses we got + * If it's exactly 1, we passed the check + * If it's < 1, we failed the check + * Anything else is an error + */ + if (num_results < 1) { + DEBUG(SSSDBG_CONF_SETTINGS, + "User [%s] was not found with the specified filter. " + "Denying access.\n", state->username); + } else if (results == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "num_results > 0, but results is NULL\n"); + ret = ERR_INTERNAL; + goto done; + } else if (num_results > 1) { + /* It should not be possible to get more than one reply + * here, since we're doing a base-scoped search + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Received multiple replies\n"); + ret = ERR_INTERNAL; + goto done; + } else { /* Ok, we got a single reply */ + ret = sysdb_attrs_get_string(results[0], SYSDB_LDAP_ACESS_LOCKOUT_DURATION, + &pwdAccountLockedDurationTime); + if (ret != EOK) { + /* This attribute might not be set even if account is locked */ + pwdAccountLockedDurationTime = NULL; + } + + ret = sysdb_attrs_get_string(results[0], SYSDB_LDAP_ACCESS_LOCKED_TIME, + &pwdAccountLockedTime); + if (ret == EOK) { + + ret = is_account_locked(pwdAccountLockedTime, + pwdAccountLockedDurationTime, + state->pwpol_mode, + state->username, + &locked); + if (ret != EOK) { + if (ret == ERR_TIMESPEC_NOT_SUPPORTED) { + DEBUG(SSSDBG_MINOR_FAILURE, + "timezone specifier in ppolicy is not supported\n"); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "is_account_locked failed: %d:[%s].\n", + ret, sss_strerror(ret)); + } + + DEBUG(SSSDBG_MINOR_FAILURE, + "Account will be considered to be locked.\n"); + locked = true; + } + } else { + /* Attribute SYSDB_LDAP_ACCESS_LOCKED_TIME in not be present unless + * user's account is blocked by password policy. + */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Attribute %s failed to be obtained - [%d][%s].\n", + SYSDB_LDAP_ACCESS_LOCKED_TIME, ret, strerror(ret)); + } + } + + if (locked) { + DEBUG(SSSDBG_TRACE_FUNC, + "Access denied by online lookup - account is locked.\n"); + ret = ERR_ACCESS_DENIED; + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Access granted by online lookup - account is not locked.\n"); + ret = EOK; + } + + /* Save '!locked' to the cache for future offline access checks. + * Locked == true => access denied, + * Locked == false => access granted + */ + tret = sdap_save_user_cache_bool(state->domain, state->username, + SYSDB_LDAP_ACCESS_CACHED_LOCKOUT, + !locked); + + if (tret != EOK) { + /* Failing to save to the cache is non-fatal. + * Just return the result. + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set user locked attribute\n"); + goto done; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t sdap_access_ppolicy_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t sdap_get_basedn_user_entry(struct ldb_message *user_entry, + const char *username, + const char **_basedn) +{ + const char *basedn; + errno_t ret; + + basedn = ldb_msg_find_attr_as_string(user_entry, SYSDB_ORIG_DN, NULL); + if (basedn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE,"Could not find originalDN for user [%s]\n", + username); + ret = EINVAL; + goto done; + } + + *_basedn = basedn; + ret = EOK; + +done: + return ret; +} + +static errno_t get_access_filter(TALLOC_CTX *mem_ctx, + struct dp_option *opts, + const char **_filter) +{ + const char *filter; + + filter = dp_opt_get_cstring(opts, SDAP_ACCESS_FILTER); + if (filter == NULL) { + /* It's okay if this is NULL. In that case we will simply act + * like the 'deny' provider. + */ + DEBUG(SSSDBG_FATAL_FAILURE, + "Warning: LDAP access rule 'filter' is set, " + "but no ldap_access_filter configured. " + "All domain users will be denied access.\n"); + return EOK; + } + + filter = sdap_get_access_filter(mem_ctx, filter); + if (filter == NULL) { + return ENOMEM; + } + + *_filter = filter; + + return EOK; +} + +static errno_t check_expire_policy(struct dp_option *opts) +{ + const char *expire_policy; + bool matched_policy = false; + const char *policies[] = {LDAP_ACCOUNT_EXPIRE_SHADOW, + LDAP_ACCOUNT_EXPIRE_AD, + LDAP_ACCOUNT_EXPIRE_NDS, + LDAP_ACCOUNT_EXPIRE_RHDS, + LDAP_ACCOUNT_EXPIRE_IPA, + LDAP_ACCOUNT_EXPIRE_389DS, + NULL}; + + expire_policy = dp_opt_get_cstring(opts, SDAP_ACCOUNT_EXPIRE_POLICY); + if (expire_policy == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Warning: LDAP access rule 'expire' is set, " + "but no ldap_account_expire_policy configured. " + "All domain users will be denied access.\n"); + return EOK; + } + + for (unsigned i = 0; policies[i] != NULL; i++) { + if (strcasecmp(expire_policy, policies[i]) == 0) { + matched_policy = true; + break; + } + } + + if (matched_policy == false) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported LDAP account expire policy [%s].\n", + expire_policy); + return EINVAL; + } + + return EOK; +} + +/* Please use this only for short lists */ +static errno_t check_order_list_for_duplicates(char **list, + bool case_sensitive) +{ + size_t c; + size_t d; + int cmp; + + for (c = 0; list[c] != NULL; c++) { + for (d = c + 1; list[d] != NULL; d++) { + if (case_sensitive) { + cmp = strcmp(list[c], list[d]); + } else { + cmp = strcasecmp(list[c], list[d]); + } + if (cmp == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Duplicate string [%s] found.\n", list[c]); + return EINVAL; + } + } + } + + return EOK; +} + +static errno_t get_access_order_list(TALLOC_CTX *mem_ctx, + const char *order, + char ***_order_list) +{ + errno_t ret; + char **order_list; + int order_list_len; + + ret = split_on_separator(mem_ctx, order, ',', true, true, + &order_list, &order_list_len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "split_on_separator failed.\n"); + goto done; + } + + ret = check_order_list_for_duplicates(order_list, false); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "check_order_list_for_duplicates failed.\n"); + goto done; + } + + if (order_list_len > LDAP_ACCESS_LAST) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Currently only [%d] different access rules are supported.\n", + LDAP_ACCESS_LAST); + ret = EINVAL; + goto done; + } + + *_order_list = order_list; + +done: + if (ret != EOK) { + talloc_free(order_list); + } + + return ret; +} + + +static errno_t get_order_list(TALLOC_CTX *mem_ctx, + enum sdap_access_type type, + struct dp_option *opts, + char ***_order_list) +{ + errno_t ret = EOK; + const char *order; + + switch (type) { + case SDAP_TYPE_LDAP: + order = dp_opt_get_cstring(opts, SDAP_ACCESS_ORDER); + if (order == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_access_order not given, using 'filter'.\n"); + order = "filter"; + } + break; + case SDAP_TYPE_IPA: + order = dp_opt_get_cstring(opts, IPA_ACCESS_ORDER); + if (order == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_access_order not given, using 'expire'.\n"); + order = "expire"; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown sdap_access_type [%i].\n", type); + ret = EINVAL; + goto done; + } + + /* We can pass _order_list because it is the last operation. + * Should this ever change, this would require an intermediate variable and + * to assign the value to _order_list on success at the very last moment. */ + ret = get_access_order_list(mem_ctx, order, _order_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "get_access_order_list failed: [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + return ret; +} + + +static errno_t set_sdap_access_rules(TALLOC_CTX *mem_ctx, + struct dp_option *opts, + char **order_list, + struct sdap_access_ctx *access_ctx) +{ + int ret = EOK; + int c; + const char *filter = NULL; + + + for (c = 0; order_list[c] != NULL; c++) { + if (strcasecmp(order_list[c], LDAP_ACCESS_FILTER_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_FILTER; + if (get_access_filter(mem_ctx, opts, &filter) != EOK) { + goto done; + } + + } else if (strcasecmp(order_list[c], LDAP_ACCESS_EXPIRE_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE; + if (check_expire_policy(opts) != EOK) { + goto done; + } + + } else if (strcasecmp(order_list[c], LDAP_ACCESS_SERVICE_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_SERVICE; + } else if (strcasecmp(order_list[c], LDAP_ACCESS_HOST_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_HOST; + } else if (strcasecmp(order_list[c], LDAP_ACCESS_RHOST_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_RHOST; + } else if (strcasecmp(order_list[c], LDAP_ACCESS_LOCK_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_LOCKOUT; + } else if (strcasecmp(order_list[c], + LDAP_ACCESS_EXPIRE_POLICY_REJECT_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_REJECT; + } else if (strcasecmp(order_list[c], + LDAP_ACCESS_EXPIRE_POLICY_WARN_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_WARN; + } else if (strcasecmp(order_list[c], + LDAP_ACCESS_EXPIRE_POLICY_RENEW_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_RENEW; + } else if (strcasecmp(order_list[c], LDAP_ACCESS_PPOLICY_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_PPOLICY; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected access rule name [%s].\n", order_list[c]); + ret = EINVAL; + goto done; + } + } + access_ctx->access_rule[c] = LDAP_ACCESS_EMPTY; + if (c == 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "Warning: access_provider=ldap set, " + "but ldap_access_order is empty. " + "All domain users will be denied access.\n"); + } + +done: + if (ret == EOK) { + access_ctx->filter = filter; + } else { + talloc_zfree(filter); + } + + return ret; +} + + +static errno_t set_ipa_access_rules(TALLOC_CTX *mem_ctx, + struct dp_option *opts, + char **order_list, + struct sdap_access_ctx *access_ctx) +{ + int ret = EOK; + int c; + const char *filter = NULL; + + + for (c = 0; order_list[c] != NULL; c++) { + if (strcasecmp(order_list[c], LDAP_ACCESS_EXPIRE_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE; + if (check_expire_policy(opts) != EOK) { + goto done; + } + } else if (strcasecmp(order_list[c], + LDAP_ACCESS_EXPIRE_POLICY_REJECT_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_REJECT; + } else if (strcasecmp(order_list[c], + LDAP_ACCESS_EXPIRE_POLICY_WARN_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_WARN; + } else if (strcasecmp(order_list[c], + LDAP_ACCESS_EXPIRE_POLICY_RENEW_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_RENEW; + } else if (strcasecmp(order_list[c], LDAP_ACCESS_PPOLICY_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_PPOLICY; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected access rule name [%s].\n", order_list[c]); + ret = EINVAL; + goto done; + } + } + access_ctx->access_rule[c] = LDAP_ACCESS_EMPTY; + if (c == 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "Warning: access_provider=ipa set, " + "but ipa_access_order is empty. " + "All domain users will be denied access.\n"); + } + +done: + if (ret == EOK) { + access_ctx->filter = filter; + } else { + talloc_zfree(filter); + } + + return ret; +} + + +errno_t sdap_set_access_rules(TALLOC_CTX *mem_ctx, + struct sdap_access_ctx *access_ctx, + struct dp_option *opts, + struct dp_option *more_opts) +{ + errno_t ret; + char **order_list = NULL; + + ret = get_order_list(mem_ctx, access_ctx->type, opts, &order_list); + if (ret != EOK) { + goto done; + } + + switch (access_ctx->type) { + case SDAP_TYPE_LDAP: + ret = set_sdap_access_rules(mem_ctx, opts, order_list, access_ctx); + break; + case SDAP_TYPE_IPA: + ret = set_ipa_access_rules(mem_ctx, more_opts, order_list, access_ctx); + break; + default: + ret = EINVAL; + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid sdap_access_type [%i].\n", access_ctx->type); + break; + } + +done: + talloc_free(order_list); + return ret; +} diff --git a/src/providers/ldap/sdap_access.h b/src/providers/ldap/sdap_access.h new file mode 100644 index 0000000..2a13a9f --- /dev/null +++ b/src/providers/ldap/sdap_access.h @@ -0,0 +1,114 @@ +/* + SSSD + + sdap_access.h + + Authors: + Stephen Gallagher + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef SDAP_ACCESS_H_ +#define SDAP_ACCESS_H_ + +#include "providers/backend.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_id_op.h" + +/* Attributes in sysdb, used for caching last values of lockout or filter + * access control checks. + */ +#define SYSDB_LDAP_ACCESS_FILTER "ldap_access_filter_allow" +#define SYSDB_LDAP_ACCESS_CACHED_LOCKOUT "ldap_access_lockout_allow" +/* names of ppolicy attributes */ +#define SYSDB_LDAP_ACCESS_LOCKED_TIME "pwdAccountLockedTime" +#define SYSDB_LDAP_ACESS_LOCKOUT_DURATION "pwdLockoutDuration" +#define SYSDB_LDAP_ACCESS_LOCKOUT "pwdLockout" + +#define LDAP_ACCESS_FILTER_NAME "filter" +#define LDAP_ACCESS_EXPIRE_NAME "expire" +#define LDAP_ACCESS_EXPIRE_POLICY_REJECT_NAME "pwd_expire_policy_reject" +#define LDAP_ACCESS_EXPIRE_POLICY_WARN_NAME "pwd_expire_policy_warn" +#define LDAP_ACCESS_EXPIRE_POLICY_RENEW_NAME "pwd_expire_policy_renew" +#define LDAP_ACCESS_SERVICE_NAME "authorized_service" +#define LDAP_ACCESS_HOST_NAME "host" +#define LDAP_ACCESS_RHOST_NAME "rhost" +#define LDAP_ACCESS_LOCK_NAME "lockout" +#define LDAP_ACCESS_PPOLICY_NAME "ppolicy" + +#define LDAP_ACCOUNT_EXPIRE_SHADOW "shadow" +#define LDAP_ACCOUNT_EXPIRE_AD "ad" +#define LDAP_ACCOUNT_EXPIRE_RHDS "rhds" +#define LDAP_ACCOUNT_EXPIRE_IPA "ipa" +#define LDAP_ACCOUNT_EXPIRE_389DS "389ds" +#define LDAP_ACCOUNT_EXPIRE_NDS "nds" + +enum ldap_access_rule { + LDAP_ACCESS_EMPTY = -1, + LDAP_ACCESS_FILTER = 0, + LDAP_ACCESS_EXPIRE, + LDAP_ACCESS_SERVICE, + LDAP_ACCESS_HOST, + LDAP_ACCESS_RHOST, + LDAP_ACCESS_LOCKOUT, + LDAP_ACCESS_EXPIRE_POLICY_REJECT, + LDAP_ACCESS_EXPIRE_POLICY_WARN, + LDAP_ACCESS_EXPIRE_POLICY_RENEW, + LDAP_ACCESS_PPOLICY, + LDAP_ACCESS_LAST +}; + +enum sdap_access_type { + SDAP_TYPE_LDAP, + SDAP_TYPE_IPA +}; + +struct sdap_access_ctx { + enum sdap_access_type type; + struct sdap_id_ctx *id_ctx; + const char *filter; + int access_rule[LDAP_ACCESS_LAST + 1]; +}; + +struct tevent_req * +sdap_pam_access_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_access_ctx *access_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +sdap_pam_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +struct tevent_req * +sdap_access_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + struct sdap_access_ctx *access_ctx, + struct sdap_id_conn_ctx *conn, + struct pam_data *pd); +errno_t sdap_access_recv(struct tevent_req *req); + +/* Set the access rules based on ldap_access_order */ +errno_t sdap_set_access_rules(TALLOC_CTX *mem_ctx, + struct sdap_access_ctx *access_ctx, + struct dp_option *opts, + struct dp_option *more_opts); + +#endif /* SDAP_ACCESS_H_ */ diff --git a/src/providers/ldap/sdap_ad_groups.c b/src/providers/ldap/sdap_ad_groups.c new file mode 100644 index 0000000..e8c6280 --- /dev/null +++ b/src/providers/ldap/sdap_ad_groups.c @@ -0,0 +1,69 @@ +/* + SSSD + + AD groups helper routines + + Authors: + Lukas Slebodnik + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include "db/sysdb.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async_private.h" + +/* ==Group-Parsing Routines=============================================== */ + +errno_t sdap_check_ad_group_type(struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs *group_attrs, + const char *group_name, + bool *_need_filter) +{ + int32_t ad_group_type; + errno_t ret = EOK; + *_need_filter = false; + + if (opts->schema_type == SDAP_SCHEMA_AD + && !opts->allow_remote_domain_local_groups) { + ret = sysdb_attrs_get_int32_t(group_attrs, SYSDB_GROUP_TYPE, + &ad_group_type); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_int32_t failed.\n"); + return ret; + } + + DEBUG(SSSDBG_TRACE_ALL, + "AD group [%s] has type flags %#x.\n", + group_name, ad_group_type); + + /* Only security groups from AD are considered for POSIX groups. + * Additionally only global and universal group are taken to account + * for trusted domains. */ + if (!(ad_group_type & SDAP_AD_GROUP_TYPE_SECURITY) + || (IS_SUBDOMAIN(dom) + && (!((ad_group_type & SDAP_AD_GROUP_TYPE_GLOBAL) + || (ad_group_type & SDAP_AD_GROUP_TYPE_UNIVERSAL))))) { + DEBUG(SSSDBG_TRACE_FUNC, + "Filtering AD group [%s].\n", group_name); + + *_need_filter = true; + } + } + + return ret; +} diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c new file mode 100644 index 0000000..ab3572d --- /dev/null +++ b/src/providers/ldap/sdap_async.c @@ -0,0 +1,3169 @@ +/* + SSSD + + Async LDAP Helper routines + + Copyright (C) Simo Sorce - 2009 + + 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 3 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; 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, see . +*/ + + +#include +#include "util/util.h" +#include "util/strtonum.h" +#include "util/probes.h" +#include "util/sss_chain_id.h" +#include "providers/ldap/sdap_async_private.h" + +#define REPLY_REALLOC_INCREMENT 10 + +struct sdap_op { + struct sdap_op *prev, *next; + struct sdap_handle *sh; + uint64_t chain_id; + + int msgid; + char *stat_info; + uint64_t start_time; + int timeout; + bool done; + + sdap_op_callback_t *callback; + void *data; + + struct tevent_context *ev; + struct sdap_msg *list; + struct sdap_msg *last; +}; + +int sdap_op_get_msgid(struct sdap_op *op) +{ + return op != NULL ? op->msgid : 0; +} + +/* ==LDAP-Memory-Handling================================================= */ + +static int lmsg_destructor(void *mem) +{ + ldap_msgfree((LDAPMessage *)mem); + return 0; +} + +/* ==sdap-handle-utility-functions======================================== */ + +static inline void sdap_handle_release(struct sdap_handle *sh); +static int sdap_handle_destructor(void *mem); + +struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx) +{ + struct sdap_handle *sh; + + sh = talloc_zero(memctx, struct sdap_handle); + if (!sh) return NULL; + + talloc_set_destructor((TALLOC_CTX *)sh, sdap_handle_destructor); + + return sh; +} + +static int sdap_handle_destructor(void *mem) +{ + struct sdap_handle *sh = talloc_get_type(mem, struct sdap_handle); + + /* if the structure is currently locked, then mark it to be released + * and prevent talloc from freeing the memory */ + if (sh->destructor_lock) { + sh->release_memory = true; + return -1; + } + + sdap_handle_release(sh); + return 0; +} + +static void sdap_call_op_callback(struct sdap_op *op, struct sdap_msg *reply, + int error) +{ + uint64_t time_spend; + const char *info = (op->stat_info == NULL ? "-" : op->stat_info); + + if (op->start_time != 0) { + time_spend = get_spend_time_us(op->start_time); + DEBUG(SSSDBG_PERF_STAT, + "Handling LDAP operation [%d][%s] took %s.\n", + op->msgid, info, sss_format_time(time_spend)); + + /* time_spend is in us and timeout in s */ + if (op->timeout != 0 && (time_spend / op->timeout) >= (80 * 10000)) { + DEBUG(SSSDBG_IMPORTANT_INFO, "LDAP operation [%d][%s] seems slow, " + "took more than 80%% of timeout [%d].\n", + op->msgid, info, op->timeout); + } + + /* Avoid multiple outputs for the same operation if multiple results + * are returned */ + op->start_time = 0; + } + + op->callback(op, reply, error, op->data); +} + +static void sdap_handle_release(struct sdap_handle *sh) +{ + struct sdap_op *op; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Trace: sh[%p], connected[%d], ops[%p], ldap[%p], " + "destructor_lock[%d], release_memory[%d]\n", + sh, (int)sh->connected, sh->ops, sh->ldap, + (int)sh->destructor_lock, (int)sh->release_memory); + + if (sh->destructor_lock) return; + sh->destructor_lock = true; + + /* make sure nobody tries to reuse this connection from now on */ + sh->connected = false; + + remove_ldap_connection_callbacks(sh); + + while (sh->ops) { + op = sh->ops; + sdap_call_op_callback(op, NULL, EIO); + /* calling the callback may result in freeing the op */ + /* check if it is still the same or avoid freeing */ + if (op == sh->ops) talloc_free(op); + } + + if (sh->ldap) { + ldap_unbind_ext(sh->ldap, NULL, NULL); + sh->ldap = NULL; + } + + /* ok, we have done the job, unlock now */ + sh->destructor_lock = false; + + /* finally if a destructor was ever called, free sh before + * exiting */ + if (sh->release_memory) { + /* neutralize the destructor as we already handled + * all was needed to be released */ + talloc_set_destructor((TALLOC_CTX *)sh, NULL); + talloc_free(sh); + } +} + +/* ==Parse-Results-And-Handle-Disconnections============================== */ +static void sdap_process_message(struct tevent_context *ev, + struct sdap_handle *sh, LDAPMessage *msg); +static void sdap_process_result(struct tevent_context *ev, void *pvt); +static void sdap_process_next_reply(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +void sdap_ldap_result(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *pvt) +{ + sdap_process_result(ev, pvt); +} + +static void sdap_ldap_next_result(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + sdap_process_result(ev, pvt); +} + +static struct sdap_op *sdap_get_message_op(struct sdap_handle *sh, + LDAPMessage *msg) +{ + struct sdap_op *op; + int msgid; + + msgid = ldap_msgid(msg); + if (msgid == -1) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid message id!\n"); + return NULL; + } + + for (op = sh->ops; op; op = op->next) { + if (op->msgid == msgid) { + return op; + } + } + + return NULL; +} + +static void sdap_process_result(struct tevent_context *ev, void *pvt) +{ + struct sdap_handle *sh = talloc_get_type(pvt, struct sdap_handle); + uint64_t old_chain_id; + struct timeval no_timeout = {0, 0}; + struct tevent_timer *te; + struct sdap_op *op; + LDAPMessage *msg; + int ret; + + /* This is a top level event, always use chain id 0. We set a proper id + * later in this function once we can match the reply with an operation. */ + old_chain_id = sss_chain_id_set(0); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Trace: sh[%p], connected[%d], ops[%p], ldap[%p]\n", + sh, (int)sh->connected, sh->ops, sh->ldap); + + if (!sh->connected || !sh->ldap) { + DEBUG(SSSDBG_OP_FAILURE, "ERROR: LDAP connection is not connected!\n"); + sdap_handle_release(sh); + return; + } + + ret = ldap_result(sh->ldap, LDAP_RES_ANY, 0, &no_timeout, &msg); + if (ret == 0) { + /* this almost always means we have reached the end of + * the list of received messages */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Trace: end of ldap_result list\n"); + return; + } + + if (ret == -1) { + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &ret); + DEBUG(SSSDBG_OP_FAILURE, + "ldap_result error: [%s]\n", ldap_err2string(ret)); + sdap_handle_release(sh); + return; + } + + /* We don't know if this will be the last result. + * + * important: we must do this before actually processing the message + * because the message processing might even free the sdap_handler + * so it must be the last operation. + * FIXME: use tevent_immediate/tevent_queues, when available */ + memset(&no_timeout, 0, sizeof(struct timeval)); + + te = tevent_add_timer(ev, sh, no_timeout, sdap_ldap_next_result, sh); + if (!te) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add critical timer to fetch next result!\n"); + } + + /* Set the chain id if we can match the operation. */ + op = sdap_get_message_op(sh, msg); + if (op != NULL) { + sss_chain_id_set(op->chain_id); + } + + /* now process this message */ + sdap_process_message(ev, sh, msg); + + /* Restore the chain id. */ + sss_chain_id_set(old_chain_id); +} + +static const char *sdap_ldap_result_str(int msgtype) +{ + switch (msgtype) { + case LDAP_RES_BIND: + return "LDAP_RES_BIND"; + + case LDAP_RES_SEARCH_ENTRY: + return "LDAP_RES_SEARCH_ENTRY"; + + case LDAP_RES_SEARCH_REFERENCE: + return "LDAP_RES_SEARCH_REFERENCE"; + + case LDAP_RES_SEARCH_RESULT: + return "LDAP_RES_SEARCH_RESULT"; + + case LDAP_RES_MODIFY: + return "LDAP_RES_MODIFY"; + + case LDAP_RES_ADD: + return "LDAP_RES_ADD"; + + case LDAP_RES_DELETE: + return "LDAP_RES_DELETE"; + + case LDAP_RES_MODDN: + /* These are the same result + case LDAP_RES_MODRDN: + case LDAP_RES_RENAME: + */ + return "LDAP_RES_RENAME"; + + case LDAP_RES_COMPARE: + return "LDAP_RES_COMPARE"; + + case LDAP_RES_EXTENDED: + return "LDAP_RES_EXTENDED"; + + case LDAP_RES_INTERMEDIATE: + return "LDAP_RES_INTERMEDIATE"; + + case LDAP_RES_ANY: + return "LDAP_RES_ANY"; + + case LDAP_RES_UNSOLICITED: + return "LDAP_RES_UNSOLICITED"; + + default: + /* Unmatched, fall through */ + break; + } + + /* Unknown result type */ + return "Unknown result type!"; +} + +/* process a message calling the right operation callback. + * msg is completely taken care of (including freeing it) + * NOTE: this function may even end up freeing the sdap_handle + * so sdap_handle must not be used after this function is called + */ +static void sdap_process_message(struct tevent_context *ev, + struct sdap_handle *sh, LDAPMessage *msg) +{ + struct sdap_msg *reply; + struct sdap_op *op; + int msgtype; + int ret; + + msgtype = ldap_msgtype(msg); + + op = sdap_get_message_op(sh, msg); + if (op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Unmatched msgid, discarding message (type: %0x)\n", + msgtype); + ldap_msgfree(msg); + return; + } + + /* shouldn't happen */ + if (op->done) { + DEBUG(SSSDBG_OP_FAILURE, + "Operation [%p] already handled (type: %0x)\n", op, msgtype); + ldap_msgfree(msg); + return; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Message type: [%s]\n", sdap_ldap_result_str(msgtype)); + + switch (msgtype) { + case LDAP_RES_SEARCH_ENTRY: + case LDAP_RES_SEARCH_REFERENCE: + /* go and process entry */ + break; + + case LDAP_RES_BIND: + case LDAP_RES_SEARCH_RESULT: + case LDAP_RES_MODIFY: + case LDAP_RES_ADD: + case LDAP_RES_DELETE: + case LDAP_RES_MODDN: + case LDAP_RES_COMPARE: + case LDAP_RES_EXTENDED: + case LDAP_RES_INTERMEDIATE: + /* no more results expected with this msgid */ + op->done = true; + break; + + default: + /* unknown msg type?? */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Couldn't figure out the msg type! [%0x]\n", msgtype); + ldap_msgfree(msg); + return; + } + + reply = talloc_zero(op, struct sdap_msg); + if (!reply) { + ldap_msgfree(msg); + ret = ENOMEM; + } else { + reply->msg = msg; + ret = sss_mem_attach(reply, msg, lmsg_destructor); + if (ret != EOK) { + ldap_msgfree(msg); + talloc_zfree(reply); + } + } + + if (op->list) { + /* list exist, queue it */ + + op->last->next = reply; + op->last = reply; + + } else { + /* create list, then call callback */ + op->list = op->last = reply; + + /* must be the last operation as it may end up freeing all memory + * including all ops handlers */ + sdap_call_op_callback(op, reply, ret); + } +} + +static void sdap_unlock_next_reply(struct sdap_op *op) +{ + struct timeval tv; + struct tevent_timer *te; + struct sdap_msg *next_reply; + + if (op->list) { + next_reply = op->list->next; + /* get rid of the previous reply, it has been processed already */ + talloc_zfree(op->list); + op->list = next_reply; + } + + /* if there are still replies to parse, queue a new operation */ + if (op->list) { + /* use a very small timeout, so that fd operations have a chance to be + * served while processing a long reply */ + tv = tevent_timeval_current(); + + /* wait 5 microsecond */ + tv.tv_usec += 5; + tv.tv_sec += tv.tv_usec / 1000000; + tv.tv_usec = tv.tv_usec % 1000000; + + te = tevent_add_timer(op->ev, op, tv, + sdap_process_next_reply, op); + if (!te) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add critical timer for next reply!\n"); + sdap_call_op_callback(op, NULL, EFAULT); + } + } +} + +static void sdap_process_next_reply(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct sdap_op *op = talloc_get_type(pvt, struct sdap_op); + + sdap_call_op_callback(op, op->list, EOK); +} + +/* ==LDAP-Operations-Helpers============================================== */ + +static int sdap_op_destructor(void *mem) +{ + struct sdap_op *op = (struct sdap_op *)mem; + + DLIST_REMOVE(op->sh->ops, op); + + if (op->done) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Operation %d finished\n", op->msgid); + return 0; + } + + /* we don't check the result here, if a message was really abandoned, + * hopefully the server will get an abandon. + * If the operation was already fully completed, this is going to be + * just a noop */ + DEBUG(SSSDBG_TRACE_LIBS, "Abandoning operation %d\n", op->msgid); + ldap_abandon_ext(op->sh->ldap, op->msgid, NULL, NULL); + + return 0; +} + +static void sdap_op_timeout(struct tevent_req *req) +{ + struct sdap_op *op = tevent_req_callback_data(req, struct sdap_op); + + /* should never happen, but just in case */ + if (op->done) { + DEBUG(SSSDBG_OP_FAILURE, "Timeout happened after op was finished !?\n"); + return; + } + + /* signal the caller that we have a timeout */ + DEBUG(SSSDBG_TRACE_LIBS, "Issuing timeout [ldap_opt_timeout] for message id %d\n", op->msgid); + sdap_call_op_callback(op, NULL, ETIMEDOUT); +} + +int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_handle *sh, int msgid, const char *stat_info, + sdap_op_callback_t *callback, void *data, + int timeout, struct sdap_op **_op) +{ + struct sdap_op *op; + + op = talloc_zero(memctx, struct sdap_op); + if (!op) return ENOMEM; + + op->start_time = get_start_time(); + op->timeout = timeout; + op->sh = sh; + op->msgid = msgid; + if (stat_info != NULL) { + op->stat_info = talloc_strdup(op, stat_info); + if (op->stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy stat_info, ignored.\n"); + } + } + op->callback = callback; + op->data = data; + op->ev = ev; + op->chain_id = sss_chain_id_get(); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "New operation %d timeout %d\n", op->msgid, timeout); + + /* check if we need to set a timeout */ + if (timeout) { + struct tevent_req *req; + struct timeval tv; + + tv = tevent_timeval_current(); + tv = tevent_timeval_add(&tv, timeout, 0); + + /* allocate on op, so when it get freed the timeout is removed */ + req = tevent_wakeup_send(op, ev, tv); + if (!req) { + talloc_zfree(op); + return ENOMEM; + } + tevent_req_set_callback(req, sdap_op_timeout, op); + } + + DLIST_ADD(sh->ops, op); + + talloc_set_destructor((TALLOC_CTX *)op, sdap_op_destructor); + + *_op = op; + return EOK; +} + +/* ==Modify-Password====================================================== */ + +static errno_t +sdap_chpass_result(TALLOC_CTX *mem_ctx, + int ldap_result, + const char *ldap_msg, + char **_user_msg) +{ + errno_t ret; + + switch (ldap_result) { + case LDAP_SUCCESS: + /* There's no need to set _user_msg here. */ + return EOK; + case LDAP_CONSTRAINT_VIOLATION: + if (ldap_msg == NULL || *ldap_msg == '\0') { + ldap_msg = "Please make sure the password " + "meets the complexity constraints."; + } + ret = ERR_CHPASS_DENIED; + break; + default: + ret = ERR_NETWORK_IO; + } + + if (ldap_msg != NULL) { + *_user_msg = talloc_strdup(mem_ctx, ldap_msg); + if (*_user_msg == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + } + + return ret; +} + +struct sdap_exop_modify_passwd_state { + struct sdap_handle *sh; + + struct sdap_op *op; + + char *user_error_message; +}; + +static void sdap_exop_modify_passwd_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + char *user_dn, + const char *password, + const char *new_password, + int timeout) +{ + struct tevent_req *req = NULL; + struct sdap_exop_modify_passwd_state *state; + int ret; + BerElement *ber = NULL; + struct berval *bv = NULL; + int msgid; + LDAPControl **request_controls = NULL; + LDAPControl *ctrls[2] = { NULL, NULL }; + char *stat_info; + + req = tevent_req_create(memctx, &state, + struct sdap_exop_modify_passwd_state); + if (!req) return NULL; + + state->sh = sh; + state->user_error_message = NULL; + + ber = ber_alloc_t( LBER_USE_DER ); + if (ber == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "ber_alloc_t failed.\n"); + talloc_zfree(req); + return NULL; + } + + ret = ber_printf( ber, "{tststs}", LDAP_TAG_EXOP_MODIFY_PASSWD_ID, + user_dn, + LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, password, + LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, new_password); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_printf failed.\n"); + ber_free(ber, 1); + talloc_zfree(req); + return NULL; + } + + ret = ber_flatten(ber, &bv); + ber_free(ber, 1); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n"); + talloc_zfree(req); + return NULL; + } + + ret = sdap_control_create(state->sh, LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, &ctrls[0]); + if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed to create " + "Password Policy control.\n"); + ret = ERR_INTERNAL; + goto fail; + } + request_controls = ctrls; + + DEBUG(SSSDBG_CONF_SETTINGS, "Executing extended operation\n"); + + ret = ldap_extended_operation(state->sh->ldap, LDAP_EXOP_MODIFY_PASSWD, + bv, request_controls, NULL, &msgid); + ber_bvfree(bv); + if (ctrls[0]) ldap_control_free(ctrls[0]); + if (ret == -1 || msgid == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_extended_operation failed\n"); + ret = ERR_NETWORK_IO; + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "ldap_extended_operation sent, msgid = %d\n", msgid); + + stat_info = talloc_asprintf(state, "server: [%s] modify passwd dn: [%s]", + sdap_get_server_peer_str_safe(state->sh), + user_dn); + if (stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); + } + + ret = sdap_op_add(state, ev, state->sh, msgid, stat_info, + sdap_exop_modify_passwd_done, req, timeout, &state->op); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); + ret = ERR_INTERNAL; + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sdap_exop_modify_passwd_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct sdap_exop_modify_passwd_state *state = tevent_req_data(req, + struct sdap_exop_modify_passwd_state); + char *errmsg = NULL; + int ret; + LDAPControl **response_controls = NULL; + int c; + ber_int_t pp_grace; + ber_int_t pp_expire; + LDAPPasswordPolicyError pp_error; + int result; + + if (error) { + tevent_req_error(req, error); + return; + } + + ret = ldap_parse_result(state->sh->ldap, reply->msg, + &result, NULL, &errmsg, NULL, + &response_controls, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_result failed (%d)\n", state->op->msgid); + ret = ERR_INTERNAL; + goto done; + } + + if (response_controls == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "Server returned no controls.\n"); + } else { + for (c = 0; response_controls[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Server returned control [%s].\n", + response_controls[c]->ldctl_oid); + if (strcmp(response_controls[c]->ldctl_oid, + LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) { + ret = ldap_parse_passwordpolicy_control(state->sh->ldap, + response_controls[c], + &pp_expire, &pp_grace, + &pp_error); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_parse_passwordpolicy_control failed.\n"); + ret = ERR_NETWORK_IO; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Password Policy Response: expire [%d] grace [%d] " + "error [%s].\n", pp_expire, pp_grace, + ldap_passwordpolicy_err2txt(pp_error)); + } + } + } + + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_extended_operation result: %s(%d), %s\n", + sss_ldap_err2string(result), result, errmsg); + + ret = sdap_chpass_result(state, result, errmsg, &state->user_error_message); + +done: + ldap_controls_free(response_controls); + ldap_memfree(errmsg); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +errno_t sdap_exop_modify_passwd_recv(struct tevent_req *req, + TALLOC_CTX * mem_ctx, + char **user_error_message) +{ + struct sdap_exop_modify_passwd_state *state = tevent_req_data(req, + struct sdap_exop_modify_passwd_state); + + /* We want to return the error message even on failure */ + *user_error_message = talloc_steal(mem_ctx, state->user_error_message); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_modify_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_op *op; + + int ldap_result; + char *ldap_msg; +}; + +static void sdap_modify_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +static struct tevent_req * +sdap_modify_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + int timeout, + const char *dn, + char *attr, + char **values) +{ + struct tevent_req *req; + struct sdap_modify_state *state; + LDAPMod **mods; + errno_t ret; + int msgid; + char *stat_info; + + req = tevent_req_create(mem_ctx, &state, struct sdap_modify_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->sh = sh; + + mods = talloc_zero_array(state, LDAPMod *, 2); + if (mods == NULL) { + ret = ENOMEM; + goto done; + } + + mods[0] = talloc_zero(mods, LDAPMod); + if (mods[0] == NULL) { + ret = ENOMEM; + goto done; + } + + mods[0]->mod_op = LDAP_MOD_REPLACE; + mods[0]->mod_type = attr; + mods[0]->mod_vals.modv_strvals = values; + mods[1] = NULL; + + ret = ldap_modify_ext(state->sh->ldap, dn, mods, NULL, NULL, &msgid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_modify_ext() failed [%d]\n", ret); + goto done; + } + + stat_info = talloc_asprintf(state, "server: [%s] modify dn: [%s] attr: [%s]", + sdap_get_server_peer_str_safe(state->sh), dn, + attr); + if (stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); + } + + ret = sdap_op_add(state, state->ev, state->sh, msgid, stat_info, + sdap_modify_done, req, timeout, &state->op); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_modify_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req; + struct sdap_modify_state *state; + char *errmsg; + errno_t ret; + int result; + int lret; + + req = talloc_get_type(pvt, struct tevent_req); + state = tevent_req_data(req, struct sdap_modify_state); + + if (error) { + tevent_req_error(req, error); + return; + } + + lret = ldap_parse_result(state->sh->ldap, reply->msg, &result, + NULL, &errmsg, NULL, NULL, 0); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed (%d)\n", + state->op->msgid); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "ldap_modify result: %s(%d), %s\n", + sss_ldap_err2string(result), + result, errmsg); + + state->ldap_result = result; + if (errmsg != NULL) { + state->ldap_msg = talloc_strdup(state, errmsg); + if (state->ldap_msg == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = EOK; + +done: + ldap_memfree(errmsg); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t sdap_modify_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + int *_ldap_result, + char **_ldap_msg) +{ + struct sdap_modify_state *state; + + state = tevent_req_data(req, struct sdap_modify_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_ldap_result != NULL) { + *_ldap_result = state->ldap_result; + } + + if (_ldap_msg != NULL) { + *_ldap_msg = talloc_steal(mem_ctx, state->ldap_msg); + } + + return EOK; +} + +struct sdap_modify_passwd_state { + const char *dn; + char *user_msg; +}; + +static void sdap_modify_passwd_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_modify_passwd_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + int timeout, + char *attr, + const char *user_dn, + const char *new_password) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_modify_passwd_state *state; + char **values; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_modify_passwd_state); + if (req == NULL) { + return NULL; + } + + state->dn = user_dn; + + values = talloc_zero_array(state, char *, 2); + if (values == NULL) { + ret = ENOMEM; + goto done; + } + + values[0] = talloc_strdup(values, new_password); + if (values[0] == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = sdap_modify_send(state, ev, sh, timeout, user_dn, attr, values); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_modify_passwd_done, req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_modify_passwd_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_modify_passwd_state *state; + int ldap_result; + char *ldap_msg; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_modify_passwd_state); + + ret = sdap_modify_recv(state, subreq, &ldap_result, &ldap_msg); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Password change for [%s] failed [%d]: %s\n", + state->dn, ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = sdap_chpass_result(state, ldap_result, ldap_msg, &state->user_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Password change for [%s] failed [%d]: %s\n", + state->dn, ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Password change for [%s] was successful\n", + state->dn); + + tevent_req_done(req); +} + +errno_t sdap_modify_passwd_recv(struct tevent_req *req, + TALLOC_CTX * mem_ctx, + char **_user_error_message) +{ + struct sdap_modify_passwd_state *state; + + state = tevent_req_data(req, struct sdap_modify_passwd_state); + + /* We want to return the error message even on failure */ + *_user_error_message = talloc_steal(mem_ctx, state->user_msg); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Update-passwordLastChanged-attribute====================== */ +struct sdap_modify_shadow_lastchange_state { + const char *dn; +}; + +static void sdap_modify_shadow_lastchange_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_modify_shadow_lastchange_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + const char *dn, + char *attr) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_modify_shadow_lastchange_state *state; + char **values; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_modify_shadow_lastchange_state); + if (req == NULL) { + return NULL; + } + + state->dn = dn; + values = talloc_zero_array(state, char *, 2); + if (values == NULL) { + ret = ENOMEM; + goto done; + } + + /* The attribute contains number of days since the epoch */ + values[0] = talloc_asprintf(values, "%"SPRItime, time(NULL)/86400); + if (values[0] == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = sdap_modify_send(state, ev, sh, 5, dn, attr, values); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_modify_shadow_lastchange_done, req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void sdap_modify_shadow_lastchange_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_modify_shadow_lastchange_state *state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_modify_shadow_lastchange_state); + + ret = sdap_modify_recv(state, subreq, NULL, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "shadowLastChange change for [%s] failed [%d]: %s\n", + state->dn, ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "shadowLastChange change for [%s] was successful\n", + state->dn); + + tevent_req_done(req); +} + +errno_t sdap_modify_shadow_lastchange_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +/* ==Fetch-RootDSE============================================= */ + +struct sdap_get_rootdse_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + + struct sysdb_attrs *rootdse; +}; + +static void sdap_get_rootdse_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh) +{ + struct tevent_req *req, *subreq; + struct sdap_get_rootdse_state *state; + const char *attrs[] = { + "*", + "altServer", + SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS, + "supportedControl", + "supportedExtension", + "supportedFeatures", + "supportedLDAPVersion", + "supportedSASLMechanisms", + SDAP_ROOTDSE_ATTR_AD_VERSION, + SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT, + SDAP_IPA_LAST_USN, SDAP_AD_LAST_USN, + NULL + }; + + DEBUG(SSSDBG_TRACE_ALL, "Getting rootdse\n"); + + req = tevent_req_create(memctx, &state, struct sdap_get_rootdse_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->rootdse = NULL; + + subreq = sdap_get_generic_send(state, ev, opts, sh, + "", LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_get_rootdse_done, req); + + return req; +} + +/* This is not a real attribute, it's just there to avoid + * actually pulling real data down, to save bandwidth + */ +static void sdap_get_rootdse_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_rootdse_state *state = tevent_req_data(req, + struct sdap_get_rootdse_state); + struct sysdb_attrs **results; + size_t num_results; + int ret; + + ret = sdap_get_generic_recv(subreq, state, &num_results, &results); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (num_results == 0 || !results) { + DEBUG(SSSDBG_OP_FAILURE, "RootDSE could not be retrieved. " + "Please check that anonymous access to RootDSE is allowed\n" + ); + tevent_req_error(req, ENOENT); + return; + } + + if (num_results > 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Multiple replies when searching for RootDSE??\n"); + tevent_req_error(req, EIO); + return; + } + + state->rootdse = talloc_steal(state, results[0]); + talloc_zfree(results); + + DEBUG(SSSDBG_TRACE_INTERNAL, "Got rootdse\n"); + + tevent_req_done(req); + return; +} + +int sdap_get_rootdse_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sysdb_attrs **rootdse) +{ + struct sdap_get_rootdse_state *state = tevent_req_data(req, + struct sdap_get_rootdse_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *rootdse = talloc_steal(memctx, state->rootdse); + + return EOK; +} + +/* ==Helpers for parsing replies============================== */ +struct sdap_reply { + size_t reply_max; + size_t reply_count; + struct sysdb_attrs **reply; +}; + +static errno_t add_to_reply(TALLOC_CTX *mem_ctx, + struct sdap_reply *sreply, + struct sysdb_attrs *msg) +{ + if (sreply->reply == NULL || sreply->reply_max == sreply->reply_count) { + sreply->reply_max += REPLY_REALLOC_INCREMENT; + sreply->reply = talloc_realloc(mem_ctx, sreply->reply, + struct sysdb_attrs *, + sreply->reply_max); + if (sreply->reply == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc failed.\n"); + return ENOMEM; + } + } + + sreply->reply[sreply->reply_count++] = talloc_steal(sreply->reply, msg); + + return EOK; +} + +struct sdap_deref_reply { + size_t reply_max; + size_t reply_count; + struct sdap_deref_attrs **reply; +}; + +static errno_t add_to_deref_reply(TALLOC_CTX *mem_ctx, + int num_maps, + struct sdap_deref_reply *dreply, + struct sdap_deref_attrs **res) +{ + int i; + + if (res == NULL) { + /* Nothing to add, probably ACIs prevented us from dereferencing + * the attribute */ + return EOK; + } + + for (i=0; i < num_maps; i++) { + if (res[i]->attrs == NULL) continue; /* Nothing in this map */ + + if (dreply->reply == NULL || + dreply->reply_max == dreply->reply_count) { + dreply->reply_max += REPLY_REALLOC_INCREMENT; + dreply->reply = talloc_realloc(mem_ctx, dreply->reply, + struct sdap_deref_attrs *, + dreply->reply_max); + if (dreply->reply == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc failed.\n"); + return ENOMEM; + } + } + + dreply->reply[dreply->reply_count++] = + talloc_steal(dreply->reply, res[i]); + } + + return EOK; +} + +const char *sdap_get_server_peer_str(struct sdap_handle *sh) +{ + int ret; + int fd; + struct sockaddr sa; + socklen_t sa_len = sizeof(sa); + char ip[NI_MAXHOST]; + static char out[NI_MAXHOST + 8]; + int port; + + ret = get_fd_from_ldap(sh->ldap, &fd); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "cannot get sdap fd\n"); + return NULL; + } + + ret = getpeername(fd, &sa, &sa_len); + if (ret == -1) { + DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); + return NULL; + } + + switch (sa.sa_family) { + case AF_INET: { + struct sockaddr_in in; + socklen_t in_len = sizeof(in); + + ret = getpeername(fd, (struct sockaddr *)(&in), &in_len); + if (ret == -1) { + DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); + return NULL; + } + + ret = getnameinfo((struct sockaddr *)(&in), in_len, + ip, sizeof(ip), NULL, 0, NI_NUMERICHOST); + if (ret != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "getnameinfo failed\n"); + return NULL; + } + + port = ntohs(in.sin_port); + ret = snprintf(out, sizeof(out), "%s:%d", ip, port); + break; + } + case AF_INET6: { + struct sockaddr_in6 in6; + socklen_t in6_len = sizeof(in6); + + ret = getpeername(fd, (struct sockaddr *)(&in6), &in6_len); + if (ret == -1) { + DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); + return NULL; + } + + ret = getnameinfo((struct sockaddr *)(&in6), in6_len, + ip, sizeof(ip), NULL, 0, NI_NUMERICHOST); + if (ret != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "getnameinfo failed\n"); + return NULL; + } + + port = ntohs(in6.sin6_port); + ret = snprintf(out, sizeof(out), "[%s]:%d", ip, port); + break; + } + case AF_UNIX: { + struct sockaddr_un un; + socklen_t un_len = sizeof(un); + + ret = getpeername(fd, (struct sockaddr *)(&un), &un_len); + if (ret == -1) { + DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); + return NULL; + } + + ret = snprintf(out, sizeof(out), "%.*s", + (int)strnlen(un.sun_path, un_len - offsetof(struct sockaddr_un, + sun_path)), un.sun_path); + break; + } + default: + return NULL; + } + + if (ret < 0 || ret >= sizeof(out)) { + return NULL; + } + + return out; +} + +const char *sdap_get_server_peer_str_safe(struct sdap_handle *sh) +{ + const char *ip = sdap_get_server_peer_str(sh); + return ip != NULL ? ip : "- IP not available -"; +} + +static void sdap_print_server(struct sdap_handle *sh) +{ + const char *ip; + + /* The purpose of the call is to add the server IP to the debug output if + * debug_level is SSSDBG_TRACE_INTERNAL or higher */ + if (DEBUG_IS_SET(SSSDBG_TRACE_INTERNAL)) { + ip = sdap_get_server_peer_str(sh); + if (ip != NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Searching %s\n", ip); + } else { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_server_peer_str failed.\n"); + } + } +} + +/* ==Generic Search exposing all options======================= */ +typedef errno_t (*sdap_parse_cb)(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt); + +struct sdap_get_generic_ext_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + const char *search_base; + int scope; + const char *filter; + const char **attrs; + int timeout; + int sizelimit; + + struct sdap_op *op; + + struct berval cookie; + + LDAPControl **serverctrls; + int nserverctrls; + LDAPControl **clientctrls; + + size_t ref_count; + char **refs; + + sdap_parse_cb parse_cb; + void *cb_data; + + unsigned int flags; +}; + +static errno_t sdap_get_generic_ext_step(struct tevent_req *req); + +static void sdap_get_generic_op_finished(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +enum { + /* Be silent about exceeded size limit */ + SDAP_SRCH_FLG_SIZELIMIT_SILENT = 1 << 0, + + /* Allow paging */ + SDAP_SRCH_FLG_PAGING = 1 << 1, + + /* Only attribute descriptions are requested */ + SDAP_SRCH_FLG_ATTRS_ONLY = 1 << 2, +}; + +static struct tevent_req * +sdap_get_generic_ext_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + int scope, + const char *filter, + const char **attrs, + LDAPControl **serverctrls, + LDAPControl **clientctrls, + int sizelimit, + int timeout, + sdap_parse_cb parse_cb, + void *cb_data, + unsigned int flags) +{ + errno_t ret; + struct sdap_get_generic_ext_state *state; + struct tevent_req *req; + int i; + LDAPControl *control; + + req = tevent_req_create(memctx, &state, struct sdap_get_generic_ext_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->search_base = search_base; + state->scope = scope; + state->filter = filter; + state->attrs = attrs; + state->op = NULL; + state->sizelimit = sizelimit; + state->timeout = timeout; + state->cookie.bv_len = 0; + state->cookie.bv_val = NULL; + state->parse_cb = parse_cb; + state->cb_data = cb_data; + state->clientctrls = clientctrls; + state->flags = flags; + + if (state->sh == NULL || state->sh->ldap == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Trying LDAP search while not connected.\n"); + tevent_req_error(req, EIO); + tevent_req_post(req, ev); + return req; + } + + sdap_print_server(sh); + + /* Be extra careful and never allow paging for BASE searches, + * even if requested. + */ + if (scope == LDAP_SCOPE_BASE && (flags & SDAP_SRCH_FLG_PAGING)) { + /* Disable paging */ + state->flags &= ~SDAP_SRCH_FLG_PAGING; + DEBUG(SSSDBG_TRACE_FUNC, + "WARNING: Disabling paging because scope is set to base.\n"); + } + + /* Also check for deref/asq requests and force + * paging on for those requests + */ + /* X-DEREF */ + control = ldap_control_find(LDAP_CONTROL_X_DEREF, + serverctrls, + NULL); + if (control) { + state->flags |= SDAP_SRCH_FLG_PAGING; + } + + /* ASQ */ + control = ldap_control_find(LDAP_SERVER_ASQ_OID, + serverctrls, + NULL); + if (control) { + state->flags |= SDAP_SRCH_FLG_PAGING; + } + + for (state->nserverctrls=0; + serverctrls && serverctrls[state->nserverctrls]; + state->nserverctrls++) ; + + /* One extra space for NULL, one for page control */ + state->serverctrls = talloc_array(state, LDAPControl *, + state->nserverctrls+2); + if (!state->serverctrls) { + tevent_req_error(req, ENOMEM); + tevent_req_post(req, ev); + return req; + } + + for (i=0; i < state->nserverctrls; i++) { + state->serverctrls[i] = serverctrls[i]; + } + state->serverctrls[i] = NULL; + + PROBE(SDAP_GET_GENERIC_EXT_SEND, state->search_base, + state->scope, state->filter, state->attrs); + + ret = sdap_get_generic_ext_step(req); + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; + } + + return req; +} + +static errno_t sdap_get_generic_ext_step(struct tevent_req *req) +{ + struct sdap_get_generic_ext_state *state = + tevent_req_data(req, struct sdap_get_generic_ext_state); + char *errmsg; + int lret; + int optret; + errno_t ret; + int msgid; + bool disable_paging; + char *stat_info; + + LDAPControl *page_control = NULL; + + /* Make sure to free any previous operations so + * if we are handling a large number of pages we + * don't waste memory. + */ + talloc_zfree(state->op); + + DEBUG(SSSDBG_TRACE_FUNC, + "calling ldap_search_ext with [%s][%s].\n", + state->filter ? state->filter : "no filter", + state->search_base); + if (state->attrs) { + for (int i = 0; state->attrs[i]; i++) { + DEBUG(SSSDBG_TRACE_LIBS, + "Requesting attrs: [%s]\n", state->attrs[i]); + } + } + + disable_paging = dp_opt_get_bool(state->opts->basic, SDAP_DISABLE_PAGING); + + if (!disable_paging + && (state->flags & SDAP_SRCH_FLG_PAGING) + && sdap_is_control_supported(state->sh, + LDAP_CONTROL_PAGEDRESULTS)) { + lret = ldap_create_page_control(state->sh->ldap, + state->sh->page_size, + state->cookie.bv_val ? + &state->cookie : + NULL, + false, + &page_control); + if (lret != LDAP_SUCCESS) { + ret = EIO; + goto done; + } + state->serverctrls[state->nserverctrls] = page_control; + state->serverctrls[state->nserverctrls+1] = NULL; + } + + lret = ldap_search_ext(state->sh->ldap, state->search_base, + state->scope, state->filter, + discard_const(state->attrs), + (state->flags & SDAP_SRCH_FLG_ATTRS_ONLY), + state->serverctrls, + state->clientctrls, NULL, state->sizelimit, &msgid); + ldap_control_free(page_control); + state->serverctrls[state->nserverctrls] = NULL; + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldap_search_ext failed: %s\n", sss_ldap_err2string(lret)); + if (lret == LDAP_SERVER_DOWN) { + ret = ETIMEDOUT; + optret = sss_ldap_get_diagnostic_msg(state, state->sh->ldap, + &errmsg); + if (optret == LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "Connection error: %s\n", errmsg); + sss_log(SSS_LOG_ERR, "LDAP connection error: %s", errmsg); + } else { + sss_log(SSS_LOG_ERR, "LDAP connection error, %s", + sss_ldap_err2string(lret)); + } + } else if (lret == LDAP_FILTER_ERROR) { + ret = ERR_INVALID_FILTER; + } else { + ret = EIO; + } + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "ldap_search_ext called, msgid = %d\n", msgid); + + stat_info = talloc_asprintf(state, "server: [%s] filter: [%s] base: [%s]", + sdap_get_server_peer_str_safe(state->sh), + state->filter, state->search_base); + if (stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); + } + + ret = sdap_op_add(state, state->ev, state->sh, msgid, stat_info, + sdap_get_generic_op_finished, req, + state->timeout, + &state->op); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); + goto done; + } + +done: + return ret; +} + +static errno_t +sdap_get_generic_ext_add_references(struct sdap_get_generic_ext_state *state, + char **refs) +{ + int i; + + if (refs == NULL) { + /* Rare, but it's possible that we might get a reference result with + * no references attached. + */ + return EOK; + } + + for (i = 0; refs[i]; i++) { + DEBUG(SSSDBG_TRACE_LIBS, "Additional References: %s\n", refs[i]); + } + + /* Extend the size of the ref array */ + state->refs = talloc_realloc(state, state->refs, char *, + state->ref_count + i); + if (state->refs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_realloc failed extending ref_array.\n"); + return ENOMEM; + } + + /* Copy in all the references */ + for (i = 0; refs[i]; i++) { + state->refs[state->ref_count + i] = + talloc_strdup(state->refs, refs[i]); + + if (state->refs[state->ref_count + i] == NULL) { + return ENOMEM; + } + } + + state->ref_count += i; + + return EOK; +} + +static void sdap_get_generic_op_finished(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct sdap_get_generic_ext_state *state = tevent_req_data(req, + struct sdap_get_generic_ext_state); + char *errmsg = NULL; + char **refs = NULL; + int result; + int ret; + int lret; + ber_int_t total_count; + struct berval cookie; + LDAPControl **returned_controls = NULL; + LDAPControl *page_control; + + if (error) { + tevent_req_error(req, error); + return; + } + + switch (ldap_msgtype(reply->msg)) { + case LDAP_RES_SEARCH_REFERENCE: + ret = ldap_parse_reference(state->sh->ldap, reply->msg, + &refs, NULL, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_reference failed (%d)\n", state->op->msgid); + tevent_req_error(req, EIO); + return; + } + + ret = sdap_get_generic_ext_add_references(state, refs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_get_generic_ext_add_references failed: %s(%d)\n", + sss_strerror(ret), ret); + ldap_memvfree((void **)refs); + tevent_req_error(req, ret); + return; + } + + /* Remove the original strings */ + ldap_memvfree((void **)refs); + + /* unlock the operation so that we can proceed with the next result */ + sdap_unlock_next_reply(state->op); + break; + + case LDAP_RES_SEARCH_ENTRY: + ret = state->parse_cb(state->sh, reply, state->cb_data); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "reply parsing callback failed.\n"); + tevent_req_error(req, ret); + return; + } + + sdap_unlock_next_reply(state->op); + break; + + case LDAP_RES_SEARCH_RESULT: + ret = ldap_parse_result(state->sh->ldap, reply->msg, + &result, NULL, &errmsg, &refs, + &returned_controls, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_result failed (%d)\n", state->op->msgid); + tevent_req_error(req, EIO); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Search result: %s(%d), %s\n", + sss_ldap_err2string(result), result, + errmsg ? errmsg : "no errmsg set"); + + if (result == LDAP_SIZELIMIT_EXCEEDED + || result == LDAP_ADMINLIMIT_EXCEEDED) { + /* Try to return what we've got */ + + if ( ! (state->flags & SDAP_SRCH_FLG_SIZELIMIT_SILENT)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "LDAP sizelimit was exceeded, " + "returning incomplete data\n"); + } + } else if (result == LDAP_INAPPROPRIATE_MATCHING) { + /* This error should only occur when we're testing for + * specialized functionality like the LDAP matching rule + * filter for Active Directory. Warn at a higher log + * level and return EIO. + */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "LDAP_INAPPROPRIATE_MATCHING: %s\n", + errmsg ? errmsg : "no errmsg set"); + ldap_memfree(errmsg); + tevent_req_error(req, EIO); + return; + } else if (result == LDAP_UNAVAILABLE_CRITICAL_EXTENSION) { + ldap_memfree(errmsg); + tevent_req_error(req, ENOTSUP); + return; + } else if (result == LDAP_REFERRAL) { + ret = sdap_get_generic_ext_add_references(state, refs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_get_generic_ext_add_references failed: %s(%d)\n", + sss_strerror(ret), ret); + tevent_req_error(req, ret); + } + /* For referrals, we need to fall through as if it was LDAP_SUCCESS */ + } else if (result != LDAP_SUCCESS && result != LDAP_NO_SUCH_OBJECT) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected result from ldap: %s(%d), %s\n", + sss_ldap_err2string(result), result, + errmsg ? errmsg : "no errmsg set"); + ldap_memfree(errmsg); + tevent_req_error(req, EIO); + return; + } + ldap_memfree(errmsg); + + /* Determine if there are more pages to retrieve */ + page_control = ldap_control_find(LDAP_CONTROL_PAGEDRESULTS, + returned_controls, NULL ); + if (!page_control) { + /* No paging support. We are done */ + tevent_req_done(req); + return; + } + + lret = ldap_parse_pageresponse_control(state->sh->ldap, page_control, + &total_count, &cookie); + ldap_controls_free(returned_controls); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine page control\n"); + tevent_req_error(req, EIO); + return; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Total count [%d]\n", total_count); + + if (cookie.bv_val != NULL && cookie.bv_len > 0) { + /* Cookie contains data, which means there are more requests + * to be processed. + */ + talloc_zfree(state->cookie.bv_val); + state->cookie.bv_len = cookie.bv_len; + state->cookie.bv_val = talloc_memdup(state, + cookie.bv_val, + cookie.bv_len); + if (!state->cookie.bv_val) { + tevent_req_error(req, ENOMEM); + return; + } + ber_memfree(cookie.bv_val); + + ret = sdap_get_generic_ext_step(req); + if (ret != EOK) { + tevent_req_error(req, ENOMEM); + return; + } + + return; + } + /* The cookie must be freed even if len == 0 */ + ber_memfree(cookie.bv_val); + + /* This was the last page. We're done */ + + tevent_req_done(req); + return; + + default: + /* what is going on here !? */ + tevent_req_error(req, EIO); + return; + } +} + +static int +sdap_get_generic_ext_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *ref_count, + char ***refs) +{ + struct sdap_get_generic_ext_state *state = + tevent_req_data(req, struct sdap_get_generic_ext_state); + + PROBE(SDAP_GET_GENERIC_EXT_RECV, state->search_base, + state->scope, state->filter); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (ref_count) { + *ref_count = state->ref_count; + } + + if (refs) { + *refs = talloc_steal(mem_ctx, state->refs); + } + + return EOK; +} + +/* This search handler can be used by most calls */ +static void generic_ext_search_handler(struct tevent_req *subreq, + struct sdap_options *opts) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + int ret; + size_t ref_count, i; + char **refs; + + ret = sdap_get_generic_ext_recv(subreq, req, &ref_count, &refs); + talloc_zfree(subreq); + + if (ret != EOK) { + if (ret == ETIMEDOUT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_ext_recv failed: [%d]: %s " + "[ldap_search_timeout]\n", + ret, sss_strerror(ret)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_ext_recv request failed: [%d]: %s\n", + ret, sss_strerror(ret)); + } + tevent_req_error(req, ret); + return; + } + + if (ref_count > 0) { + /* We will ignore referrals in the generic handler */ + DEBUG(SSSDBG_TRACE_ALL, + "Request included referrals which were ignored.\n"); + if (debug_level & SSSDBG_TRACE_ALL) { + for(i = 0; i < ref_count; i++) { + DEBUG(SSSDBG_TRACE_ALL, + " Ref: %s\n", refs[i]); + } + } + } + + talloc_free(refs); + tevent_req_done(req); +} + +/* ==Generic Search exposing all options======================= */ +struct sdap_get_and_parse_generic_state { + struct sdap_attr_map *map; + int map_num_attrs; + + struct sdap_reply sreply; + struct sdap_options *opts; +}; + +static void sdap_get_and_parse_generic_done(struct tevent_req *subreq); +static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt); + +struct tevent_req *sdap_get_and_parse_generic_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + int scope, + const char *filter, + const char **attrs, + struct sdap_attr_map *map, + int map_num_attrs, + int attrsonly, + LDAPControl **serverctrls, + LDAPControl **clientctrls, + int sizelimit, + int timeout, + bool allow_paging) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_get_and_parse_generic_state *state = NULL; + unsigned int flags = 0; + + req = tevent_req_create(memctx, &state, + struct sdap_get_and_parse_generic_state); + if (!req) return NULL; + + state->map = map; + state->map_num_attrs = map_num_attrs; + state->opts = opts; + + if (allow_paging) { + flags |= SDAP_SRCH_FLG_PAGING; + } + + if (attrsonly) { + flags |= SDAP_SRCH_FLG_ATTRS_ONLY; + } + + subreq = sdap_get_generic_ext_send(state, ev, opts, sh, search_base, + scope, filter, attrs, serverctrls, + clientctrls, sizelimit, timeout, + sdap_get_and_parse_generic_parse_entry, + state, flags); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_get_and_parse_generic_done, req); + + return req; +} + +static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt) +{ + errno_t ret; + struct sysdb_attrs *attrs; + struct sdap_get_and_parse_generic_state *state = + talloc_get_type(pvt, struct sdap_get_and_parse_generic_state); + + bool disable_range_rtrvl = dp_opt_get_bool(state->opts->basic, + SDAP_DISABLE_RANGE_RETRIEVAL); + + ret = sdap_parse_entry(state, sh, msg, + state->map, state->map_num_attrs, + &attrs, disable_range_rtrvl); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); + return ret; + } + + ret = add_to_reply(state, &state->sreply, attrs); + if (ret != EOK) { + talloc_free(attrs); + DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n"); + return ret; + } + + /* add_to_reply steals attrs, no need to free them here */ + return EOK; +} + +static void sdap_get_and_parse_generic_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_and_parse_generic_state *state = + tevent_req_data(req, struct sdap_get_and_parse_generic_state); + + return generic_ext_search_handler(subreq, state->opts); +} + +int sdap_get_and_parse_generic_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply) +{ + struct sdap_get_and_parse_generic_state *state = tevent_req_data(req, + struct sdap_get_and_parse_generic_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->sreply.reply_count; + *reply = talloc_steal(mem_ctx, state->sreply.reply); + + return EOK; +} + + +/* ==Simple generic search============================================== */ +struct sdap_get_generic_state { + size_t reply_count; + struct sysdb_attrs **reply; +}; + +static void sdap_get_generic_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + int scope, + const char *filter, + const char **attrs, + struct sdap_attr_map *map, + int map_num_attrs, + int timeout, + bool allow_paging) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_get_generic_state *state = NULL; + + req = tevent_req_create(memctx, &state, struct sdap_get_generic_state); + if (!req) return NULL; + + subreq = sdap_get_and_parse_generic_send(memctx, ev, opts, sh, search_base, + scope, filter, attrs, + map, map_num_attrs, + false, NULL, NULL, 0, timeout, + allow_paging); + if (subreq == NULL) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_get_generic_done, req); + + return req; +} + +static void sdap_get_generic_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_generic_state *state = + tevent_req_data(req, struct sdap_get_generic_state); + errno_t ret; + + ret = sdap_get_and_parse_generic_recv(subreq, state, + &state->reply_count, &state->reply); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + tevent_req_done(req); +} + +int sdap_get_generic_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply) +{ + struct sdap_get_generic_state *state = + tevent_req_data(req, struct sdap_get_generic_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->reply_count; + *reply = talloc_steal(mem_ctx, state->reply); + + return EOK; +} + +/* ==OpenLDAP deref search============================================== */ +static int sdap_x_deref_create_control(struct sdap_handle *sh, + const char *deref_attr, + const char **attrs, + LDAPControl **ctrl); + +static void sdap_x_deref_search_done(struct tevent_req *subreq); +static int sdap_x_deref_search_ctrls_destructor(void *ptr); + +static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt); +struct sdap_x_deref_search_state { + struct sdap_handle *sh; + struct sdap_op *op; + struct sdap_attr_map_info *maps; + LDAPControl **ctrls; + struct sdap_options *opts; + bool ldap_ignore_unreadable_references; + + struct sdap_deref_reply dreply; + int num_maps; +}; + +static struct tevent_req * +sdap_x_deref_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_options *opts, struct sdap_handle *sh, + const char *base_dn, const char *filter, + const char *deref_attr, const char **attrs, + struct sdap_attr_map_info *maps, int num_maps, + int timeout) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_x_deref_search_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct sdap_x_deref_search_state); + if (!req) return NULL; + + state->sh = sh; + state->maps = maps; + state->op = NULL; + state->opts = opts; + state->num_maps = num_maps; + state->ctrls = talloc_zero_array(state, LDAPControl *, 2); + if (state->ctrls == NULL) { + talloc_zfree(req); + return NULL; + } + talloc_set_destructor((TALLOC_CTX *) state->ctrls, + sdap_x_deref_search_ctrls_destructor); + + state->ldap_ignore_unreadable_references = dp_opt_get_bool(opts->basic, + SDAP_IGNORE_UNREADABLE_REFERENCES); + + ret = sdap_x_deref_create_control(sh, deref_attr, + attrs, &state->ctrls[0]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not create OpenLDAP deref control\n"); + talloc_zfree(req); + return NULL; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Dereferencing entry [%s] using OpenLDAP deref\n", base_dn); + subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, + filter == NULL ? LDAP_SCOPE_BASE + : LDAP_SCOPE_SUBTREE, + filter, attrs, + state->ctrls, NULL, 0, timeout, + sdap_x_deref_parse_entry, + state, SDAP_SRCH_FLG_PAGING); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_x_deref_search_done, req); + + return req; +} + +static int sdap_x_deref_create_control(struct sdap_handle *sh, + const char *deref_attr, + const char **attrs, + LDAPControl **ctrl) +{ + struct berval derefval; + int ret; + struct LDAPDerefSpec ds[2]; + + ds[0].derefAttr = discard_const(deref_attr); + ds[0].attributes = discard_const(attrs); + + ds[1].derefAttr = NULL; /* sentinel */ + + ret = ldap_create_deref_control_value(sh->ldap, ds, &derefval); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_create_deref_control_value failed: %s\n", + ldap_err2string(ret)); + return ret; + } + + ret = sdap_control_create(sh, LDAP_CONTROL_X_DEREF, + 1, &derefval, 1, ctrl); + ldap_memfree(derefval.bv_val); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed %d\n", ret); + return ret; + } + + return EOK; +} + +static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt) +{ + errno_t ret; + LDAPControl **ctrls = NULL; + LDAPControl *derefctrl = NULL; + LDAPDerefRes *deref_res = NULL; + LDAPDerefRes *dref; + struct sdap_deref_attrs **res; + TALLOC_CTX *tmp_ctx; + + struct sdap_x_deref_search_state *state = talloc_get_type(pvt, + struct sdap_x_deref_search_state); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + ret = ldap_get_entry_controls(state->sh->ldap, msg->msg, + &ctrls); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed\n"); + goto done; + } + + if (!ctrls) { + /* When we attempt to request attributes that are not present in + * the dereferenced links, some serves might not send the dereference + * control back at all. Be permissive and treat the search as if + * it didn't find anything. + */ + DEBUG(SSSDBG_MINOR_FAILURE, "No controls found for entry\n"); + ret = EOK; + goto done; + } + + res = NULL; + + derefctrl = ldap_control_find(LDAP_CONTROL_X_DEREF, ctrls, NULL); + if (!derefctrl) { + DEBUG(SSSDBG_FUNC_DATA, "No deref controls found\n"); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Got deref control\n"); + + ret = ldap_parse_derefresponse_control(state->sh->ldap, + derefctrl, + &deref_res); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_derefresponse_control failed: %s\n", + ldap_err2string(ret)); + goto done; + } + + for (dref = deref_res; dref; dref=dref->next) { + ret = sdap_parse_deref(tmp_ctx, state->maps, state->num_maps, + dref, &res); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_parse_deref failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = add_to_deref_reply(state, state->num_maps, + &state->dreply, res); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_to_deref_reply failed.\n"); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, + "All deref results from a single control parsed\n"); + ldap_derefresponse_free(deref_res); + deref_res = NULL; + + ret = EOK; +done: + if (ret != EOK && ret != ENOMEM) { + if (state->ldap_ignore_unreadable_references) { + DEBUG(SSSDBG_TRACE_FUNC, "Ignoring unreadable reference\n"); + ret = EOK; + } + } + + talloc_zfree(tmp_ctx); + ldap_controls_free(ctrls); + ldap_derefresponse_free(deref_res); + return ret; +} + +static void sdap_x_deref_search_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_x_deref_search_state *state = + tevent_req_data(req, struct sdap_x_deref_search_state); + + return generic_ext_search_handler(subreq, state->opts); +} + +static int sdap_x_deref_search_ctrls_destructor(void *ptr) +{ + LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *); + + if (ctrls && ctrls[0]) { + ldap_control_free(ctrls[0]); + } + + return 0; +} + +static int +sdap_x_deref_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply) +{ + struct sdap_x_deref_search_state *state = tevent_req_data(req, + struct sdap_x_deref_search_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->dreply.reply_count; + *reply = talloc_steal(mem_ctx, state->dreply.reply); + + return EOK; +} + +/* ==Security Descriptor (ACL) search=================================== */ +struct sdap_sd_search_state { + LDAPControl **ctrls; + struct sdap_options *opts; + size_t reply_count; + struct sysdb_attrs **reply; + struct sdap_reply sreply; + + /* Referrals returned by the search */ + size_t ref_count; + char **refs; +}; + +static int sdap_sd_search_create_control(struct sdap_handle *sh, + int val, + LDAPControl **ctrl); +static int sdap_sd_search_ctrls_destructor(void *ptr); +static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt); +static void sdap_sd_search_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_sd_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_options *opts, struct sdap_handle *sh, + const char *base_dn, int sd_flags, + const char **attrs, int timeout) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_sd_search_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct sdap_sd_search_state); + if (!req) return NULL; + + state->ctrls = talloc_zero_array(state, LDAPControl *, 2); + state->opts = opts; + if (state->ctrls == NULL) { + ret = EIO; + goto fail; + } + talloc_set_destructor((TALLOC_CTX *) state->ctrls, + sdap_sd_search_ctrls_destructor); + + ret = sdap_sd_search_create_control(sh, sd_flags, &state->ctrls[0]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not create SD control\n"); + ret = EIO; + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Searching entry [%s] using SD\n", base_dn); + subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, + LDAP_SCOPE_BASE, "(objectclass=*)", attrs, + state->ctrls, NULL, 0, timeout, + sdap_sd_search_parse_entry, + state, SDAP_SRCH_FLG_PAGING); + if (!subreq) { + ret = EIO; + goto fail; + } + tevent_req_set_callback(subreq, sdap_sd_search_done, req); + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static int sdap_sd_search_create_control(struct sdap_handle *sh, + int val, + LDAPControl **ctrl) +{ + struct berval *sdval; + int ret; + BerElement *ber = NULL; + ber = ber_alloc_t(LBER_USE_DER); + if (ber == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ber_alloc_t failed.\n"); + return ENOMEM; + } + + ret = ber_printf(ber, "{i}", val); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "ber_printf failed.\n"); + ber_free(ber, 1); + return EIO; + } + + ret = ber_flatten(ber, &sdval); + ber_free(ber, 1); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n"); + return EIO; + } + + ret = sdap_control_create(sh, LDAP_SERVER_SD_OID, 1, sdval, 1, ctrl); + ber_bvfree(sdval); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed\n"); + return ret; + } + + return EOK; +} + +static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt) +{ + errno_t ret; + struct sysdb_attrs *attrs; + struct sdap_sd_search_state *state = + talloc_get_type(pvt, struct sdap_sd_search_state); + + bool disable_range_rtrvl = dp_opt_get_bool(state->opts->basic, + SDAP_DISABLE_RANGE_RETRIEVAL); + + ret = sdap_parse_entry(state, sh, msg, + NULL, 0, + &attrs, disable_range_rtrvl); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); + return ret; + } + + ret = add_to_reply(state, &state->sreply, attrs); + if (ret != EOK) { + talloc_free(attrs); + DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n"); + return ret; + } + + /* add_to_reply steals attrs, no need to free them here */ + return EOK; +} + +static void sdap_sd_search_done(struct tevent_req *subreq) +{ + int ret; + + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_sd_search_state *state = + tevent_req_data(req, struct sdap_sd_search_state); + + ret = sdap_get_generic_ext_recv(subreq, state, + &state->ref_count, + &state->refs); + talloc_zfree(subreq); + + if (ret != EOK) { + if (ret == ETIMEDOUT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_ext_recv request failed: [%d]: %s " + "[ldap_network_timeout]\n", + ret, sss_strerror(ret)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_ext_recv request failed: [%d]: %s\n", + ret, sss_strerror(ret)); + } + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int sdap_sd_search_ctrls_destructor(void *ptr) +{ + LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *); + if (ctrls && ctrls[0]) { + ldap_control_free(ctrls[0]); + } + + return 0; +} + +int sdap_sd_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sysdb_attrs ***_reply, + size_t *_ref_count, + char ***_refs) +{ + struct sdap_sd_search_state *state = tevent_req_data(req, + struct sdap_sd_search_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_reply_count = state->sreply.reply_count; + *_reply = talloc_steal(mem_ctx, state->sreply.reply); + + if(_ref_count) { + *_ref_count = state->ref_count; + } + + if (_refs) { + *_refs = talloc_steal(mem_ctx, state->refs); + } + + return EOK; +} + +/* ==Attribute scoped search============================================ */ +struct sdap_asq_search_state { + struct sdap_attr_map_info *maps; + int num_maps; + LDAPControl **ctrls; + struct sdap_options *opts; + bool ldap_ignore_unreadable_references; + + struct sdap_deref_reply dreply; +}; + +static int sdap_asq_search_create_control(struct sdap_handle *sh, + const char *attr, + LDAPControl **ctrl); +static int sdap_asq_search_ctrls_destructor(void *ptr); +static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt); +static void sdap_asq_search_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_asq_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_options *opts, struct sdap_handle *sh, + const char *base_dn, const char *deref_attr, + const char **attrs, struct sdap_attr_map_info *maps, + int num_maps, int timeout) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_asq_search_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct sdap_asq_search_state); + if (!req) return NULL; + + state->maps = maps; + state->num_maps = num_maps; + state->ctrls = talloc_zero_array(state, LDAPControl *, 2); + state->opts = opts; + if (state->ctrls == NULL) { + talloc_zfree(req); + return NULL; + } + talloc_set_destructor((TALLOC_CTX *) state->ctrls, + sdap_asq_search_ctrls_destructor); + + state->ldap_ignore_unreadable_references = dp_opt_get_bool(opts->basic, + SDAP_IGNORE_UNREADABLE_REFERENCES); + + ret = sdap_asq_search_create_control(sh, deref_attr, &state->ctrls[0]); + if (ret != EOK) { + talloc_zfree(req); + DEBUG(SSSDBG_CRIT_FAILURE, "Could not create ASQ control\n"); + return NULL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Dereferencing entry [%s] using ASQ\n", base_dn); + subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, + LDAP_SCOPE_BASE, NULL, attrs, + state->ctrls, NULL, 0, timeout, + sdap_asq_search_parse_entry, + state, SDAP_SRCH_FLG_PAGING); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_asq_search_done, req); + + return req; +} + + +static int sdap_asq_search_create_control(struct sdap_handle *sh, + const char *attr, + LDAPControl **ctrl) +{ + struct berval *asqval; + int ret; + BerElement *ber = NULL; + + ber = ber_alloc_t(LBER_USE_DER); + if (ber == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ber_alloc_t failed.\n"); + return ENOMEM; + } + + ret = ber_printf(ber, "{s}", attr); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "ber_printf failed.\n"); + ber_free(ber, 1); + return EIO; + } + + ret = ber_flatten(ber, &asqval); + ber_free(ber, 1); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n"); + return EIO; + } + + ret = sdap_control_create(sh, LDAP_SERVER_ASQ_OID, 1, asqval, 1, ctrl); + ber_bvfree(asqval); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed\n"); + return ret; + } + + return EOK; +} + +static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt) +{ + errno_t ret; + struct sdap_asq_search_state *state = + talloc_get_type(pvt, struct sdap_asq_search_state); + struct berval **vals; + int i, mi; + struct sdap_attr_map *map; + int num_attrs = 0; + struct sdap_deref_attrs **res; + char *tmp; + char *dn = NULL; + TALLOC_CTX *tmp_ctx; + bool disable_range_rtrvl; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + res = talloc_array(tmp_ctx, struct sdap_deref_attrs *, + state->num_maps); + if (!res) { + ret = ENOMEM; + goto done; + } + + for (mi =0; mi < state->num_maps; mi++) { + res[mi] = talloc_zero(res, struct sdap_deref_attrs); + if (!res[mi]) { + ret = ENOMEM; + goto done; + } + res[mi]->map = state->maps[mi].map; + res[mi]->attrs = NULL; + } + + + tmp = ldap_get_dn(sh->ldap, msg->msg); + if (!tmp) { + ret = EINVAL; + goto done; + } + + dn = talloc_strdup(tmp_ctx, tmp); + ldap_memfree(tmp); + if (!dn) { + ret = ENOMEM; + goto done; + } + + /* Find all suitable maps in the list */ + vals = ldap_get_values_len(sh->ldap, msg->msg, "objectClass"); + if (!vals) { + DEBUG(SSSDBG_OP_FAILURE, + "Unknown entry type, no objectClass found for DN [%s]!\n", dn); + ret = EINVAL; + goto done; + } + for (mi =0; mi < state->num_maps; mi++) { + map = NULL; + for (i = 0; vals[i]; i++) { + /* the objectclass is always the first name in the map */ + if (strncasecmp(state->maps[mi].map[0].name, + vals[i]->bv_val, vals[i]->bv_len) == 0) { + /* it's an entry of the right type */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Matched objectclass [%s] on DN [%s], will use associated map\n", + state->maps[mi].map[0].name, dn); + map = state->maps[mi].map; + num_attrs = state->maps[mi].num_attrs; + break; + } + } + if (!map) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "DN [%s] did not match the objectClass [%s]\n", + dn, state->maps[mi].map[0].name); + continue; + } + + disable_range_rtrvl = dp_opt_get_bool(state->opts->basic, + SDAP_DISABLE_RANGE_RETRIEVAL); + + ret = sdap_parse_entry(res[mi], sh, msg, + map, num_attrs, + &res[mi]->attrs, disable_range_rtrvl); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + } + ldap_value_free_len(vals); + + ret = add_to_deref_reply(state, state->num_maps, + &state->dreply, res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_to_deref_reply failed.\n"); + goto done; + } + + ret = EOK; +done: + if (ret != EOK && ret != ENOMEM) { + if (state->ldap_ignore_unreadable_references) { + DEBUG(SSSDBG_TRACE_FUNC, "Ignoring unreadable reference [%s]\n", + dn != NULL ? dn : "(null)"); + ret = EOK; + } + } + talloc_zfree(tmp_ctx); + return ret; +} + +static void sdap_asq_search_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_asq_search_state *state = + tevent_req_data(req, struct sdap_asq_search_state); + + return generic_ext_search_handler(subreq, state->opts); +} + +static int sdap_asq_search_ctrls_destructor(void *ptr) +{ + LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *); + + if (ctrls && ctrls[0]) { + ldap_control_free(ctrls[0]); + } + + return 0; +} + +int sdap_asq_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply) +{ + struct sdap_asq_search_state *state = tevent_req_data(req, + struct sdap_asq_search_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->dreply.reply_count; + *reply = talloc_steal(mem_ctx, state->dreply.reply); + + return EOK; +} + +/* ==Generic Deref Search============================================ */ +enum sdap_deref_type { + SDAP_DEREF_OPENLDAP, + SDAP_DEREF_ASQ +}; + +struct sdap_deref_search_state { + struct sdap_handle *sh; + const char *base_dn; + const char *deref_attr; + + size_t reply_count; + struct sdap_deref_attrs **reply; + enum sdap_deref_type deref_type; + unsigned flags; +}; + +static void sdap_deref_search_done(struct tevent_req *subreq); +static void sdap_deref_search_with_filter_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_deref_search_with_filter_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + const char *filter, + const char *deref_attr, + const char **attrs, + int num_maps, + struct sdap_attr_map_info *maps, + int timeout, + unsigned flags) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_deref_search_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_deref_search_state); + if (!req) return NULL; + + state->sh = sh; + state->reply_count = 0; + state->reply = NULL; + state->flags = flags; + + if (sdap_is_control_supported(sh, LDAP_CONTROL_X_DEREF)) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports OpenLDAP deref\n"); + state->deref_type = SDAP_DEREF_OPENLDAP; + + subreq = sdap_x_deref_search_send(state, ev, opts, sh, search_base, + filter, deref_attr, attrs, maps, + num_maps, timeout); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot start OpenLDAP deref search\n"); + goto fail; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Server does not support any known deref method!\n"); + goto fail; + } + + tevent_req_set_callback(subreq, sdap_deref_search_with_filter_done, req); + return req; + +fail: + talloc_zfree(req); + return NULL; +} + +static void sdap_deref_search_with_filter_done(struct tevent_req *subreq) +{ + sdap_deref_search_done(subreq); +} + +int sdap_deref_search_with_filter_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply) +{ + return sdap_deref_search_recv(req, mem_ctx, reply_count, reply); +} + +struct tevent_req * +sdap_deref_search_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *base_dn, + const char *deref_attr, + const char **attrs, + int num_maps, + struct sdap_attr_map_info *maps, + int timeout) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_deref_search_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_deref_search_state); + if (!req) return NULL; + + state->sh = sh; + state->reply_count = 0; + state->reply = NULL; + state->base_dn = base_dn; + state->deref_attr = deref_attr; + + PROBE(SDAP_DEREF_SEARCH_SEND, state->base_dn, state->deref_attr); + + if (sdap_is_control_supported(sh, LDAP_SERVER_ASQ_OID)) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports ASQ\n"); + state->deref_type = SDAP_DEREF_ASQ; + + subreq = sdap_asq_search_send(state, ev, opts, sh, base_dn, + deref_attr, attrs, maps, num_maps, + timeout); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot start ASQ search\n"); + goto fail; + } + } else if (sdap_is_control_supported(sh, LDAP_CONTROL_X_DEREF)) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports OpenLDAP deref\n"); + state->deref_type = SDAP_DEREF_OPENLDAP; + + subreq = sdap_x_deref_search_send(state, ev, opts, sh, base_dn, NULL, + deref_attr, attrs, maps, num_maps, + timeout); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot start OpenLDAP deref search\n"); + goto fail; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Server does not support any known deref method!\n"); + goto fail; + } + + tevent_req_set_callback(subreq, sdap_deref_search_done, req); + return req; + +fail: + talloc_zfree(req); + return NULL; +} + +static void sdap_deref_search_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_deref_search_state *state = tevent_req_data(req, + struct sdap_deref_search_state); + int ret; + + switch (state->deref_type) { + case SDAP_DEREF_OPENLDAP: + ret = sdap_x_deref_search_recv(subreq, state, + &state->reply_count, &state->reply); + break; + case SDAP_DEREF_ASQ: + ret = sdap_asq_search_recv(subreq, state, + &state->reply_count, &state->reply); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown deref method %d\n", state->deref_type); + tevent_req_error(req, EINVAL); + return; + } + + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "dereference processing failed [%d]: %s\n", ret, strerror(ret)); + if (ret == ENOTSUP) { + state->sh->disable_deref = true; + } + + if (!(state->flags & SDAP_DEREF_FLG_SILENT)) { + if (ret == ENOTSUP) { + sss_log(SSS_LOG_WARNING, + "LDAP server claims to support deref, but deref search " + "failed. Disabling deref for further requests. You can " + "permanently disable deref by setting " + "ldap_deref_threshold to 0 in domain configuration."); + } else { + sss_log(SSS_LOG_WARNING, + "dereference processing failed : %s", strerror(ret)); + } + } + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_deref_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply) +{ + struct sdap_deref_search_state *state = tevent_req_data(req, + struct sdap_deref_search_state); + + PROBE(SDAP_DEREF_SEARCH_RECV, state->base_dn, state->deref_attr); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->reply_count; + *reply = talloc_steal(mem_ctx, state->reply); + + return EOK; +} + +bool sdap_has_deref_support_ex(struct sdap_handle *sh, + struct sdap_options *opts, + bool ignore_client) +{ + const char *deref_oids[][2] = { { LDAP_SERVER_ASQ_OID, "ASQ" }, + { LDAP_CONTROL_X_DEREF, "OpenLDAP" }, + { NULL, NULL } + }; + int i; + int deref_threshold; + + if (sh->disable_deref) { + return false; + } + + if (ignore_client == false) { + deref_threshold = dp_opt_get_int(opts->basic, SDAP_DEREF_THRESHOLD); + if (deref_threshold == 0) { + return false; + } + } + + for (i=0; deref_oids[i][0]; i++) { + if (sdap_is_control_supported(sh, deref_oids[i][0])) { + DEBUG(SSSDBG_TRACE_FUNC, "The server supports deref method %s\n", + deref_oids[i][1]); + return true; + } + } + + return false; +} + +bool sdap_has_deref_support(struct sdap_handle *sh, struct sdap_options *opts) +{ + return sdap_has_deref_support_ex(sh, opts, false); +} diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h new file mode 100644 index 0000000..5458d21 --- /dev/null +++ b/src/providers/ldap/sdap_async.h @@ -0,0 +1,455 @@ +/* + SSSD + + Async LDAP Helper routines + + Copyright (C) Simo Sorce + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_ASYNC_H_ +#define _SDAP_ASYNC_H_ + +#include +#include +#include +#include +#include "providers/backend.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_id_op.h" +#include "providers/fail_over.h" + +#define AD_TOKENGROUPS_ATTR "tokenGroups" + +struct tevent_req *sdap_connect_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + const char *uri, + struct sockaddr *sockaddr, + socklen_t sockaddr_len, + bool use_start_tls); +int sdap_connect_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_handle **sh); + +struct tevent_req *sdap_connect_host_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct resolv_ctx *resolv_ctx, + enum restrict_family family_order, + enum host_database *host_db, + const char *protocol, + const char *host, + int port, + bool use_start_tls); + +errno_t sdap_connect_host_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sdap_handle **_sh); + +/* Search users in LDAP, return them as attrs */ +enum sdap_entry_lookup_type { + SDAP_LOOKUP_SINGLE, /* Direct single-user/group lookup */ + SDAP_LOOKUP_WILDCARD, /* Multiple entries with a limit */ + SDAP_LOOKUP_ENUMERATE, /* Fetch all entries from the server */ +}; + +struct tevent_req *sdap_search_user_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type); +int sdap_search_user_recv(TALLOC_CTX *memctx, struct tevent_req *req, + char **higher_usn, struct sysdb_attrs ***users, + size_t *count); + +/* Search users in LDAP using the request above, save them to cache */ +struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type, + struct sysdb_attrs *mapped_attrs); +int sdap_get_users_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp); + +struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_options *opts, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type, + bool no_members); +int sdap_get_groups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp); + +struct tevent_req *sdap_get_netgroups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout); +int sdap_get_netgroups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp, + size_t *reply_count, + struct sysdb_attrs ***reply); + +struct tevent_req * +sdap_host_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *hostname, + struct sdap_attr_map *host_map, + struct sdap_search_base **search_bases); + +errno_t +sdap_host_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *host_count, + struct sysdb_attrs ***hosts); + +struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + const char *sasl_mech, + const char *sasl_user, + const char *user_dn, + struct sss_auth_token *authtok, + int simple_bind_timeout); + +errno_t sdap_auth_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_ppolicy_data **ppolicy); + +struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_handle *sh, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + const char *name, + int filter_type, + const char *extra_value, + const char **grp_attrs, + bool set_non_posix); +int sdap_get_initgr_recv(struct tevent_req *req); + +struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + char *user_dn, + const char *password, + const char *new_password, + int timeout); +errno_t sdap_exop_modify_passwd_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **user_error_msg); + +struct tevent_req * +sdap_modify_passwd_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + int timeout, + char *attr, + const char *user_dn, + const char *new_password); + +errno_t sdap_modify_passwd_recv(struct tevent_req *req, + TALLOC_CTX * mem_ctx, + char **_user_error_message); + +struct tevent_req * +sdap_modify_shadow_lastchange_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + const char *dn, + char *attr); + +errno_t sdap_modify_shadow_lastchange_recv(struct tevent_req *req); + +enum connect_tls { + CON_TLS_DFL, + CON_TLS_ON, + CON_TLS_OFF +}; + +struct tevent_req *sdap_cli_connect_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct be_ctx *be, + struct sdap_service *service, + bool skip_rootdse, + enum connect_tls force_tls, + bool skip_auth); +int sdap_cli_connect_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + bool *can_retry, + struct sdap_handle **gsh, + struct sdap_server_opts **srv_opts); + +/* Exposes all options of generic send while allowing to parse by map */ +struct tevent_req *sdap_get_and_parse_generic_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + int scope, + const char *filter, + const char **attrs, + struct sdap_attr_map *map, + int map_num_attrs, + int attrsonly, + LDAPControl **serverctrls, + LDAPControl **clientctrls, + int sizelimit, + int timeout, + bool allow_paging); +int sdap_get_and_parse_generic_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply); + +struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + int scope, + const char *filter, + const char **attrs, + struct sdap_attr_map *map, + int map_num_attrs, + int timeout, + bool allow_paging); +int sdap_get_generic_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, size_t *reply_count, + struct sysdb_attrs ***reply_list); + +bool sdap_has_deref_support_ex(struct sdap_handle *sh, + struct sdap_options *opts, + bool ignore_client); +bool sdap_has_deref_support(struct sdap_handle *sh, + struct sdap_options *opts); + +enum sdap_deref_flags { + SDAP_DEREF_FLG_SILENT = 1 << 0, /* Do not warn if dereference fails */ +}; + +struct tevent_req * +sdap_deref_search_with_filter_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + const char *filter, + const char *deref_attr, + const char **attrs, + int num_maps, + struct sdap_attr_map_info *maps, + int timeout, + unsigned flags); +int sdap_deref_search_with_filter_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply); + +struct tevent_req * +sdap_deref_search_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *base_dn, + const char *deref_attr, + const char **attrs, + int num_maps, + struct sdap_attr_map_info *maps, + int timeout); +int sdap_deref_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply); + + +struct tevent_req * +sdap_sd_search_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *base_dn, + int sd_flags, + const char **attrs, + int timeout); +int sdap_sd_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sysdb_attrs ***_reply, + size_t *_ref_count, + char ***_refs); + +errno_t +sdap_attrs_add_ldap_attr(struct sysdb_attrs *ldap_attrs, + const char *attr_name, + const char *attr_desc, + bool multivalued, + const char *name, + struct sysdb_attrs *attrs); + +#define sdap_attrs_add_string(ldap_attrs, attr_name, attr_desc, name, attrs) \ + sdap_attrs_add_ldap_attr(ldap_attrs, attr_name, attr_desc, \ + false, name, attrs) + +#define sdap_attrs_add_list(ldap_attrs, attr_name, attr_desc, name, attrs) \ + sdap_attrs_add_ldap_attr(ldap_attrs, attr_name, attr_desc, \ + true, name, attrs) + +errno_t +sdap_save_all_names(const char *name, + struct sysdb_attrs *ldap_attrs, + struct sss_domain_info *dom, + enum sysdb_member_type entry_type, + struct sysdb_attrs *attrs); + +struct tevent_req * +sdap_get_services_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration); +errno_t +sdap_get_services_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value); + +struct tevent_req * +enum_services_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge); + +errno_t +enum_services_recv(struct tevent_req *req); + +struct tevent_req * +sdap_get_iphost_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration); +errno_t +sdap_get_iphost_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value); + +struct tevent_req * +enum_iphosts_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge); + +errno_t +enum_iphosts_recv(struct tevent_req *req); + +struct tevent_req * +sdap_get_ipnetwork_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration); + +errno_t +sdap_get_ipnetwork_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value); + +struct tevent_req * +enum_ipnetworks_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge); + +errno_t +enum_ipnetworks_recv(struct tevent_req *req); + +struct tevent_req * +sdap_ad_tokengroups_initgroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_handle *sh, + const char *name, + const char *orig_dn, + int timeout, + bool use_id_mapping); + +errno_t +sdap_ad_tokengroups_initgroups_recv(struct tevent_req *req); + +errno_t +sdap_handle_id_collision_for_incomplete_groups(struct data_provider *dp, + struct sss_domain_info *domain, + const char *name, + gid_t gid, + const char *original_dn, + const char *sid_str, + const char *uuid, + bool posix, + time_t now); + +struct sdap_id_conn_ctx *get_ldap_conn_from_sdom_pvt(struct sdap_options *opts, + struct sdap_domain *sdom); +#endif /* _SDAP_ASYNC_H_ */ diff --git a/src/providers/ldap/sdap_async_ad.h b/src/providers/ldap/sdap_async_ad.h new file mode 100644 index 0000000..a5f47a1 --- /dev/null +++ b/src/providers/ldap/sdap_async_ad.h @@ -0,0 +1,59 @@ +/* + SSSD - header files for AD specific enhancement in the common LDAP/SDAP + code + + Authors: + Sumit Bose + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef SDAP_ASYNC_AD_H_ +#define SDAP_ASYNC_AD_H_ + +errno_t sdap_ad_save_group_membership_with_idmapping(const char *username, + struct sdap_options *opts, + struct sss_domain_info *user_dom, + struct sdap_idmap_ctx *idmap_ctx, + size_t num_sids, + char **sids); + +errno_t +sdap_ad_tokengroups_get_posix_members(TALLOC_CTX *mem_ctx, + struct sss_domain_info *user_domain, + size_t num_sids, + char **sids, + size_t *_num_missing, + char ***_missing, + size_t *_num_valid, + char ***_valid_groups); + +errno_t +sdap_ad_tokengroups_update_members(const char *username, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + char **ldap_groups); +struct tevent_req * +sdap_ad_resolve_sids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + struct sdap_options *opts, + struct sss_domain_info *domain, + char **sids); + +errno_t sdap_ad_resolve_sids_recv(struct tevent_req *req); +#endif /* SDAP_ASYNC_AD_H_ */ diff --git a/src/providers/ldap/sdap_async_autofs.c b/src/providers/ldap/sdap_async_autofs.c new file mode 100644 index 0000000..8a542f9 --- /dev/null +++ b/src/providers/ldap/sdap_async_autofs.c @@ -0,0 +1,1479 @@ +/* + SSSD + + Async LDAP Helper routines for autofs + + Authors: + Jakub Hrozek + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" +#include "db/sysdb_autofs.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_autofs.h" +#include "providers/ldap/sdap_ops.h" + +/* ====== Utility functions ====== */ +static const char * +get_autofs_map_name(struct sysdb_attrs *map, struct sdap_options *opts) +{ + errno_t ret; + struct ldb_message_element *el; + + ret = sysdb_attrs_get_el(map, + opts->autofs_mobject_map[SDAP_AT_AUTOFS_MAP_NAME].sys_name, + &el); + if (ret) return NULL; + if (el->num_values == 0) return NULL; + + return (const char *)el->values[0].data; +} + +static const char * +get_autofs_entry_attr(struct sysdb_attrs *entry, struct sdap_options *opts, + enum sdap_autofs_entry_attrs attr) +{ + errno_t ret; + struct ldb_message_element *el; + + ret = sysdb_attrs_get_el(entry, + opts->autofs_entry_map[attr].sys_name, + &el); + if (ret) return NULL; + if (el->num_values != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected one entry got %d\n", el->num_values); + return NULL; + } + + return (const char *)el->values[0].data; +} + +static const char * +get_autofs_entry_key(struct sysdb_attrs *entry, struct sdap_options *opts) +{ + return get_autofs_entry_attr(entry, opts, SDAP_AT_AUTOFS_ENTRY_KEY); +} + +static const char * +get_autofs_entry_value(struct sysdb_attrs *entry, struct sdap_options *opts) +{ + return get_autofs_entry_attr(entry, opts, SDAP_AT_AUTOFS_ENTRY_VALUE); +} + +static errno_t +add_autofs_entry(struct sss_domain_info *domain, + const char *map, + struct sdap_options *opts, + struct sysdb_attrs *entry, + time_t now) +{ + const char *key; + const char *value; + + key = get_autofs_entry_key(entry, opts); + if (!key) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get autofs entry key\n"); + return EINVAL; + } + + value = get_autofs_entry_value(entry, opts); + if (!value) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get autofs entry value\n"); + return EINVAL; + } + + return sysdb_save_autofsentry(domain, map, key, value, NULL, + domain->autofsmap_timeout, now); +} + +static errno_t +save_autofs_entries(struct sss_domain_info *domain, + struct sdap_options *opts, + const char *map, + char **add_dn_list, + hash_table_t *entry_hash) +{ + hash_key_t key; + hash_value_t value; + size_t i; + int hret; + errno_t ret; + struct sysdb_attrs *entry; + time_t now; + + if (!add_dn_list) { + return EOK; + } + + now = time(NULL); + + for (i=0; add_dn_list[i]; i++) { + key.type = HASH_KEY_STRING; + key.str = (char *) add_dn_list[i]; + + hret = hash_lookup(entry_hash, &key, &value); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot retrieve entry [%s] from hash\n", add_dn_list[i]); + continue; + } + + entry = talloc_get_type(value.ptr, struct sysdb_attrs); + if (!entry) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot retrieve entry [%s] from ptr\n", add_dn_list[i]); + continue; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Saving autofs entry [%s]\n", add_dn_list[i]); + ret = add_autofs_entry(domain, map, opts, entry, now); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot save entry [%s] to cache\n", add_dn_list[i]); + continue; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Saved entry [%s]\n", add_dn_list[i]); + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "All entries saved\n"); + return EOK; +} + +static errno_t +del_autofs_entries(struct sss_domain_info *dom, + char **del_dn_list) +{ + size_t i; + errno_t ret; + + for (i=0; del_dn_list[i]; i++) { + DEBUG(SSSDBG_TRACE_FUNC, + "Removing autofs entry [%s]\n", del_dn_list[i]); + + ret = sysdb_del_autofsentry(dom, del_dn_list[i]); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot delete entry %s\n", del_dn_list[i]); + continue; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "All entries removed\n"); + return EOK; +} + +static errno_t +save_autofs_map(struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs *map, + bool enumerated) +{ + const char *mapname; + const char *origdn; + errno_t ret; + time_t now; + + mapname = get_autofs_map_name(map, opts); + if (!mapname) return EINVAL; + + ret = sysdb_attrs_get_string(map, SYSDB_ORIG_DN, &origdn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get original dn [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + now = time(NULL); + + ret = sysdb_save_autofsmap(dom, mapname, mapname, origdn, + NULL, dom->autofsmap_timeout, now, enumerated); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +struct automntmaps_process_members_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + int timeout; + + const char *orig_dn; + char *base_filter; + char *filter; + const char **attrs; + size_t base_iter; + struct sdap_search_base **search_bases; + + struct sysdb_attrs *map; + + struct sysdb_attrs **entries; + size_t entries_count; +}; + +static void +automntmaps_process_members_done(struct tevent_req *subreq); +static errno_t +automntmaps_process_members_next_base(struct tevent_req *req); + +static struct tevent_req * +automntmaps_process_members_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sss_domain_info *dom, + struct sdap_search_base **search_bases, + int timeout, + struct sysdb_attrs *map) +{ + errno_t ret; + struct tevent_req *req; + struct automntmaps_process_members_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct automntmaps_process_members_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->timeout = timeout; + state->base_iter = 0; + state->map = map; + state->search_bases = search_bases; + + state->base_filter = talloc_asprintf(state, "(&(%s=*)(objectclass=%s))", + opts->autofs_entry_map[SDAP_AT_AUTOFS_ENTRY_KEY].name, + opts->autofs_entry_map[SDAP_OC_AUTOFS_ENTRY].name); + if (!state->base_filter) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto immediate; + } + + ret = build_attrs_from_map(state, opts->autofs_entry_map, + SDAP_OPTS_AUTOFS_ENTRY, NULL, + &state->attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build attributes from map\n"); + ret = ENOMEM; + goto immediate; + } + + + ret = sysdb_attrs_get_string(state->map, SYSDB_ORIG_DN, &state->orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get originalDN\n"); + goto immediate; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Examining autofs map [%s]\n", state->orig_dn); + + ret = automntmaps_process_members_next_base(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "search failed [%d]: %s\n", ret, strerror(ret)); + goto immediate; + } + + return req; + +immediate: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t +automntmaps_process_members_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct automntmaps_process_members_state *state = + tevent_req_data(req, struct automntmaps_process_members_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for automount map entries with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + state->orig_dn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->autofs_entry_map, + SDAP_OPTS_AUTOFS_ENTRY, + state->timeout, true); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot start search for entries\n"); + return EIO; + } + tevent_req_set_callback(subreq, automntmaps_process_members_done, req); + + return EOK; +} + +static void +automntmaps_process_members_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct automntmaps_process_members_state *state = + tevent_req_data(req, struct automntmaps_process_members_state); + errno_t ret; + struct sysdb_attrs **entries; + size_t entries_count, i; + + ret = sdap_get_generic_recv(subreq, state, + &entries_count, &entries); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (entries_count > 0) { + state->entries = talloc_realloc(state, state->entries, + struct sysdb_attrs *, + state->entries_count + entries_count + 1); + if (state->entries == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + for (i=0; i < entries_count; i++) { + state->entries[state->entries_count + i] = + talloc_steal(state->entries, entries[i]); + } + + state->entries_count += entries_count; + state->entries[state->entries_count] = NULL; + } + + state->base_iter++; + if (state->search_bases[state->base_iter]) { + ret = automntmaps_process_members_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "No more search bases to try\n"); + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for autofs entries, returned %zu results.\n", + state->entries_count); + + tevent_req_done(req); + return; +} + +static errno_t +automntmaps_process_members_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *entries_count, + struct sysdb_attrs ***entries) +{ + struct automntmaps_process_members_state *state; + state = tevent_req_data(req, struct automntmaps_process_members_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (entries_count) { + *entries_count = state->entries_count; + } + + if (entries) { + *entries = talloc_steal(mem_ctx, state->entries); + } + + return EOK; +} + +struct sdap_get_automntmap_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + + char *higher_timestamp; + + struct sysdb_attrs **map; + size_t count; + + struct sysdb_attrs **entries; + size_t entries_count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t +sdap_get_automntmap_next_base(struct tevent_req *req); +static void +sdap_get_automntmap_process(struct tevent_req *subreq); + +static struct tevent_req * +sdap_get_automntmap_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_get_automntmap_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_get_automntmap_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->attrs = attrs; + state->higher_timestamp = NULL; + state->map = NULL; + state->count = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + + ret = sdap_get_automntmap_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + return req; +} + +static errno_t +sdap_get_automntmap_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_automntmap_state *state; + + state = tevent_req_data(req, struct sdap_get_automntmap_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for automount maps with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->autofs_mobject_map, SDAP_OPTS_AUTOFS_MAP, + state->timeout, + false); + if (!subreq) { + return EIO; + } + tevent_req_set_callback(subreq, sdap_get_automntmap_process, req); + + return EOK; +} + +static void +sdap_get_automntmap_done(struct tevent_req *subreq); + +static void +sdap_get_automntmap_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_automntmap_state *state = tevent_req_data(req, + struct sdap_get_automntmap_state); + errno_t ret; + + ret = sdap_get_generic_recv(subreq, state, + &state->count, &state->map); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for autofs maps, returned %zu results.\n", state->count); + + if (state->count == 0) { + /* No maps found in this search */ + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_automntmap_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ENOENT); + } + return; + } + + tevent_req_error(req, ENOENT); + return; + } else if (state->count > 1) { + DEBUG(SSSDBG_OP_FAILURE, + "The search yielded more than one autofs map\n"); + tevent_req_error(req, EIO); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Processing autofs maps\n"); + subreq = automntmaps_process_members_send(state, state->ev, state->opts, + state->sh, state->dom, + state->search_bases, + state->timeout, + state->map[0]); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_automntmap_done, req); + + return; +} + +static void +sdap_get_automntmap_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_automntmap_state *state = tevent_req_data(req, + struct sdap_get_automntmap_state); + errno_t ret; + + ret = automntmaps_process_members_recv(subreq, state, &state->entries_count, + &state->entries); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "automount map members received\n"); + tevent_req_done(req); + return; +} + +static errno_t +sdap_get_automntmap_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **map, + size_t *entries_count, + struct sysdb_attrs ***entries) +{ + struct sdap_get_automntmap_state *state = tevent_req_data(req, + struct sdap_get_automntmap_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (map) { + *map = talloc_steal(mem_ctx, state->map[0]); + } + + if (entries_count) { + *entries_count = state->entries_count; + } + + if (entries) { + *entries = talloc_steal(mem_ctx, state->entries); + } + + return EOK; +} + +struct sdap_autofs_setautomntent_state { + char *filter; + const char **attrs; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sysdb_ctx *sysdb; + struct sdap_id_op *sdap_op; + struct sss_domain_info *dom; + + const char *mapname; + struct sysdb_attrs *map; + struct sysdb_attrs **entries; + size_t entries_count; + + int dp_error; +}; + +static void +sdap_autofs_setautomntent_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_autofs_setautomntent_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_handle *sh, + struct sdap_id_op *op, + struct sdap_options *opts, + const char *mapname) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_autofs_setautomntent_state *state; + char *clean_mapname; + errno_t ret; + + req = tevent_req_create(memctx, &state, + struct sdap_autofs_setautomntent_state); + if (!req) return NULL; + + if (!mapname) { + DEBUG(SSSDBG_CRIT_FAILURE, "No map name given\n"); + ret = EINVAL; + goto fail; + } + + state->sh = sh; + state->sysdb = sysdb; + state->opts = opts; + state->sdap_op = op; + state->dom = dom; + state->mapname = mapname; + + ret = sss_filter_sanitize(state, mapname, &clean_mapname); + if (ret != EOK) { + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + state->opts->autofs_mobject_map[SDAP_AT_AUTOFS_MAP_NAME].name, + clean_mapname, + state->opts->autofs_mobject_map[SDAP_OC_AUTOFS_MAP].name); + if (!state->filter) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto fail; + } + talloc_free(clean_mapname); + + ret = build_attrs_from_map(state, state->opts->autofs_mobject_map, + SDAP_OPTS_AUTOFS_MAP, NULL, + &state->attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build attributes from map\n"); + ret = ENOMEM; + goto fail; + } + + subreq = sdap_get_automntmap_send(state, ev, dom, + state->opts, + state->opts->sdom->autofs_search_bases, + state->sh, + state->attrs, state->filter, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT)); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_automntmap_send failed\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sdap_autofs_setautomntent_done, req); + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +sdap_autofs_setautomntent_save(struct tevent_req *req); + +static void +sdap_autofs_setautomntent_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_autofs_setautomntent_state *state = tevent_req_data(req, + struct sdap_autofs_setautomntent_state); + + ret = sdap_get_automntmap_recv(subreq, state, &state->map, + &state->entries_count, &state->entries); + talloc_zfree(subreq); + if (ret != EOK) { + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not find automount map\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_get_automntmap_recv failed [%d]: %s\n", + ret, strerror(ret)); + } + tevent_req_error(req, ret); + return; + } + + ret = sdap_autofs_setautomntent_save(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not save automount map\n"); + tevent_req_error(req, ret); + return; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; +} + +static errno_t +sdap_autofs_setautomntent_save(struct tevent_req *req) +{ + struct sdap_autofs_setautomntent_state *state = tevent_req_data(req, + struct sdap_autofs_setautomntent_state); + errno_t ret, tret; + bool in_transaction = false; + TALLOC_CTX *tmp_ctx; + struct ldb_message **entries = NULL; + size_t count; + const char *key; + const char *val; + char **sysdb_entrylist = NULL; + char **ldap_entrylist = NULL; + char **add_entries = NULL; + char **del_entries = NULL; + size_t i, j; + + hash_table_t *entry_hash = NULL; + hash_key_t hkey; + hash_value_t value; + int hret; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + DEBUG(SSSDBG_TRACE_LIBS, + "Got %zu map entries from LDAP\n", state->entries_count); + if (state->entries_count == 0) { + /* No entries for this map in LDAP. + * We need to ensure that there are no entries + * in the sysdb either. + */ + ldap_entrylist = NULL; + } else { + ldap_entrylist = talloc_array(tmp_ctx, char *, + state->entries_count+1); + if (!ldap_entrylist) { + ret = ENOMEM; + goto done; + } + + ret = sss_hash_create(state, 0, &entry_hash); + if (ret) { + goto done; + } + + /* Get a list of the map members by DN */ + for (i=0, j=0; i < state->entries_count; i++) { + key = get_autofs_entry_key(state->entries[i], state->opts); + val = get_autofs_entry_value(state->entries[i], state->opts); + if (!key || !val) { + DEBUG(SSSDBG_MINOR_FAILURE, "Malformed entry, skipping\n"); + continue; + } + + ldap_entrylist[j] = sysdb_autofsentry_strdn(ldap_entrylist, + state->dom, + state->mapname, + key, val); + if (!ldap_entrylist[j]) { + ret = ENOMEM; + goto done; + } + + hkey.type = HASH_KEY_STRING; + hkey.str = ldap_entrylist[j]; + value.type = HASH_VALUE_PTR; + value.ptr = state->entries[i]; + + hret = hash_enter(entry_hash, &hkey, &value); + if (hret != HASH_SUCCESS) { + ret = EIO; + goto done; + } + + j++; + } + /* terminate array with NULL after the last retrieved entry */ + ldap_entrylist[j] = NULL; + } + + ret = sysdb_autofs_entries_by_map(tmp_ctx, state->dom, state->mapname, + &count, &entries); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "cache lookup for the map failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Got %zu map entries from sysdb\n", count); + if (count == 0) { + /* No map members for this map in sysdb currently */ + sysdb_entrylist = NULL; + } else { + sysdb_entrylist = talloc_array(state, char *, count+1); + if (!sysdb_entrylist) { + ret = ENOMEM; + goto done; + } + + /* Get a list of the map members by DN */ + for (i=0; i < count; i++) { + sysdb_entrylist[i] = talloc_strdup(sysdb_entrylist, + ldb_dn_get_linearized(entries[i]->dn)); + if (!sysdb_entrylist[i]) { + ret = ENOMEM; + goto done; + } + } + sysdb_entrylist[count] = NULL; + } + + /* Find the differences between the sysdb and LDAP lists + * Entries in the sysdb only must be removed. + */ + ret = diff_string_lists(tmp_ctx, ldap_entrylist, sysdb_entrylist, + &add_entries, &del_entries, NULL); + if (ret != EOK) goto done; + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot start sysdb transaction [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + in_transaction = true; + + /* Save the map itself */ + ret = save_autofs_map(state->dom, state->opts, state->map, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save autofs map entry [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + /* Create entries that don't exist yet */ + if (add_entries && add_entries[0]) { + ret = save_autofs_entries(state->dom, state->opts, + state->mapname, add_entries, + entry_hash); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save autofs entries [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + /* Delete entries that don't exist anymore */ + if (del_entries && del_entries[0]) { + ret = del_autofs_entries(state->dom, del_entries); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete autofs entries [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot commit sysdb transaction [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot cancel sysdb transaction [%d]: %s\n", + ret, strerror(ret)); + } + } + talloc_zfree(tmp_ctx); + return ret; +} + +errno_t +sdap_autofs_setautomntent_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct sdap_autofs_get_map_state { + struct sdap_id_ctx *id_ctx; + struct sdap_options *opts; + struct sdap_id_op *sdap_op; + const char *mapname; + int dp_error; +}; + +static errno_t sdap_autofs_get_map_retry(struct tevent_req *req); +static void sdap_autofs_get_map_connect_done(struct tevent_req *subreq); +static void sdap_autofs_get_map_done(struct tevent_req *subreq); + +struct tevent_req *sdap_autofs_get_map_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + const char *mapname) +{ + struct tevent_req *req; + struct sdap_autofs_get_map_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_autofs_get_map_state); + if (!req) { + return NULL; + } + + state->id_ctx = id_ctx; + state->opts = id_ctx->opts; + state->mapname = mapname; + state->dp_error = DP_ERR_FATAL; + + state->sdap_op = sdap_id_op_create(state, id_ctx->conn->conn_cache); + if (!state->sdap_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = sdap_autofs_get_map_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, id_ctx->be->ev); + + return req; +} + +static errno_t sdap_autofs_get_map_retry(struct tevent_req *req) +{ + struct sdap_autofs_get_map_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct sdap_autofs_get_map_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: " + "%d(%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_map_connect_done, req); + + return EAGAIN; +} + +static void sdap_autofs_get_map_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_autofs_get_map_state *state; + char *filter; + char *safe_mapname; + const char **attrs; + int dp_error; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_autofs_get_map_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "LDAP connection failed " + "[%d]: %s\n", ret, strerror(ret)); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "LDAP connection successful\n"); + + ret = sss_filter_sanitize(state, state->mapname, &safe_mapname); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + state->opts->autofs_mobject_map[SDAP_AT_AUTOFS_MAP_NAME].name, + safe_mapname, + state->opts->autofs_mobject_map[SDAP_OC_AUTOFS_MAP].name); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build filter\n"); + tevent_req_error(req, ret); + return; + } + + ret = build_attrs_from_map(state, state->opts->autofs_mobject_map, + SDAP_OPTS_AUTOFS_MAP, NULL, &attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build attributes from map\n"); + tevent_req_error(req, ret); + return; + } + + subreq = sdap_search_bases_return_first_send(state, state->id_ctx->be->ev, + state->opts, sdap_id_op_handle(state->sdap_op), + state->opts->sdom->autofs_search_bases, + state->opts->autofs_mobject_map, false, + dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT), + filter, attrs, NULL); + if (subreq == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_map_done, req); +} + +static void sdap_autofs_get_map_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_autofs_get_map_state *state; + struct sysdb_attrs **reply; + size_t reply_count; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_autofs_get_map_state); + + ret = sdap_search_bases_return_first_recv(subreq, state, &reply_count, + &reply); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &state->dp_error); + if (state->dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_autofs_get_map_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (reply_count == 0) { + ret = sysdb_delete_autofsmap(state->id_ctx->be->domain, state->mapname); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete autofs map %s [%d]: %s\n", + state->mapname, ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; + } + + ret = save_autofs_map(state->id_ctx->be->domain, state->opts, reply[0], false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save autofs map %s [%d]: %s\n", + state->mapname, ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sdap_autofs_get_map_recv(struct tevent_req *req, + int *dp_error) +{ + struct sdap_autofs_get_map_state *state; + + state = tevent_req_data(req, struct sdap_autofs_get_map_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +struct sdap_autofs_get_entry_state { + struct sdap_id_ctx *id_ctx; + struct sdap_options *opts; + struct sdap_id_op *sdap_op; + const char *mapname; + const char *entryname; + int dp_error; +}; + +static errno_t sdap_autofs_get_entry_retry(struct tevent_req *req); +static void sdap_autofs_get_entry_connect_done(struct tevent_req *subreq); +static void sdap_autofs_get_entry_done(struct tevent_req *subreq); + +struct tevent_req *sdap_autofs_get_entry_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + const char *mapname, + const char *entryname) +{ + struct tevent_req *req; + struct sdap_autofs_get_entry_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_autofs_get_entry_state); + if (!req) { + return NULL; + } + + state->id_ctx = id_ctx; + state->opts = id_ctx->opts; + state->mapname = mapname; + state->entryname = entryname; + state->dp_error = DP_ERR_FATAL; + + state->sdap_op = sdap_id_op_create(state, id_ctx->conn->conn_cache); + if (!state->sdap_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = sdap_autofs_get_entry_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, id_ctx->be->ev); + + return req; +} + +static errno_t sdap_autofs_get_entry_retry(struct tevent_req *req) +{ + struct sdap_autofs_get_entry_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct sdap_autofs_get_entry_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: " + "%d(%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_entry_connect_done, req); + + return EAGAIN; +} + +static void sdap_autofs_get_entry_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_autofs_get_entry_state *state; + struct ldb_message *map; + char *filter; + char *safe_entryname; + const char **attrs; + const char *base_dn; + int dp_error; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_autofs_get_entry_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "LDAP connection failed " + "[%d]: %s\n", ret, strerror(ret)); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "LDAP connection successful\n"); + + ret = sysdb_get_map_byname(state, state->id_ctx->be->domain, + state->mapname, &map); + if (ret == ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Map %s does not exist!\n", state->mapname); + tevent_req_error(req, ret); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get map %s [%d]: %s\n", + state->mapname, ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + base_dn = ldb_msg_find_attr_as_string(map, SYSDB_ORIG_DN, NULL); + if (base_dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get originalDN\n"); + tevent_req_error(req, ERR_INTERNAL); + return; + } + + ret = sss_filter_sanitize(state, state->entryname, &safe_entryname); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + state->opts->autofs_entry_map[SDAP_AT_AUTOFS_ENTRY_KEY].name, + safe_entryname, + state->opts->autofs_entry_map[SDAP_OC_AUTOFS_ENTRY].name); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build filter\n"); + tevent_req_error(req, ret); + return; + } + + ret = build_attrs_from_map(state, state->opts->autofs_entry_map, + SDAP_OPTS_AUTOFS_ENTRY, NULL, &attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build attributes from map\n"); + tevent_req_error(req, ret); + return; + } + + subreq = sdap_search_bases_return_first_send(state, state->id_ctx->be->ev, + state->opts, sdap_id_op_handle(state->sdap_op), + state->opts->sdom->autofs_search_bases, + state->opts->autofs_entry_map, false, + dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT), + filter, attrs, base_dn); + if (subreq == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_entry_done, req); +} + +static errno_t sdap_autofs_save_entry(struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs *newentry, + const char *mapname, + const char *entryname); + +static void sdap_autofs_get_entry_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_autofs_get_entry_state *state; + struct sysdb_attrs **reply; + size_t reply_count; + size_t i; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_autofs_get_entry_state); + + ret = sdap_search_bases_return_first_recv(subreq, state, &reply_count, + &reply); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &state->dp_error); + if (state->dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_autofs_get_entry_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* This will delete the entry if it already exist. */ + if (reply_count == 0) { + ret = sdap_autofs_save_entry(state->id_ctx->be->domain, state->opts, + NULL, state->mapname, state->entryname); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + goto done; + } + + /* If other attribute then automountKey is in the distinguished name and + * there are multiple entries with different casing then we may get more + * then one result. */ + for (i = 0; i < reply_count; i++) { + ret = sdap_autofs_save_entry(state->id_ctx->be->domain, state->opts, + reply[i], state->mapname, + state->entryname); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + } + +done: + tevent_req_done(req); + return; +} + +errno_t sdap_autofs_get_entry_recv(struct tevent_req *req, + int *dp_error) +{ + struct sdap_autofs_get_entry_state *state; + + state = tevent_req_data(req, struct sdap_autofs_get_entry_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +static errno_t sdap_autofs_save_entry(struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs *newentry, + const char *mapname, + const char *entryname) +{ + bool in_transaction = false; + errno_t ret; + int tret; + + ret = sysdb_transaction_start(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot start sysdb transaction [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + in_transaction = true; + + /* Delete existing entry to cover case where new entry has the same key + * but different automountInformation. Because the dn is created from the + * combination of key and information it would be possible to end up with + * two entries with same key but different information otherwise. + */ + ret = sysdb_del_autofsentry_by_key(domain, mapname, entryname); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot delete entry %s:%s\n", + mapname, entryname); + goto done; + } + + if (newentry != NULL) { + ret = add_autofs_entry(domain, mapname, opts, newentry, time(NULL)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save autofs entry %s:%s [%d]: %s\n", + mapname, entryname, ret, sss_strerror(ret)); + goto done; + } + } + + ret = sysdb_transaction_commit(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot commit sysdb transaction [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + in_transaction = false; + + ret = EOK; + +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(domain->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot cancel sysdb transaction " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + } + + return ret; +} diff --git a/src/providers/ldap/sdap_async_connection.c b/src/providers/ldap/sdap_async_connection.c new file mode 100644 index 0000000..e863872 --- /dev/null +++ b/src/providers/ldap/sdap_async_connection.c @@ -0,0 +1,2386 @@ +/* + SSSD + + Async LDAP Helper routines + + Copyright (C) Simo Sorce - 2009 + Copyright (C) 2010, rhafer@suse.de, Novell Inc. + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include "util/util.h" +#include "util/sss_krb5.h" +#include "util/sss_ldap.h" +#include "util/strtonum.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" + +#define MAX_RETRY_ATTEMPTS 1 + +/* ==Connect-to-LDAP-Server=============================================== */ + +struct sdap_rebind_proc_params { + struct sdap_options *opts; + struct sdap_handle *sh; + bool use_start_tls; +}; + +static int sdap_rebind_proc(LDAP *ldap, LDAP_CONST char *url, ber_tag_t request, + ber_int_t msgid, void *params); + +struct sdap_connect_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + const char *uri; + bool use_start_tls; + + struct sdap_op *op; + + struct sdap_msg *reply; + int result; +}; + +static void sdap_sys_connect_done(struct tevent_req *subreq); +static void sdap_connect_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +struct tevent_req *sdap_connect_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + const char *uri, + struct sockaddr *sockaddr, + socklen_t sockaddr_len, + bool use_start_tls) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_connect_state *state; + int ret; + int timeout; + + req = tevent_req_create(memctx, &state, struct sdap_connect_state); + if (!req) return NULL; + + if (uri == NULL || sockaddr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid uri or sockaddr\n"); + ret = EINVAL; + goto fail; + } + + state->reply = talloc(state, struct sdap_msg); + if (!state->reply) { + talloc_zfree(req); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->use_start_tls = use_start_tls; + + state->uri = talloc_asprintf(state, "%s", uri); + if (!state->uri) { + talloc_zfree(req); + return NULL; + } + + state->sh = sdap_handle_create(state); + if (!state->sh) { + talloc_zfree(req); + return NULL; + } + + state->sh->page_size = dp_opt_get_int(state->opts->basic, + SDAP_PAGE_SIZE); + + timeout = dp_opt_get_int(state->opts->basic, SDAP_NETWORK_TIMEOUT); + + subreq = sss_ldap_init_send(state, ev, state->uri, sockaddr, + sockaddr_len, timeout); + if (subreq == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "sss_ldap_init_send failed.\n"); + goto fail; + } + + tevent_req_set_callback(subreq, sdap_sys_connect_done, req); + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sdap_sys_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_connect_state *state = tevent_req_data(req, + struct sdap_connect_state); + struct timeval tv; + int ver; + int lret = 0; + int optret; + int ret = EOK; + int msgid; + char *errmsg = NULL; + bool ldap_referrals; + const char *ldap_deref; + int ldap_deref_val; + struct sdap_rebind_proc_params *rebind_proc_params; + int sd; + bool sasl_nocanon; + const char *sasl_mech; + int sasl_minssf; + ber_len_t ber_sasl_minssf; + int sasl_maxssf; + ber_len_t ber_sasl_maxssf; + char *stat_info; + + ret = sss_ldap_init_recv(subreq, &state->sh->ldap, &sd); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_async_connect_call request failed: [%d]: %s.\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = setup_ldap_connection_callbacks(state->sh, state->ev); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "setup_ldap_connection_callbacks failed: [%d]: %s.\n", + ret, sss_strerror(ret)); + goto fail; + } + + /* If sss_ldap_init_recv() does not return a valid file descriptor we have + * to assume that the connection callback will be called by internally by + * the OpenLDAP client library. */ + if (sd != -1) { + ret = sdap_call_conn_cb(state->uri, sd, state->sh); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_call_conn_cb failed.\n"); + goto fail; + } + } + + /* Force ldap version to 3 */ + ver = LDAP_VERSION3; + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_PROTOCOL_VERSION, &ver); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set ldap version to 3\n"); + goto fail; + } + + /* TODO: maybe this can be remove when we go async, currently we need it + * to handle EINTR during poll(). */ + ret = ldap_set_option(state->sh->ldap, LDAP_OPT_RESTART, LDAP_OPT_ON); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set restart option.\n"); + } + + /* Set Network Timeout */ + tv.tv_sec = dp_opt_get_int(state->opts->basic, SDAP_NETWORK_TIMEOUT); + tv.tv_usec = 0; + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_NETWORK_TIMEOUT, &tv); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set network timeout to %d\n", + dp_opt_get_int(state->opts->basic, SDAP_NETWORK_TIMEOUT)); + goto fail; + } + + /* Set Default Timeout */ + tv.tv_sec = dp_opt_get_int(state->opts->basic, SDAP_OPT_TIMEOUT); + tv.tv_usec = 0; + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_TIMEOUT, &tv); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set default timeout to %d\n", + dp_opt_get_int(state->opts->basic, SDAP_OPT_TIMEOUT)); + goto fail; + } + + /* Set Referral chasing */ + ldap_referrals = dp_opt_get_bool(state->opts->basic, SDAP_REFERRALS); + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_REFERRALS, + (ldap_referrals ? LDAP_OPT_ON : LDAP_OPT_OFF)); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set referral chasing to %s\n", + (ldap_referrals ? "LDAP_OPT_ON" : "LDAP_OPT_OFF")); + goto fail; + } + + if (ldap_referrals) { + rebind_proc_params = talloc_zero(state->sh, + struct sdap_rebind_proc_params); + if (rebind_proc_params == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto fail; + } + + rebind_proc_params->opts = state->opts; + rebind_proc_params->sh = state->sh; + rebind_proc_params->use_start_tls = state->use_start_tls; + + lret = ldap_set_rebind_proc(state->sh->ldap, sdap_rebind_proc, + rebind_proc_params); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_set_rebind_proc failed.\n"); + goto fail; + } + } + + /* Set alias dereferencing */ + ldap_deref = dp_opt_get_string(state->opts->basic, SDAP_DEREF); + if (ldap_deref != NULL) { + ret = deref_string_to_val(ldap_deref, &ldap_deref_val); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "deref_string_to_val failed.\n"); + goto fail; + } + + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_DEREF, &ldap_deref_val); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set deref option to %d\n", ldap_deref_val); + goto fail; + } + + } + + /* Set host name canonicalization for LDAP SASL bind */ + sasl_nocanon = !dp_opt_get_bool(state->opts->basic, SDAP_SASL_CANONICALIZE); + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_X_SASL_NOCANON, + sasl_nocanon ? LDAP_OPT_ON : LDAP_OPT_OFF); + if (lret != LDAP_OPT_SUCCESS) { + /* Do not fail, just warn into both debug logs and syslog */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to set LDAP SASL nocanon option to %s. If your system " + "is configured to use SASL, LDAP operations might fail.\n", + sasl_nocanon ? "true" : "false"); + sss_log(SSS_LOG_INFO, + "Failed to set LDAP SASL nocanon option to %s. If your system " + "is configured to use SASL, LDAP operations might fail.\n", + sasl_nocanon ? "true" : "false"); + } + + sasl_mech = dp_opt_get_string(state->opts->basic, SDAP_SASL_MECH); + if (sasl_mech != NULL) { + sasl_minssf = dp_opt_get_int(state->opts->basic, SDAP_SASL_MINSSF); + if (sasl_minssf >= 0) { + ber_sasl_minssf = (ber_len_t)sasl_minssf; + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_X_SASL_SSF_MIN, + &ber_sasl_minssf); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set LDAP MIN SSF option " + "to %d\n", sasl_minssf); + goto fail; + } + } + + sasl_maxssf = dp_opt_get_int(state->opts->basic, SDAP_SASL_MAXSSF); + if (sasl_maxssf >= 0) { + ber_sasl_maxssf = (ber_len_t)sasl_maxssf; + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_X_SASL_SSF_MAX, + &ber_sasl_maxssf); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set LDAP MAX SSF option " + "to %d\n", sasl_maxssf); + goto fail; + } + } + } + + /* if we do not use start_tls the connection is not really connected yet + * just fake an async procedure and leave connection to the bind call */ + if (!state->use_start_tls) { + tevent_req_done(req); + return; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Executing START TLS\n"); + + lret = ldap_start_tls(state->sh->ldap, NULL, NULL, &msgid); + if (lret != LDAP_SUCCESS) { + optret = sss_ldap_get_diagnostic_msg(state, state->sh->ldap, + &errmsg); + if (optret == LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_start_tls failed: [%s] [%s]\n", + sss_ldap_err2string(lret), + errmsg); + sss_log(SSS_LOG_ERR, "Could not start TLS. %s", errmsg); + } + else { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_start_tls failed: [%s]\n", + sss_ldap_err2string(lret)); + sss_log(SSS_LOG_ERR, "Could not start TLS. " + "Check for certificate issues."); + } + goto fail; + } + + stat_info = talloc_asprintf(state, "server: [%s] START TLS", + sdap_get_server_peer_str_safe(state->sh)); + if (stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); + } + + ret = sdap_set_connected(state->sh, state->ev); + if (ret) goto fail; + + ret = sdap_op_add(state, state->ev, state->sh, msgid, stat_info, + sdap_connect_done, req, + dp_opt_get_int(state->opts->basic, SDAP_OPT_TIMEOUT), + &state->op); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); + goto fail; + } + + return; + +fail: + if (ret) { + tevent_req_error(req, ret); + } else { + if (lret == LDAP_SERVER_DOWN) { + tevent_req_error(req, ETIMEDOUT); + } else { + tevent_req_error(req, EIO); + } + } + return; +} + +static void sdap_connect_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct sdap_connect_state *state = tevent_req_data(req, + struct sdap_connect_state); + char *errmsg = NULL; + char *tlserr; + int ret; + int optret; + + if (error) { + tevent_req_error(req, error); + return; + } + + state->reply = talloc_steal(state, reply); + + ret = ldap_parse_result(state->sh->ldap, state->reply->msg, + &state->result, NULL, &errmsg, NULL, NULL, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_result failed (%d)\n", sdap_op_get_msgid(state->op)); + tevent_req_error(req, EIO); + return; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "START TLS result: %s(%d), %s\n", + sss_ldap_err2string(state->result), state->result, errmsg); + ldap_memfree(errmsg); + + if (ldap_tls_inplace(state->sh->ldap)) { + DEBUG(SSSDBG_TRACE_ALL, "SSL/TLS handler already in place.\n"); + tevent_req_done(req); + return; + } + +/* FIXME: take care that ldap_install_tls might block */ + ret = ldap_install_tls(state->sh->ldap); + if (ret != LDAP_SUCCESS) { + + optret = sss_ldap_get_diagnostic_msg(state, state->sh->ldap, + &tlserr); + if (optret == LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_install_tls failed: [%s] [%s]\n", + sss_ldap_err2string(ret), + tlserr); + sss_log(SSS_LOG_ERR, "Could not start TLS encryption. %s", tlserr); + } + else { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_install_tls failed: [%s]\n", + sss_ldap_err2string(ret)); + sss_log(SSS_LOG_ERR, "Could not start TLS encryption. " + "Check for certificate issues."); + } + + state->result = ret; + tevent_req_error(req, EIO); + return; + } + + tevent_req_done(req); +} + +int sdap_connect_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_handle **sh) +{ + struct sdap_connect_state *state = tevent_req_data(req, + struct sdap_connect_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *sh = talloc_steal(memctx, state->sh); + if (!*sh) { + return ENOMEM; + } + return EOK; +} + +struct sdap_connect_host_state { + struct tevent_context *ev; + struct sdap_options *opts; + char *uri; + char *protocol; + char *host; + int port; + bool use_start_tls; + + struct sdap_handle *sh; +}; + +static void sdap_connect_host_resolv_done(struct tevent_req *subreq); +static void sdap_connect_host_done(struct tevent_req *subreq); + +struct tevent_req *sdap_connect_host_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct resolv_ctx *resolv_ctx, + enum restrict_family family_order, + enum host_database *host_db, + const char *protocol, + const char *host, + int port, + bool use_start_tls) +{ + struct sdap_connect_host_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_connect_host_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->port = port; + state->use_start_tls = use_start_tls; + + state->protocol = talloc_strdup(state, protocol); + if (state->protocol == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->host = talloc_strdup(state, host); + if (state->host == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->uri = talloc_asprintf(state, "%s://%s:%d", protocol, host, port); + if (state->uri == NULL) { + ret = ENOMEM; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resolving host %s\n", host); + + subreq = resolv_gethostbyname_send(state, state->ev, resolv_ctx, + host, family_order, host_db); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_connect_host_resolv_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void sdap_connect_host_resolv_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_connect_host_state *state = NULL; + struct resolv_hostent *hostent = NULL; + struct sockaddr *sockaddr = NULL; + socklen_t sockaddr_len; + int status; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_connect_host_state); + + ret = resolv_gethostbyname_recv(subreq, state, &status, NULL, &hostent); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to resolve host %s: %s\n", + state->host, resolv_strerror(status)); + goto done; + } + + sockaddr = resolv_get_sockaddr_address(state, hostent, state->port, + &sockaddr_len); + if (sockaddr == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "resolv_get_sockaddr_address() failed\n"); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Connecting to %s\n", state->uri); + + subreq = sdap_connect_send(state, state->ev, state->opts, + state->uri, sockaddr, sockaddr_len, + state->use_start_tls); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_connect_host_done, req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static void sdap_connect_host_done(struct tevent_req *subreq) +{ + struct sdap_connect_host_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_connect_host_state); + + ret = sdap_connect_recv(subreq, state, &state->sh); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + /* if TLS was used, the sdap handle is already marked as connected */ + if (!state->use_start_tls) { + /* we need to mark handle as connected to allow anonymous bind */ + ret = sdap_set_connected(state->sh, state->ev); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_set_connected() failed\n"); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successful connection to %s\n", state->uri); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sdap_connect_host_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sdap_handle **_sh) +{ + struct sdap_connect_host_state *state = NULL; + state = tevent_req_data(req, struct sdap_connect_host_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_sh = talloc_steal(mem_ctx, state->sh); + + return EOK; +} + + +/* ==Simple-Bind========================================================== */ + +struct simple_bind_state { + struct tevent_context *ev; + struct sdap_handle *sh; + const char *user_dn; + + struct sdap_op *op; + + struct sdap_msg *reply; + struct sdap_ppolicy_data *ppolicy; +}; + +static void simple_bind_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +static struct tevent_req *simple_bind_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + int timeout, + const char *user_dn, + struct berval *pw) +{ + struct tevent_req *req; + struct simple_bind_state *state; + int ret = EOK; + int msgid; + int ldap_err; + LDAPControl **request_controls = NULL; + LDAPControl *ctrls[2] = { NULL, NULL }; + char *stat_info; + + req = tevent_req_create(memctx, &state, struct simple_bind_state); + if (!req) return NULL; + + state->reply = talloc(state, struct sdap_msg); + if (!state->reply) { + talloc_zfree(req); + return NULL; + } + + state->ev = ev; + state->sh = sh; + state->user_dn = user_dn; + + ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, &ctrls[0]); + if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_ldap_control_create failed to create " + "Password Policy control.\n"); + goto fail; + } + request_controls = ctrls; + + DEBUG(SSSDBG_CONF_SETTINGS, + "Executing simple bind as: %s\n", state->user_dn); + + ret = ldap_sasl_bind(state->sh->ldap, state->user_dn, LDAP_SASL_SIMPLE, + pw, request_controls, NULL, &msgid); + if (ctrls[0]) ldap_control_free(ctrls[0]); + if (ret == -1 || msgid == -1) { + ret = ldap_get_option(state->sh->ldap, + LDAP_OPT_RESULT_CODE, &ldap_err); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_sasl_bind failed (couldn't get ldap error)\n"); + ret = LDAP_LOCAL_ERROR; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_sasl_bind failed (%d)[%s]\n", + ldap_err, sss_ldap_err2string(ldap_err)); + ret = ldap_err; + } + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "ldap simple bind sent, msgid = %d\n", msgid); + + if (!sh->connected) { + ret = sdap_set_connected(sh, ev); + if (ret) goto fail; + } + + stat_info = talloc_asprintf(state, "server: [%s] simple bind: [%s]", + sdap_get_server_peer_str_safe(state->sh), + state->user_dn); + if (stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); + } + + ret = sdap_op_add(state, ev, sh, msgid, stat_info, + simple_bind_done, req, timeout, &state->op); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); + goto fail; + } + + return req; + +fail: + if (ret == LDAP_SERVER_DOWN) { + tevent_req_error(req, ETIMEDOUT); + } else { + tevent_req_error(req, ERR_NETWORK_IO); + } + tevent_req_post(req, ev); + return req; +} + +static void simple_bind_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct simple_bind_state *state = tevent_req_data(req, + struct simple_bind_state); + char *errmsg = NULL; + char *nval; + errno_t ret = ERR_INTERNAL; + int lret; + LDAPControl **response_controls; + int c; + ber_int_t pp_grace; + ber_int_t pp_expire; + LDAPPasswordPolicyError pp_error; + int result = LDAP_OTHER; + bool on_grace_login_limit = false; + + if (error) { + tevent_req_error(req, error); + return; + } + + state->reply = talloc_steal(state, reply); + + lret = ldap_parse_result(state->sh->ldap, state->reply->msg, + &result, NULL, &errmsg, NULL, + &response_controls, 0); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldap_parse_result failed (%d)\n", sdap_op_get_msgid(state->op)); + ret = ERR_INTERNAL; + goto done; + } + + if (result == LDAP_SUCCESS) { + ret = EOK; + } else if (result == LDAP_INVALID_CREDENTIALS + && errmsg != NULL && strstr(errmsg, "data 775,") != NULL) { + /* Value 775 is described in + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms681386%28v=vs.85%29.aspx + * for more details please see commit message. */ + ret = ERR_ACCOUNT_LOCKED; + } else { + ret = ERR_AUTH_FAILED; + } + + if (response_controls == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "Server returned no controls.\n"); + state->ppolicy = NULL; + } else { + for (c = 0; response_controls[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Server returned control [%s].\n", + response_controls[c]->ldctl_oid); + + if (strcmp(response_controls[c]->ldctl_oid, + LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) { + lret = ldap_parse_passwordpolicy_control(state->sh->ldap, + response_controls[c], + &pp_expire, &pp_grace, + &pp_error); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldap_parse_passwordpolicy_control failed.\n"); + ret = ERR_INTERNAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Password Policy Response: expire [%d] grace [%d] " + "error [%s].\n", pp_expire, pp_grace, + ldap_passwordpolicy_err2txt(pp_error)); + if (!state->ppolicy) + state->ppolicy = talloc_zero(state, + struct sdap_ppolicy_data); + if (state->ppolicy == NULL) { + ret = ENOMEM; + goto done; + } + state->ppolicy->grace = pp_grace; + state->ppolicy->expire = pp_expire; + if (result == LDAP_SUCCESS) { + /* We have to set the on_grace_login_limit as when going + * through the response controls 389-ds may return both + * an warning and an error (and the order is not ensured) + * for the GraceLimit: + * - [1.3.6.1.4.1.42.2.27.8.5.1] for the GraceLimit itself + * - [2.16.840.1.113730.3.4.4] for the PasswordExpired + * + * So, in order to avoid bulldozing the GraceLimit, let's + * set it to true when pp_grace >= 0 and, in the end of + * this function, just return EOK when LDAP returns the + * PasswordExpired error but the GraceLimit is still valid. + */ + on_grace_login_limit = false; + if (pp_error == PP_changeAfterReset) { + DEBUG(SSSDBG_TRACE_LIBS, + "Password was reset. " + "User must set a new password.\n"); + ret = ERR_PASSWORD_EXPIRED; + } else if (pp_grace >= 0) { + on_grace_login_limit = true; + DEBUG(SSSDBG_TRACE_LIBS, + "Password expired. " + "[%d] grace logins remaining.\n", + pp_grace); + } else if (pp_expire > 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Password will expire in [%d] seconds.\n", + pp_expire); + } + } else if (result == LDAP_INVALID_CREDENTIALS && + pp_error == PP_passwordExpired) { + /* According to + * https://www.ietf.org/archive/id/draft-behera-ldap-password-policy-11.txt + * section 8.1.2.3.2. this condition means "No Remaining + * Grace Authentications". */ + DEBUG(SSSDBG_TRACE_LIBS, + "Password expired, grace logins exhausted.\n"); + ret = ERR_AUTH_FAILED; + } + } else if (strcmp(response_controls[c]->ldctl_oid, + LDAP_CONTROL_PWEXPIRED) == 0) { + /* I haven't found a proper documentation of this control only + * the Red Hat Directory Server documentation has a short + * description in the section "Understanding Password + * Expiration Controls", e.g. + * https://access.redhat.com/documentation/en-us/red_hat_directory_server/11/html/administration_guide/understanding_password_expiration_controls + */ + if (result == LDAP_INVALID_CREDENTIALS) { + DEBUG(SSSDBG_TRACE_LIBS, + "Password expired, grace logins exhausted.\n"); + ret = ERR_AUTH_FAILED; + } else { + DEBUG(SSSDBG_TRACE_LIBS, + "Password expired, user must set a new password.\n"); + ret = ERR_PASSWORD_EXPIRED; + } + } else if (strcmp(response_controls[c]->ldctl_oid, + LDAP_CONTROL_PWEXPIRING) == 0) { + /* ignore controls with suspiciously long values */ + if (response_controls[c]->ldctl_value.bv_len > 32) { + continue; + } + + if (!state->ppolicy) { + state->ppolicy = talloc(state, struct sdap_ppolicy_data); + } + + if (state->ppolicy == NULL) { + ret = ENOMEM; + goto done; + } + /* ensure that bv_val is a null-terminated string */ + nval = talloc_strndup(NULL, + response_controls[c]->ldctl_value.bv_val, + response_controls[c]->ldctl_value.bv_len); + if (nval == NULL) { + ret = ENOMEM; + goto done; + } + state->ppolicy->expire = strtouint32(nval, NULL, 10); + lret = errno; + talloc_zfree(nval); + if (lret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Couldn't convert control response " + "to an integer [%s].\n", strerror(lret)); + ret = ERR_INTERNAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Password will expire in [%d] seconds.\n", + state->ppolicy->expire); + } + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Bind result: %s(%d), %s\n", + sss_ldap_err2string(result), result, + errmsg ? errmsg : "no errmsg set"); + + if (result != LDAP_SUCCESS && ret == EOK) { + ret = ERR_AUTH_FAILED; + } + + if (ret == ERR_PASSWORD_EXPIRED && on_grace_login_limit) { + ret = EOK; + } + +done: + ldap_controls_free(response_controls); + ldap_memfree(errmsg); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t simple_bind_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_ppolicy_data **ppolicy) +{ + struct simple_bind_state *state = tevent_req_data(req, + struct simple_bind_state); + + if (ppolicy != NULL) { + *ppolicy = talloc_steal(memctx, state->ppolicy); + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==SASL-Bind============================================================ */ + +struct sasl_bind_state { + struct tevent_context *ev; + struct sdap_handle *sh; + + const char *sasl_mech; + const char *sasl_user; + struct berval *sasl_cred; +}; + +static int sdap_sasl_interact(LDAP *ld, unsigned flags, + void *defaults, void *interact); + +static struct tevent_req *sasl_bind_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + const char *sasl_mech, + const char *sasl_user, + struct berval *sasl_cred) +{ + struct tevent_req *req; + struct sasl_bind_state *state; + int ret = EOK; + int optret; + char *diag_msg = NULL; + + req = tevent_req_create(memctx, &state, struct sasl_bind_state); + if (!req) return NULL; + + state->ev = ev; + state->sh = sh; + state->sasl_mech = sasl_mech; + state->sasl_user = sasl_user; + state->sasl_cred = sasl_cred; + + DEBUG(SSSDBG_CONF_SETTINGS, "Executing sasl bind mech: %s, user: %s\n", + sasl_mech, sasl_user); + + /* FIXME: Warning, this is a sync call! + * No async variant exist in openldap libraries yet */ + + if (state->sh == NULL || state->sh->ldap == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Trying LDAP search while not connected.\n"); + ret = ERR_NETWORK_IO; + goto fail; + } + + ret = ldap_sasl_interactive_bind_s(state->sh->ldap, NULL, + sasl_mech, NULL, NULL, + LDAP_SASL_QUIET, + (*sdap_sasl_interact), state); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_sasl_interactive_bind_s failed (%d)[%s]\n", + ret, sss_ldap_err2string(ret)); + + optret = sss_ldap_get_diagnostic_msg(state, state->sh->ldap, + &diag_msg); + if (optret == EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Extended failure message: [%s]\n", diag_msg); + } + talloc_zfree(diag_msg); + + goto fail; + } + + if (!sh->connected) { + ret = sdap_set_connected(sh, ev); + if (ret) goto fail; + } + + /* This is a hack, relies on the fact that tevent_req_done() will always + * set the state but will not complain if no callback has been set. + * tevent_req_post() will only set the immediate event and then just call + * the async callback set by the caller right after we return using the + * state value set previously by tevent_req_done() */ + tevent_req_done(req); + tevent_req_post(req, ev); + return req; + +fail: + if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT) { + tevent_req_error(req, ETIMEDOUT); + } else { + tevent_req_error(req, ERR_AUTH_FAILED); + } + tevent_req_post(req, ev); + return req; +} + +static int sdap_sasl_interact(LDAP *ld, unsigned flags, + void *defaults, void *interact) +{ + struct sasl_bind_state *state = talloc_get_type(defaults, + struct sasl_bind_state); + sasl_interact_t *in = (sasl_interact_t *)interact; + + if (!ld) return LDAP_PARAM_ERROR; + + while (in->id != SASL_CB_LIST_END) { + + switch (in->id) { + case SASL_CB_GETREALM: + case SASL_CB_USER: + case SASL_CB_PASS: + if (in->defresult) { + in->result = in->defresult; + } else { + in->result = ""; + } + in->len = strlen(in->result); + break; + case SASL_CB_AUTHNAME: + if (state->sasl_user) { + in->result = state->sasl_user; + } else if (in->defresult) { + in->result = in->defresult; + } else { + in->result = ""; + } + in->len = strlen(in->result); + break; + case SASL_CB_NOECHOPROMPT: + case SASL_CB_ECHOPROMPT: + goto fail; + } + + in++; + } + + return LDAP_SUCCESS; + +fail: + return LDAP_UNAVAILABLE; +} + +static errno_t sasl_bind_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Perform-Kinit-given-keytab-and-principal============================= */ + +struct sdap_kinit_state { + const char *keytab; + const char *principal; + const char *realm; + int timeout; + int lifetime; + + const char *krb_service_name; + struct tevent_context *ev; + struct be_ctx *be; + + struct fo_server *kdc_srv; + time_t expire_time; +}; + +static void sdap_kinit_done(struct tevent_req *subreq); +static struct tevent_req *sdap_kinit_next_kdc(struct tevent_req *req); +static void sdap_kinit_kdc_resolved(struct tevent_req *subreq); + +static +struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct be_ctx *be, + struct sdap_handle *sh, + const char *krb_service_name, + int timeout, + const char *keytab, + const char *principal, + const char *realm, + bool canonicalize, + int lifetime) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_kinit_state *state; + int ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Attempting kinit (%s, %s, %s, %d)\n", + keytab ? keytab : "default", + principal, realm, lifetime); + + if (lifetime < 0 || lifetime > INT32_MAX) { + DEBUG(SSSDBG_CRIT_FAILURE, "Ticket lifetime out of range.\n"); + return NULL; + } + + req = tevent_req_create(memctx, &state, struct sdap_kinit_state); + if (!req) return NULL; + + state->keytab = keytab; + state->principal = principal; + state->realm = realm; + state->ev = ev; + state->be = be; + state->timeout = timeout; + state->lifetime = lifetime; + state->krb_service_name = krb_service_name; + + if (canonicalize) { + ret = setenv("KRB5_CANONICALIZE", "true", 1); + } else { + ret = setenv("KRB5_CANONICALIZE", "false", 1); + } + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to set KRB5_CANONICALIZE to %s\n", + ((canonicalize)?"true":"false")); + talloc_free(req); + return NULL; + } + + subreq = sdap_kinit_next_kdc(req); + if (!subreq) { + talloc_free(req); + return NULL; + } + + return req; +} + +static struct tevent_req *sdap_kinit_next_kdc(struct tevent_req *req) +{ + struct tevent_req *next_req; + struct sdap_kinit_state *state = tevent_req_data(req, + struct sdap_kinit_state); + + DEBUG(SSSDBG_TRACE_LIBS, + "Resolving next KDC for service %s\n", state->krb_service_name); + + next_req = be_resolve_server_send(state, state->ev, + state->be, + state->krb_service_name, + state->kdc_srv == NULL ? true : false); + if (next_req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_resolve_server_send failed.\n"); + return NULL; + } + tevent_req_set_callback(next_req, sdap_kinit_kdc_resolved, req); + + return next_req; +} + +static void sdap_kinit_kdc_resolved(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_kinit_state *state = tevent_req_data(req, + struct sdap_kinit_state); + struct tevent_req *tgtreq; + int ret; + + ret = be_resolve_server_recv(subreq, state, &state->kdc_srv); + talloc_zfree(subreq); + if (ret != EOK) { + /* all servers have been tried and none + * was found good, go offline */ + tevent_req_error(req, ERR_NETWORK_IO); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, "KDC resolved, attempting to get TGT...\n"); + + tgtreq = sdap_get_tgt_send(state, state->ev, state->realm, + state->principal, state->keytab, + state->lifetime, state->timeout); + if (!tgtreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(tgtreq, sdap_kinit_done, req); +} + +static void sdap_kinit_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_kinit_state *state = tevent_req_data(req, + struct sdap_kinit_state); + + int ret; + int result; + char *ccname = NULL; + time_t expire_time = 0; + krb5_error_code kerr; + struct tevent_req *nextreq; + + ret = sdap_get_tgt_recv(subreq, state, &result, + &kerr, &ccname, &expire_time); + talloc_zfree(subreq); + if (ret == ETIMEDOUT) { + /* The child didn't even respond. Perhaps the KDC is too busy, + * retry with another KDC */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Communication with KDC timed out, trying the next one\n"); + be_fo_set_port_status(state->be, state->krb_service_name, + state->kdc_srv, PORT_NOT_WORKING); + nextreq = sdap_kinit_next_kdc(req); + if (!nextreq) { + tevent_req_error(req, ENOMEM); + } + return; + } else if (ret != EOK) { + /* A severe error while executing the child. Abort the operation. */ + DEBUG(SSSDBG_CRIT_FAILURE, + "child failed (%d [%s])\n", ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (result == EOK) { + ret = setenv("KRB5CCNAME", ccname, 1); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to set env. variable KRB5CCNAME!\n"); + tevent_req_error(req, ERR_AUTH_FAILED); + return; + } + + state->expire_time = expire_time; + tevent_req_done(req); + return; + } else { + if (kerr == KRB5_KDC_UNREACH) { + be_fo_set_port_status(state->be, state->krb_service_name, + state->kdc_srv, PORT_NOT_WORKING); + nextreq = sdap_kinit_next_kdc(req); + if (!nextreq) { + tevent_req_error(req, ENOMEM); + } + return; + } + + } + if (result == EFAULT || result == EIO || result == EPERM) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Could not get TGT from server %s: %d [%s]\n", + state->kdc_srv ? fo_get_server_name(state->kdc_srv) : "NULL", + result, sss_strerror(result)); + } else { + DEBUG(SSSDBG_CONF_SETTINGS, + "Could not get TGT: %d [%s]\n", result, sss_strerror(result)); + } + tevent_req_error(req, ERR_AUTH_FAILED); +} + +static errno_t sdap_kinit_recv(struct tevent_req *req, + time_t *expire_time) +{ + struct sdap_kinit_state *state = tevent_req_data(req, + struct sdap_kinit_state); + enum tevent_req_state tstate; + uint64_t err_uint64 = ERR_INTERNAL; + errno_t err; + + if (tevent_req_is_error(req, &tstate, &err_uint64)) { + if (tstate != TEVENT_REQ_IN_PROGRESS) { + err = (errno_t)err_uint64; + if (err == EOK) { + return ERR_INTERNAL; + } + return err; + } + } + + *expire_time = state->expire_time; + return EOK; +} + + +/* ==Authenticaticate-User-by-DN========================================== */ + +struct sdap_auth_state { + struct sdap_ppolicy_data *ppolicy; + bool is_sasl; +}; + +static void sdap_auth_done(struct tevent_req *subreq); + +/* TODO: handle sasl_cred */ +struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + const char *sasl_mech, + const char *sasl_user, + const char *user_dn, + struct sss_auth_token *authtok, + int simple_bind_timeout) +{ + struct tevent_req *req, *subreq; + struct sdap_auth_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_auth_state); + if (!req) return NULL; + + if (sasl_mech) { + state->is_sasl = true; + subreq = sasl_bind_send(state, ev, sh, sasl_mech, sasl_user, NULL); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return tevent_req_post(req, ev); + } + } else { + const char *password = NULL; + struct berval pw; + size_t pwlen; + errno_t ret; + + /* this code doesn't make copies of password + * but only uses pointer to authtok internals + */ + ret = sss_authtok_get_password(authtok, &password, &pwlen); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot parse authtok.\n"); + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + /* Treat a zero-length password as a failure */ + if (*password == '\0') { + tevent_req_error(req, ENOENT); + return tevent_req_post(req, ev); + } + pw.bv_val = discard_const(password); + pw.bv_len = pwlen; + + state->is_sasl = false; + subreq = simple_bind_send(state, ev, sh, simple_bind_timeout, user_dn, &pw); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return tevent_req_post(req, ev); + } + } + + tevent_req_set_callback(subreq, sdap_auth_done, req); + return req; +} + +static int sdap_auth_get_authtok(const char *authtok_type, + struct dp_opt_blob authtok, + struct berval *pw) +{ + if (!authtok_type) return EOK; + if (!pw) return EINVAL; + + if (strcasecmp(authtok_type,"password") == 0) { + pw->bv_len = authtok.length; + pw->bv_val = (char *) authtok.data; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Authentication token type [%s] is not supported\n", + authtok_type); + return EINVAL; + } + + return EOK; +} + +static void sdap_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_auth_state *state = tevent_req_data(req, + struct sdap_auth_state); + int ret; + + if (state->is_sasl) { + ret = sasl_bind_recv(subreq); + state->ppolicy = NULL; + } else { + ret = simple_bind_recv(subreq, state, &state->ppolicy); + } + + if (tevent_req_error(req, ret)) { + return; + } + + tevent_req_done(req); +} + +errno_t sdap_auth_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_ppolicy_data **ppolicy) +{ + struct sdap_auth_state *state = tevent_req_data(req, + struct sdap_auth_state); + + if (ppolicy != NULL) { + *ppolicy = talloc_steal(memctx, state->ppolicy); + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Client connect============================================ */ + +struct sdap_cli_connect_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_service *service; + struct be_ctx *be; + + bool use_rootdse; + struct sysdb_attrs *rootdse; + + struct sdap_handle *sh; + + struct fo_server *srv; + + struct sdap_server_opts *srv_opts; + + enum connect_tls force_tls; + bool do_auth; + bool use_tls; + + int retry_attempts; +}; + +static int sdap_cli_resolve_next(struct tevent_req *req); +static void sdap_cli_resolve_done(struct tevent_req *subreq); +static void sdap_cli_connect_done(struct tevent_req *subreq); +static void sdap_cli_rootdse_step(struct tevent_req *req); +static void sdap_cli_rootdse_done(struct tevent_req *subreq); +static errno_t sdap_cli_use_rootdse(struct sdap_cli_connect_state *state); +static void sdap_cli_kinit_step(struct tevent_req *req); +static void sdap_cli_kinit_done(struct tevent_req *subreq); +static void sdap_cli_auth_step(struct tevent_req *req); +static void sdap_cli_auth_done(struct tevent_req *subreq); +static errno_t sdap_cli_auth_reconnect(struct tevent_req *subreq); +static void sdap_cli_auth_reconnect_done(struct tevent_req *subreq); +static void sdap_cli_rootdse_auth_done(struct tevent_req *subreq); + +static errno_t +decide_tls_usage(enum connect_tls force_tls, struct dp_option *basic, + const char *uri, bool *_use_tls) +{ + bool use_tls = true; + + switch (force_tls) { + case CON_TLS_DFL: + use_tls = dp_opt_get_bool(basic, SDAP_ID_TLS); + break; + case CON_TLS_ON: + use_tls = true; + break; + case CON_TLS_OFF: + use_tls = false; + break; + default: + return EINVAL; + break; + } + + if (use_tls && sdap_is_secure_uri(uri)) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "[%s] is a secure channel. No need to run START_TLS\n", uri); + use_tls = false; + } + + *_use_tls = use_tls; + return EOK; +} + +struct tevent_req *sdap_cli_connect_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct be_ctx *be, + struct sdap_service *service, + bool skip_rootdse, + enum connect_tls force_tls, + bool skip_auth) +{ + struct sdap_cli_connect_state *state; + struct tevent_req *req; + int ret; + + req = tevent_req_create(memctx, &state, struct sdap_cli_connect_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->service = service; + state->be = be; + state->srv = NULL; + state->srv_opts = NULL; + state->use_rootdse = !skip_rootdse; + state->force_tls = force_tls; + state->do_auth = !skip_auth; + + ret = sdap_cli_resolve_next(req); + if (ret) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static int sdap_cli_resolve_next(struct tevent_req *req) +{ + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + struct tevent_req *subreq; + + /* Before stepping to next server destroy any connection from previous attempt */ + talloc_zfree(state->sh); + + /* NOTE: this call may cause service->uri to be refreshed + * with a new valid server. Do not use service->uri before */ + subreq = be_resolve_server_send(state, state->ev, + state->be, state->service->name, + state->srv == NULL ? true : false); + if (!subreq) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_cli_resolve_done, req); + return EOK; +} + +static void sdap_cli_resolve_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + int ret; + + ret = be_resolve_server_recv(subreq, state, &state->srv); + talloc_zfree(subreq); + if (ret) { + state->srv = NULL; + /* all servers have been tried and none + * was found good, go offline */ + tevent_req_error(req, EIO); + return; + } + + ret = decide_tls_usage(state->force_tls, state->opts->basic, + state->service->uri, &state->use_tls); + + if (ret != EOK) { + tevent_req_error(req, EINVAL); + return; + } + + subreq = sdap_connect_send(state, state->ev, state->opts, + state->service->uri, + state->service->sockaddr, + state->service->sockaddr_len, + state->use_tls); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_cli_connect_done, req); +} + +static void sdap_cli_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + const char *sasl_mech; + int ret; + + talloc_zfree(state->sh); + ret = sdap_connect_recv(subreq, state, &state->sh); + talloc_zfree(subreq); + if (ret == ERR_TLS_HANDSHAKE_INTERRUPTED && + state->retry_attempts < MAX_RETRY_ATTEMPTS) { + DEBUG(SSSDBG_OP_FAILURE, + "TLS handshake was interruped, provider will retry\n"); + state->retry_attempts++; + subreq = sdap_connect_send(state, state->ev, state->opts, + state->service->uri, + state->service->sockaddr, + state->service->sockaddr_len, + state->use_tls); + + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_cli_connect_done, req); + return; + } else if (ret != EOK) { + state->retry_attempts = 0; + /* retry another server */ + be_fo_set_port_status(state->be, state->service->name, + state->srv, PORT_NOT_WORKING); + + ret = sdap_cli_resolve_next(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + + return; + } + state->retry_attempts = 0; + + if (state->use_rootdse) { + /* fetch the rootDSE this time */ + sdap_cli_rootdse_step(req); + return; + } + + sasl_mech = dp_opt_get_string(state->opts->basic, SDAP_SASL_MECH); + + if (state->do_auth && sasl_mech && state->use_rootdse) { + /* check if server claims to support the configured SASL MECH */ + if (!sdap_is_sasl_mech_supported(state->sh, sasl_mech)) { + tevent_req_error(req, ENOTSUP); + return; + } + } + + if (state->do_auth && sasl_mech && sdap_sasl_mech_needs_kinit(sasl_mech)) { + if (dp_opt_get_bool(state->opts->basic, SDAP_KRB5_KINIT)) { + sdap_cli_kinit_step(req); + return; + } + } + + sdap_cli_auth_step(req); +} + +static void sdap_cli_rootdse_step(struct tevent_req *req) +{ + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + struct tevent_req *subreq; + int ret; + + subreq = sdap_get_rootdse_send(state, state->ev, state->opts, state->sh); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_cli_rootdse_done, req); + + if (!state->sh->connected) { + /* this rootdse search is performed before we actually do a bind, + * so we need to set up the callbacks or we will never get notified + * of a reply */ + + ret = sdap_set_connected(state->sh, state->ev); + if (ret) { + tevent_req_error(req, ret); + } + } +} + +static void sdap_cli_rootdse_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + const char *sasl_mech; + int ret; + + ret = sdap_get_rootdse_recv(subreq, state, &state->rootdse); + talloc_zfree(subreq); + if (ret) { + if (ret == ETIMEDOUT) { /* retry another server */ + be_fo_set_port_status(state->be, state->service->name, + state->srv, PORT_NOT_WORKING); + ret = sdap_cli_resolve_next(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + /* RootDSE was not available on + * the server. + * Continue, and just assume that the + * features requested by the config + * work properly. + */ + state->rootdse = NULL; + } + + + ret = sdap_cli_use_rootdse(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_cli_use_rootdse failed\n"); + tevent_req_error(req, ret); + return; + } + + sasl_mech = dp_opt_get_string(state->opts->basic, SDAP_SASL_MECH); + + if (state->do_auth && sasl_mech && state->rootdse) { + /* check if server claims to support the configured SASL MECH */ + if (!sdap_is_sasl_mech_supported(state->sh, sasl_mech)) { + tevent_req_error(req, ENOTSUP); + return; + } + } + + if (state->do_auth && sasl_mech && sdap_sasl_mech_needs_kinit(sasl_mech)) { + if (dp_opt_get_bool(state->opts->basic, SDAP_KRB5_KINIT)) { + sdap_cli_kinit_step(req); + return; + } + } + + sdap_cli_auth_step(req); +} + +static errno_t sdap_cli_use_rootdse(struct sdap_cli_connect_state *state) +{ + errno_t ret; + + if (state->rootdse) { + /* save rootdse data about supported features */ + ret = sdap_set_rootdse_supported_lists(state->rootdse, state->sh); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_set_rootdse_supported_lists failed\n"); + return ret; + } + + ret = sdap_set_config_options_with_rootdse(state->rootdse, state->opts, + state->opts->sdom); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_set_config_options_with_rootdse failed.\n"); + return ret; + } + + } + + ret = sdap_get_server_opts_from_rootdse(state, + state->service->uri, + state->rootdse, + state->opts, &state->srv_opts); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_get_server_opts_from_rootdse failed.\n"); + return ret; + } + + return EOK; +} + +static void sdap_cli_kinit_step(struct tevent_req *req) +{ + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + struct tevent_req *subreq; + + subreq = sdap_kinit_send(state, state->ev, + state->be, + state->sh, + state->service->kinit_service_name, + dp_opt_get_int(state->opts->basic, + SDAP_OPT_TIMEOUT), + dp_opt_get_string(state->opts->basic, + SDAP_KRB5_KEYTAB), + dp_opt_get_string(state->opts->basic, + SDAP_SASL_AUTHID), + sdap_gssapi_realm(state->opts->basic), + dp_opt_get_bool(state->opts->basic, + SDAP_KRB5_CANONICALIZE), + dp_opt_get_int(state->opts->basic, + SDAP_KRB5_TICKET_LIFETIME)); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_cli_kinit_done, req); +} + +static void sdap_cli_kinit_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + time_t expire_time = 0; + errno_t ret; + + ret = sdap_kinit_recv(subreq, &expire_time); + talloc_zfree(subreq); + if (ret != EOK) { + /* We're not able to authenticate to the LDAP server. + * There's not much we can do except for going offline */ + DEBUG(SSSDBG_TRACE_FUNC, + "Cannot get a TGT: ret [%d](%s)\n", ret, sss_strerror(ret)); + tevent_req_error(req, EACCES); + return; + } + state->sh->expire_time = expire_time; + + sdap_cli_auth_step(req); +} + +static void sdap_cli_auth_step(struct tevent_req *req) +{ + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + struct tevent_req *subreq; + time_t now; + int expire_timeout; + int expire_offset; + + const char *sasl_mech = dp_opt_get_string(state->opts->basic, + SDAP_SASL_MECH); + const char *user_dn = dp_opt_get_string(state->opts->basic, + SDAP_DEFAULT_BIND_DN); + const char *authtok_type; + struct dp_opt_blob authtok_blob; + struct sss_auth_token *authtok; + errno_t ret; + + /* It's possible that connection was terminated by server (e.g. #2435), + to overcome this try to connect again. */ + if (state->sh == NULL || !state->sh->connected) { + DEBUG(SSSDBG_TRACE_FUNC, "No connection available. " + "Trying to reconnect.\n"); + ret = sdap_cli_auth_reconnect(req); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_cli_auth_reconnect failed: %d:[%s]\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + } + return; + } + + /* Set the LDAP expiration time + * If SASL has already set it, use the sooner of the two + */ + now = time(NULL); + expire_timeout = dp_opt_get_int(state->opts->basic, SDAP_EXPIRE_TIMEOUT); + expire_offset = dp_opt_get_int(state->opts->basic, SDAP_EXPIRE_OFFSET); + if (expire_offset > 0) { + expire_timeout += sss_rand() % (expire_offset + 1); + } else if (expire_offset < 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Negative value [%d] of ldap_connection_expire_offset " + "is not allowed.\n", + expire_offset); + } + + DEBUG(SSSDBG_CONF_SETTINGS, "expire timeout is %d\n", expire_timeout); + if (!state->sh->expire_time + || (state->sh->expire_time > (now + expire_timeout))) { + state->sh->expire_time = now + expire_timeout; + DEBUG(SSSDBG_TRACE_LIBS, + "the connection will expire at %"SPRItime"\n", + state->sh->expire_time); + } + + if (!state->do_auth || + (sasl_mech == NULL && user_dn == NULL)) { + DEBUG(SSSDBG_TRACE_LIBS, + "No authentication requested or SASL auth forced off\n"); + tevent_req_done(req); + return; + } + + authtok_type = dp_opt_get_string(state->opts->basic, + SDAP_DEFAULT_AUTHTOK_TYPE); + authtok = sss_authtok_new(state); + if(authtok == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + if (authtok_type != NULL) { + if (strcasecmp(authtok_type, "password") != 0) { + DEBUG(SSSDBG_TRACE_LIBS, "Invalid authtoken type\n"); + tevent_req_error(req, EINVAL); + return; + } + + authtok_blob = dp_opt_get_blob(state->opts->basic, + SDAP_DEFAULT_AUTHTOK); + if (authtok_blob.data) { + ret = sss_authtok_set_password(authtok, + (const char *)authtok_blob.data, + authtok_blob.length); + if (ret) { + tevent_req_error(req, ret); + return; + } + } + } + + subreq = sdap_auth_send(state, state->ev, + state->sh, sasl_mech, + dp_opt_get_string(state->opts->basic, + SDAP_SASL_AUTHID), + user_dn, authtok, + dp_opt_get_int(state->opts->basic, + SDAP_OPT_TIMEOUT)); + talloc_free(authtok); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_cli_auth_done, req); +} + +static errno_t sdap_cli_auth_reconnect(struct tevent_req *req) +{ + struct sdap_cli_connect_state *state; + struct tevent_req *subreq; + errno_t ret; + + state = tevent_req_data(req, struct sdap_cli_connect_state); + + ret = decide_tls_usage(state->force_tls, state->opts->basic, + state->service->uri, &state->use_tls); + if (ret != EOK) { + goto done; + } + + subreq = sdap_connect_send(state, state->ev, state->opts, + state->service->uri, + state->service->sockaddr, + state->service->sockaddr_len, + state->use_tls); + + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_cli_auth_reconnect_done, req); + + ret = EOK; + +done: + return ret; +} + +static void sdap_cli_auth_reconnect_done(struct tevent_req *subreq) +{ + struct sdap_cli_connect_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_cli_connect_state); + + talloc_zfree(state->sh); + + ret = sdap_connect_recv(subreq, state, &state->sh); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + /* if TLS was used, the sdap handle is already marked as connected */ + if (!state->use_tls) { + /* we need to mark handle as connected to allow anonymous bind */ + ret = sdap_set_connected(state->sh, state->ev); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_set_connected() failed.\n"); + goto done; + } + } + + /* End request if reconnecting failed to avoid endless loop */ + if (state->sh == NULL || !state->sh->connected) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to reconnect.\n"); + ret = EIO; + goto done; + } + + sdap_cli_auth_step(req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } +} + +static void sdap_cli_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + int ret; + + ret = sdap_auth_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (state->use_rootdse && !state->rootdse) { + /* We weren't able to read rootDSE during unauthenticated bind. + * Let's try again now that we are authenticated */ + subreq = sdap_get_rootdse_send(state, state->ev, + state->opts, state->sh); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_cli_rootdse_auth_done, req); + return; + } + + tevent_req_done(req); +} + +static void sdap_cli_rootdse_auth_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + + ret = sdap_get_rootdse_recv(subreq, state, &state->rootdse); + talloc_zfree(subreq); + if (ret) { + if (ret == ETIMEDOUT) { + /* The server we authenticated against went down. Retry another + * one */ + be_fo_set_port_status(state->be, state->service->name, + state->srv, PORT_NOT_WORKING); + ret = sdap_cli_resolve_next(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + /* RootDSE was not available on + * the server. + * Continue, and just assume that the + * features requested by the config + * work properly. + */ + state->use_rootdse = false; + state->rootdse = NULL; + tevent_req_done(req); + return; + } + + /* We were able to get rootDSE after authentication */ + ret = sdap_cli_use_rootdse(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_cli_use_rootdse failed\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_cli_connect_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + bool *can_retry, + struct sdap_handle **gsh, + struct sdap_server_opts **srv_opts) +{ + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + enum tevent_req_state tstate; + uint64_t err_uint64; + int err; + + if (can_retry) { + *can_retry = true; + } + if (tevent_req_is_error(req, &tstate, &err_uint64)) { + /* mark the server as bad if connection failed */ + if (state->srv) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to establish connection " + "[%"PRIu64"]: %s\n", err_uint64, sss_strerror(err_uint64)); + + be_fo_set_port_status(state->be, state->service->name, + state->srv, PORT_NOT_WORKING); + } else { + if (can_retry) { + *can_retry = false; + } + } + + if (tstate == TEVENT_REQ_USER_ERROR) { + err = (int)err_uint64; + if (err == EOK) { + return EINVAL; + } + return err; + } + return EIO; + } else if (state->srv) { + DEBUG(SSSDBG_TRACE_FUNC, "Connection established.\n"); + + be_fo_set_port_status(state->be, state->service->name, + state->srv, PORT_WORKING); + } + + if (gsh) { + if (*gsh) { + talloc_zfree(*gsh); + } + *gsh = talloc_steal(memctx, state->sh); + if (!*gsh) { + return ENOMEM; + } + } else { + talloc_zfree(state->sh); + } + + if (srv_opts) { + *srv_opts = talloc_steal(memctx, state->srv_opts); + } + + return EOK; +} + +static int synchronous_tls_setup(LDAP *ldap) +{ + int lret; + int optret; + int ldaperr; + int msgid; + char *errmsg = NULL; + char *diag_msg; + LDAPMessage *result = NULL; + TALLOC_CTX *tmp_ctx; + + DEBUG(SSSDBG_CONF_SETTINGS, "Executing START TLS\n"); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return LDAP_NO_MEMORY; + + lret = ldap_start_tls(ldap, NULL, NULL, &msgid); + if (lret != LDAP_SUCCESS) { + optret = sss_ldap_get_diagnostic_msg(tmp_ctx, ldap, &diag_msg); + if (optret == LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_start_tls failed: [%s] [%s]\n", + sss_ldap_err2string(lret), diag_msg); + sss_log(SSS_LOG_ERR, "Could not start TLS. %s", diag_msg); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldap_start_tls failed: [%s]\n", sss_ldap_err2string(lret)); + sss_log(SSS_LOG_ERR, "Could not start TLS. " + "Check for certificate issues."); + } + goto done; + } + + lret = ldap_result(ldap, msgid, 1, NULL, &result); + if (lret != LDAP_RES_EXTENDED) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected ldap_result, expected [%lu] got [%d].\n", + LDAP_RES_EXTENDED, lret); + lret = LDAP_PARAM_ERROR; + goto done; + } + + lret = ldap_parse_result(ldap, result, &ldaperr, NULL, &errmsg, NULL, NULL, + 0); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_result failed (%d) [%d][%s]\n", msgid, lret, + sss_ldap_err2string(lret)); + goto done; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "START TLS result: %s(%d), %s\n", + sss_ldap_err2string(ldaperr), ldaperr, errmsg); + + if (ldap_tls_inplace(ldap)) { + DEBUG(SSSDBG_TRACE_ALL, "SSL/TLS handler already in place.\n"); + lret = LDAP_SUCCESS; + goto done; + } + + lret = ldap_install_tls(ldap); + if (lret != LDAP_SUCCESS) { + + optret = sss_ldap_get_diagnostic_msg(tmp_ctx, ldap, &diag_msg); + if (optret == LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_install_tls failed: [%s] [%s]\n", + sss_ldap_err2string(lret), diag_msg); + sss_log(SSS_LOG_ERR, "Could not start TLS encryption. %s", diag_msg); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_install_tls failed: [%s]\n", + sss_ldap_err2string(lret)); + sss_log(SSS_LOG_ERR, "Could not start TLS encryption. " + "Check for certificate issues."); + } + + goto done; + } + + lret = LDAP_SUCCESS; +done: + if (result) ldap_msgfree(result); + if (errmsg) ldap_memfree(errmsg); + talloc_zfree(tmp_ctx); + return lret; +} + +static int sdap_rebind_proc(LDAP *ldap, LDAP_CONST char *url, ber_tag_t request, + ber_int_t msgid, void *params) +{ + struct sdap_rebind_proc_params *p = talloc_get_type(params, + struct sdap_rebind_proc_params); + const char *sasl_mech; + const char *user_dn; + struct berval password = {0, NULL}; + LDAPControl **request_controls = NULL; + LDAPControl *ctrls[2] = { NULL, NULL }; + TALLOC_CTX *tmp_ctx = NULL; + struct sasl_bind_state *sasl_bind_state; + int ret; + + if (ldap == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Trying LDAP rebind while not connected.\n"); + return ERR_NETWORK_IO; + } + + if (p->use_start_tls) { + ret = synchronous_tls_setup(ldap); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "synchronous_tls_setup failed.\n"); + return ret; + } + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return LDAP_NO_MEMORY; + } + + sasl_mech = dp_opt_get_string(p->opts->basic, SDAP_SASL_MECH); + + if (sasl_mech == NULL) { + ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, &ctrls[0]); + if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_ldap_control_create failed to create " + "Password Policy control.\n"); + goto done; + } + request_controls = ctrls; + + user_dn = dp_opt_get_string(p->opts->basic, SDAP_DEFAULT_BIND_DN); + if (user_dn != NULL) { + /* this code doesn't make copies of password + * but only keeps pointer to opts internals + */ + ret = sdap_auth_get_authtok(dp_opt_get_string(p->opts->basic, + SDAP_DEFAULT_AUTHTOK_TYPE), + dp_opt_get_blob(p->opts->basic, + SDAP_DEFAULT_AUTHTOK), + &password); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_auth_get_authtok failed.\n"); + ret = LDAP_LOCAL_ERROR; + goto done; + } + } + + ret = ldap_sasl_bind_s(ldap, user_dn, LDAP_SASL_SIMPLE, &password, + request_controls, NULL, NULL); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_sasl_bind_s failed (%d)[%s]\n", ret, + sss_ldap_err2string(ret)); + } + } else { + sasl_bind_state = talloc_zero(tmp_ctx, struct sasl_bind_state); + if (sasl_bind_state == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = LDAP_NO_MEMORY; + goto done; + } + sasl_bind_state->sasl_user = dp_opt_get_string(p->opts->basic, + SDAP_SASL_AUTHID); + ret = ldap_sasl_interactive_bind_s(ldap, NULL, + sasl_mech, NULL, NULL, + LDAP_SASL_QUIET, + (*sdap_sasl_interact), + sasl_bind_state); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_sasl_interactive_bind_s failed (%d)[%s]\n", ret, + sss_ldap_err2string(ret)); + } + } + + DEBUG(SSSDBG_TRACE_LIBS, "%s bind to [%s].\n", + (ret == LDAP_SUCCESS ? "Successfully" : "Failed to"), url); + +done: + if (ctrls[0]) ldap_control_free(ctrls[0]); + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ldap/sdap_async_enum.c b/src/providers/ldap/sdap_async_enum.c new file mode 100644 index 0000000..44cec84 --- /dev/null +++ b/src/providers/ldap/sdap_async_enum.c @@ -0,0 +1,773 @@ +/* + SSSD + + LDAP Enumeration Module + + Authors: + Simo Sorce + Jakub Hrozek + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_async_enum.h" +#include "providers/ldap/sdap_idmap.h" + +static struct tevent_req *enum_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_op *op, + bool purge); +static errno_t enum_users_recv(struct tevent_req *req); + +static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_op *op, + bool purge); +static errno_t enum_groups_recv(struct tevent_req *req); + +/* ==Enumeration-Request-with-connections=================================== */ +struct sdap_dom_enum_ex_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + + struct sdap_id_conn_ctx *user_conn; + struct sdap_id_conn_ctx *group_conn; + struct sdap_id_conn_ctx *svc_conn; + struct sdap_id_op *user_op; + struct sdap_id_op *group_op; + struct sdap_id_op *svc_op; + + bool purge; +}; + +static errno_t sdap_dom_enum_ex_retry(struct tevent_req *req, + struct sdap_id_op *op, + tevent_req_fn tcb); +static bool sdap_dom_enum_ex_connected(struct tevent_req *subreq); +static void sdap_dom_enum_ex_get_users(struct tevent_req *subreq); +static void sdap_dom_enum_ex_users_done(struct tevent_req *subreq); +static void sdap_dom_enum_ex_get_groups(struct tevent_req *subreq); +static void sdap_dom_enum_ex_groups_done(struct tevent_req *subreq); +static void sdap_dom_enum_ex_get_svcs(struct tevent_req *subreq); +static void sdap_dom_enum_ex_svcs_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_dom_enum_ex_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *user_conn, + struct sdap_id_conn_ctx *group_conn, + struct sdap_id_conn_ctx *svc_conn) +{ + struct tevent_req *req; + struct sdap_dom_enum_ex_state *state; + int t; + errno_t ret; + + req = tevent_req_create(memctx, &state, struct sdap_dom_enum_ex_state); + if (req == NULL) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sdom = sdom; + state->user_conn = user_conn; + state->group_conn = group_conn; + state->svc_conn = svc_conn; + ctx->last_enum = tevent_timeval_current(); + + t = dp_opt_get_int(ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if ((ctx->last_purge.tv_sec + t) < ctx->last_enum.tv_sec) { + state->purge = true; + } + + state->user_op = sdap_id_op_create(state, user_conn->conn_cache); + if (state->user_op == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_create failed for users\n"); + ret = EIO; + goto fail; + } + + ret = sdap_dom_enum_ex_retry(req, state->user_op, + sdap_dom_enum_ex_get_users); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_dom_enum_ex_retry failed\n"); + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t sdap_dom_enum_ex_retry(struct tevent_req *req, + struct sdap_id_op *op, + tevent_req_fn tcb) +{ + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + struct tevent_req *subreq; + errno_t ret; + + subreq = sdap_id_op_connect_send(op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_id_op_connect_send failed: %d\n", ret); + return ret; + } + + tevent_req_set_callback(subreq, tcb, req); + return EOK; +} + +static bool sdap_dom_enum_ex_connected(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, + "Backend is marked offline, retry later!\n"); + tevent_req_done(req); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Domain enumeration failed to connect to " \ + "LDAP server: (%d)[%s]\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } + return false; + } + + return true; +} + +static void sdap_dom_enum_ex_get_users(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + + if (sdap_dom_enum_ex_connected(subreq) == false) { + return; + } + + subreq = enum_users_send(state, state->ev, + state->ctx, state->sdom, + state->user_op, state->purge); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_dom_enum_ex_users_done, req); +} + +static void sdap_dom_enum_ex_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + errno_t ret; + int dp_error; + + ret = enum_users_recv(subreq); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->user_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_dom_enum_ex_retry(req, state->user_op, + sdap_dom_enum_ex_get_users); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, "Backend is offline, retrying later\n"); + tevent_req_done(req); + return; + } else if (ret != EOK && ret != ENOENT) { + /* Non-recoverable error */ + DEBUG(SSSDBG_OP_FAILURE, + "User enumeration failed: %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->group_op = sdap_id_op_create(state, state->group_conn->conn_cache); + if (state->group_op == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_create failed for groups\n"); + tevent_req_error(req, EIO); + return; + } + + ret = sdap_dom_enum_ex_retry(req, state->group_op, + sdap_dom_enum_ex_get_groups); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Continues to sdap_dom_enum_ex_get_groups */ +} + +static void sdap_dom_enum_ex_get_groups(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + + if (sdap_dom_enum_ex_connected(subreq) == false) { + return; + } + + subreq = enum_groups_send(state, state->ev, state->ctx, + state->sdom, + state->group_op, state->purge); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_dom_enum_ex_groups_done, req); +} + +static void sdap_dom_enum_ex_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + int ret; + int dp_error; + + ret = enum_groups_recv(subreq); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->group_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_dom_enum_ex_retry(req, state->group_op, + sdap_dom_enum_ex_get_groups); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, "Backend is offline, retrying later\n"); + tevent_req_done(req); + return; + } else if (ret != EOK && ret != ENOENT) { + /* Non-recoverable error */ + DEBUG(SSSDBG_OP_FAILURE, + "Group enumeration failed: %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + + state->svc_op = sdap_id_op_create(state, state->svc_conn->conn_cache); + if (state->svc_op == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_create failed for svcs\n"); + tevent_req_error(req, EIO); + return; + } + + ret = sdap_dom_enum_ex_retry(req, state->svc_op, + sdap_dom_enum_ex_get_svcs); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } +} + +static void sdap_dom_enum_ex_get_svcs(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + + if (sdap_dom_enum_ex_connected(subreq) == false) { + return; + } + + subreq = enum_services_send(state, state->ev, state->ctx, + state->svc_op, state->purge); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_dom_enum_ex_svcs_done, req); +} + +static void sdap_dom_enum_ex_svcs_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + int ret; + int dp_error; + + ret = enum_services_recv(subreq); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->svc_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_dom_enum_ex_retry(req, state->user_op, + sdap_dom_enum_ex_get_svcs); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, "Backend is offline, retrying later\n"); + tevent_req_done(req); + return; + } else if (ret != EOK && ret != ENOENT) { + /* Non-recoverable error */ + DEBUG(SSSDBG_OP_FAILURE, + "Service enumeration failed: %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + /* Ok, we've completed an enumeration. Save this to the + * sysdb so we can postpone starting up the enumeration + * process on the next SSSD service restart (to avoid + * slowing down system boot-up + */ + ret = sysdb_set_enumerated(state->sdom->dom, SYSDB_HAS_ENUMERATED_ID, + true); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not mark domain as having enumerated.\n"); + /* This error is non-fatal, so continue */ + } + + if (state->purge) { + ret = ldap_id_cleanup(state->ctx, state->sdom); + if (ret != EOK) { + /* Not fatal, worst case we'll have stale entries that would be + * removed on a subsequent online lookup + */ + DEBUG(SSSDBG_MINOR_FAILURE, "Cleanup failed: [%d]: %s\n", + ret, sss_strerror(ret)); + } + } + + tevent_req_done(req); +} + +errno_t sdap_dom_enum_ex_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Enumeration-Request==================================================== */ +struct tevent_req * +sdap_dom_enum_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn) +{ + return sdap_dom_enum_ex_send(memctx, ev, ctx, sdom, conn, conn, conn); +} + +errno_t sdap_dom_enum_recv(struct tevent_req *req) +{ + return sdap_dom_enum_ex_recv(req); +} + +/* ==User-Enumeration===================================================== */ +struct enum_users_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + + char *filter; + const char **attrs; +}; + +static void enum_users_done(struct tevent_req *subreq); + +static struct tevent_req *enum_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_op *op, + bool purge) +{ + struct tevent_req *req, *subreq; + struct enum_users_state *state; + int ret; + bool use_mapping; + + req = tevent_req_create(memctx, &state, struct enum_users_state); + if (!req) return NULL; + + state->ev = ev; + state->sdom = sdom; + state->ctx = ctx; + state->op = op; + + use_mapping = sdap_idmap_domain_has_algorithmic_mapping( + ctx->opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + + /* We always want to filter on objectclass and an available name */ + state->filter = talloc_asprintf(state, + "(&(objectclass=%s)(%s=*)", + ctx->opts->user_map[SDAP_OC_USER].name, + ctx->opts->user_map[SDAP_AT_USER_NAME].name); + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + if (use_mapping) { + /* If we're ID-mapping, check for the objectSID as well */ + state->filter = talloc_asprintf_append_buffer( + state->filter, "(%s=*)", + ctx->opts->user_map[SDAP_AT_USER_OBJECTSID].name); + } else { + /* We're not ID-mapping, so make sure to only get entries + * that have UID and GID + */ + state->filter = talloc_asprintf_append_buffer( + state->filter, "(%s=*)(%s=*)", + ctx->opts->user_map[SDAP_AT_USER_UID].name, + ctx->opts->user_map[SDAP_AT_USER_GID].name); + } + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + if (ctx->srv_opts && ctx->srv_opts->max_user_value && !purge) { + /* If we have lastUSN available and we're not doing a full + * refresh, limit to changes with a higher entryUSN value. + */ + state->filter = talloc_asprintf_append_buffer( + state->filter, + "(%s>=%s)(!(%s=%s))", + ctx->opts->user_map[SDAP_AT_USER_USN].name, + ctx->srv_opts->max_user_value, + ctx->opts->user_map[SDAP_AT_USER_USN].name, + ctx->srv_opts->max_user_value); + + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + } + + /* Terminate the search filter */ + state->filter = talloc_asprintf_append_buffer(state->filter, ")"); + if (!state->filter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, ctx->opts->user_map, + ctx->opts->user_map_cnt, + NULL, &state->attrs, NULL); + if (ret != EOK) goto fail; + + /* TODO: restrict the enumerations to using a single + * search base at a time. + */ + + subreq = sdap_get_users_send(state, state->ev, + state->sdom->dom, + state->sdom->dom->sysdb, + state->ctx->opts, + state->sdom->user_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->ctx->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + SDAP_LOOKUP_ENUMERATE, NULL); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_users_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void enum_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct enum_users_state *state = tevent_req_data(req, + struct enum_users_state); + char *usn_value; + char *endptr = NULL; + unsigned usn_number; + int ret; + + ret = sdap_get_users_recv(subreq, state, &usn_value); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (usn_value) { + talloc_zfree(state->ctx->srv_opts->max_user_value); + state->ctx->srv_opts->max_user_value = + talloc_steal(state->ctx, usn_value); + errno = 0; + usn_number = strtoul(usn_value, &endptr, 10); + if (!errno && endptr && (*endptr == '\0') && (endptr != usn_value) + && (usn_number > state->ctx->srv_opts->last_usn)) { + state->ctx->srv_opts->last_usn = usn_number; + } + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Users higher USN value: [%s]\n", + state->ctx->srv_opts->max_user_value); + + tevent_req_done(req); +} + +static errno_t enum_users_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* =Group-Enumeration===================================================== */ +struct enum_groups_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + + char *filter; + const char **attrs; +}; + +static void enum_groups_done(struct tevent_req *subreq); + +static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_op *op, + bool purge) +{ + struct tevent_req *req, *subreq; + struct enum_groups_state *state; + int ret; + bool use_mapping; + bool non_posix = false; + char *oc_list; + + req = tevent_req_create(memctx, &state, struct enum_groups_state); + if (!req) return NULL; + + state->ev = ev; + state->sdom = sdom; + state->ctx = ctx; + state->op = op; + + if (sdom->dom->type == DOM_TYPE_APPLICATION) { + non_posix = true; + } + + use_mapping = sdap_idmap_domain_has_algorithmic_mapping( + ctx->opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + + /* We always want to filter on objectclass and an available name */ + oc_list = sdap_make_oc_list(state, ctx->opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(%s)(%s=*)", oc_list, + ctx->opts->group_map[SDAP_AT_GROUP_NAME].name); + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + if (!non_posix && use_mapping) { + /* If we're ID-mapping, check for the objectSID as well */ + state->filter = talloc_asprintf_append_buffer( + state->filter, "(%s=*)", + ctx->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name); + } else { + /* We're not ID-mapping, so make sure to only get entries + * that have a non-zero GID. + */ + state->filter = talloc_asprintf_append_buffer( + state->filter, "(&(%s=*)(!(%s=0)))", + ctx->opts->group_map[SDAP_AT_GROUP_GID].name, + ctx->opts->group_map[SDAP_AT_GROUP_GID].name); + } + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + if (ctx->srv_opts && ctx->srv_opts->max_group_value && !purge) { + state->filter = talloc_asprintf_append_buffer( + state->filter, + "(%s>=%s)(!(%s=%s))", + ctx->opts->group_map[SDAP_AT_GROUP_USN].name, + ctx->srv_opts->max_group_value, + ctx->opts->group_map[SDAP_AT_GROUP_USN].name, + ctx->srv_opts->max_group_value); + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + } + + /* Terminate the search filter */ + state->filter = talloc_asprintf_append_buffer(state->filter, ")"); + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, ctx->opts->group_map, SDAP_OPTS_GROUP, + NULL, &state->attrs, NULL); + if (ret != EOK) goto fail; + + /* TODO: restrict the enumerations to using a single + * search base at a time. + */ + + subreq = sdap_get_groups_send(state, state->ev, + state->sdom, + state->ctx->opts, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->ctx->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + SDAP_LOOKUP_ENUMERATE, false); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_groups_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void enum_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct enum_groups_state *state = tevent_req_data(req, + struct enum_groups_state); + char *usn_value; + char *endptr = NULL; + unsigned usn_number; + int ret; + + ret = sdap_get_groups_recv(subreq, state, &usn_value); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (usn_value) { + talloc_zfree(state->ctx->srv_opts->max_group_value); + state->ctx->srv_opts->max_group_value = + talloc_steal(state->ctx, usn_value); + errno = 0; + usn_number = strtoul(usn_value, &endptr, 10); + if (!errno && endptr && (*endptr == '\0') && (endptr != usn_value) + && (usn_number > state->ctx->srv_opts->last_usn)) { + state->ctx->srv_opts->last_usn = usn_number; + } + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Groups higher USN value: [%s]\n", + state->ctx->srv_opts->max_group_value); + + tevent_req_done(req); +} + +static errno_t enum_groups_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_enum.h b/src/providers/ldap/sdap_async_enum.h new file mode 100644 index 0000000..2da38f9 --- /dev/null +++ b/src/providers/ldap/sdap_async_enum.h @@ -0,0 +1,49 @@ +/* + SSSD + + LDAP Enumeration Module + + Authors: + Simo Sorce + Jakub Hrozek + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_ASYNC_ENUM_H_ +#define _SDAP_ASYNC_ENUM_H_ + +struct tevent_req * +sdap_dom_enum_ex_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *user_conn, + struct sdap_id_conn_ctx *group_conn, + struct sdap_id_conn_ctx *svc_conn); + +errno_t sdap_dom_enum_ex_recv(struct tevent_req *req); + +struct tevent_req * +sdap_dom_enum_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn); + +errno_t sdap_dom_enum_recv(struct tevent_req *req); + +#endif /* _SDAP_ASYNC_ENUM_H_ */ diff --git a/src/providers/ldap/sdap_async_groups.c b/src/providers/ldap/sdap_async_groups.c new file mode 100644 index 0000000..f36e5c5 --- /dev/null +++ b/src/providers/ldap/sdap_async_groups.c @@ -0,0 +1,2471 @@ +/* + SSSD + + Async LDAP Helper routines - retrieving groups + + Copyright (C) Simo Sorce - 2009 + Copyright (C) 2010, Ralf Haferkamp , Novell Inc. + Copyright (C) Jan Zeleny - 2011 + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "util/probes.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_idmap.h" + +/* ==Group-Parsing Routines=============================================== */ + +static int sdap_find_entry_by_origDN(TALLOC_CTX *memctx, + struct sysdb_ctx *ctx, + struct sss_domain_info *domain, + const char *orig_dn, + char **_localdn, + bool *_is_group) +{ + TALLOC_CTX *tmpctx; + const char *attrs[] = {SYSDB_OBJECTCLASS, SYSDB_OBJECTCATEGORY, NULL}; + struct ldb_dn *base_dn; + char *filter; + struct ldb_message **msgs; + size_t num_msgs; + int ret; + char *sanitized_dn; + const char *objectclass; + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + ret = sss_filter_sanitize_dn(tmpctx, orig_dn, &sanitized_dn); + if (ret != EOK) { + ret = ENOMEM; + goto done; + } + + filter = talloc_asprintf(tmpctx, "%s=%s", SYSDB_ORIG_DN, sanitized_dn); + if (!filter) { + ret = ENOMEM; + goto done; + } + + base_dn = sysdb_domain_dn(tmpctx, domain); + if (!base_dn) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Searching cache for [%s].\n", sanitized_dn); + ret = sysdb_search_entry(tmpctx, ctx, + base_dn, LDB_SCOPE_SUBTREE, filter, attrs, + &num_msgs, &msgs); + if (ret) { + goto done; + } + if (num_msgs != 1) { + ret = ENOENT; + goto done; + } + + *_localdn = talloc_strdup(memctx, ldb_dn_get_linearized(msgs[0]->dn)); + if (!*_localdn) { + ret = ENOENT; + goto done; + } + + if (_is_group != NULL) { + objectclass = ldb_msg_find_attr_as_string(msgs[0], SYSDB_OBJECTCATEGORY, + NULL); + if (objectclass == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "An entry without a %s?\n", + SYSDB_OBJECTCATEGORY); + ret = EINVAL; + goto done; + } + + *_is_group = strcmp(SYSDB_GROUP_CLASS, objectclass) == 0; + } + + ret = EOK; + +done: + talloc_zfree(tmpctx); + return ret; +} + +static errno_t +sdap_get_members_with_primary_gid(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + gid_t gid, char ***_localdn, size_t *_ndn) +{ + static const char *search_attrs[] = { SYSDB_NAME, NULL }; + char *filter; + struct ldb_message **msgs; + size_t count; + size_t i; + errno_t ret; + char **localdn; + + /* Don't search if the group is non-POSIX */ + if (!gid) return EOK; + + filter = talloc_asprintf(mem_ctx, "(%s=%llu)", SYSDB_GIDNUM, + (unsigned long long) gid); + if (!filter) { + return ENOMEM; + } + + ret = sysdb_search_users(mem_ctx, domain, filter, + search_attrs, &count, &msgs); + talloc_free(filter); + if (ret == ENOENT) { + *_localdn = NULL; + *_ndn = 0; + return EOK; + } else if (ret != EOK) { + return ret; + } + + localdn = talloc_array(mem_ctx, char *, count); + if (!localdn) { + talloc_free(msgs); + return ENOMEM; + } + + for (i=0; i < count; i++) { + localdn[i] = talloc_strdup(localdn, + ldb_dn_get_linearized(msgs[i]->dn)); + if (!localdn[i]) { + talloc_free(localdn); + talloc_free(msgs); + return ENOMEM; + } + } + + talloc_free(msgs); + *_localdn = localdn; + *_ndn = count; + return EOK; +} + +static errno_t +sdap_dn_by_primary_gid(TALLOC_CTX *mem_ctx, struct sysdb_attrs *ldap_attrs, + struct sss_domain_info *domain, + struct sdap_options *opts, + char ***_dn_list, size_t *_count) +{ + gid_t gid; + errno_t ret; + + ret = sysdb_attrs_get_uint32_t(ldap_attrs, + opts->group_map[SDAP_AT_GROUP_GID].sys_name, + &gid); + if (ret == ENOENT) { + /* Non-POSIX AD group. Skip. */ + *_dn_list = NULL; + *_count = 0; + return EOK; + } else if (ret && ret != ENOENT) { + return ret; + } + + ret = sdap_get_members_with_primary_gid(mem_ctx, domain, gid, + _dn_list, _count); + if (ret) return ret; + + return EOK; +} + +static bool has_member(struct ldb_message_element *member_el, + char *member) +{ + struct ldb_val val; + + val.data = (uint8_t *) member; + val.length = strlen(member); + + /* This is bad complexity, but this loop should only be invoked in + * the very rare scenario of AD POSIX group that is primary group of + * some users but has user member attributes at the same time + */ + if (ldb_msg_find_val(member_el, &val) != NULL) { + return true; + } + + return false; +} + +static void link_pgroup_members(struct sysdb_attrs *group_attrs, + struct ldb_message_element *member_el, + char **userdns, + size_t nuserdns) +{ + int i, j; + + j = 0; + for (i=0; i < nuserdns; i++) { + if (has_member(member_el, userdns[i])) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Member %s already included, skipping\n", userdns[i]); + continue; + } + + member_el->values[member_el->num_values + j].data = (uint8_t *) \ + talloc_steal(group_attrs, userdns[i]); + member_el->values[member_el->num_values + j].length = \ + strlen(userdns[i]); + j++; + } + member_el->num_values += j; +} + +static int sdap_fill_memberships(struct sdap_options *opts, + struct sysdb_attrs *group_attrs, + struct sysdb_ctx *ctx, + struct sss_domain_info *domain, + hash_table_t *ghosts, + struct ldb_val *values, + int num_values, + char **userdns, + size_t nuserdns) +{ + struct ldb_message_element *el; + int i, j; + int ret; + errno_t hret; + hash_key_t key; + hash_value_t value; + struct sdap_domain *sdom; + struct sysdb_ctx *member_sysdb; + struct sss_domain_info *member_dom; + + ret = sysdb_attrs_get_el(group_attrs, SYSDB_MEMBER, &el); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_attrs_get_el failed\n"); + goto done; + } + + /* Just allocate both big enough to contain all members for now */ + el->values = talloc_realloc(group_attrs, el->values, struct ldb_val, + el->num_values + num_values + nuserdns); + if (!el->values) { + DEBUG(SSSDBG_MINOR_FAILURE, "No memory to allocate group attrs\n"); + ret = ENOMEM; + goto done; + } + + j = el->num_values; + for (i = 0; i < num_values; i++) { + if (ghosts == NULL) { + hret = HASH_ERROR_KEY_NOT_FOUND; + } else { + key.type = HASH_KEY_STRING; + key.str = (char *)values[i].data; + hret = hash_lookup(ghosts, &key, &value); + } + + if (hret == HASH_ERROR_KEY_NOT_FOUND) { + sdom = sdap_domain_get_by_dn(opts, (char *)values[i].data); + if (sdom == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Member [%s] is it out of domain " + "scope?\n", (char *)values[i].data); + member_sysdb = ctx; + member_dom = domain; + } else { + member_sysdb = sdom->dom->sysdb; + member_dom = sdom->dom; + } + + /* sync search entry with this as origDN */ + ret = sdap_find_entry_by_origDN(el->values, member_sysdb, + member_dom, (char *)values[i].data, + (char **)&el->values[j].data, + NULL); + if (ret == ENOENT) { + /* member may be outside of the configured search bases + * or out of scope of nesting limit */ + DEBUG(SSSDBG_MINOR_FAILURE, "Member [%s] was not found in " + "cache. Is it out of scope?\n", (char *)values[i].data); + continue; + } + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "'sdap_find_entry_by_origDN' failed for member [%s].\n", + (char *)values[i].data); + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, " member #%d (%s): [%s]\n", + i, (char *)values[i].data, + (char *)el->values[j].data); + + el->values[j].length = strlen((char *)el->values[j].data); + j++; + } else if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "hash_lookup failed: [%d]: %s\n", hret, strerror(hret)); + ret = EFAULT; + goto done; + } + + /* If the member is in ghost table, it has + * already been processed - just skip it */ + } + el->num_values = j; + + link_pgroup_members(group_attrs, el, userdns, nuserdns); + ret = EOK; + +done: + return ret; +} + +/* ==Save-Group-Entry===================================================== */ + + /* FIXME: support non legacy */ + /* FIXME: support storing additional attributes */ + +static errno_t +sdap_store_group_with_gid(struct sss_domain_info *domain, + const char *name, + gid_t gid, + struct sysdb_attrs *group_attrs, + uint64_t cache_timeout, + bool posix_group, + time_t now) +{ + errno_t ret; + + /* make sure that non-POSIX (empty or explicit gid=0) groups have the + * gidNumber set to zero even if updating existing group */ + if (!posix_group) { + ret = sysdb_attrs_add_uint32(group_attrs, SYSDB_GIDNUM, 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not set explicit GID 0 for %s\n", name); + return ret; + } + } + + ret = sysdb_store_group(domain, name, gid, group_attrs, + cache_timeout, now); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not store group %s\n", name); + return ret; + } + + return ret; +} + +static errno_t +sdap_process_ghost_members(struct sysdb_attrs *attrs, + struct sdap_options *opts, + hash_table_t *ghosts, + bool populate_members, + bool store_original_member, + struct sysdb_attrs *sysdb_attrs) +{ + errno_t ret; + struct ldb_message_element *gh; + struct ldb_message_element *memberel; + struct ldb_message_element *sysdb_memberel; + struct ldb_message_element *ghostel; + size_t cnt; + int i; + int hret; + hash_key_t key; + hash_value_t value; + + ret = sysdb_attrs_get_el(attrs, SYSDB_GHOST, &gh); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error reading ghost attributes: [%s]\n", + strerror(ret)); + return ret; + } + + ret = sysdb_attrs_get_el_ext(attrs, + opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, + false, &memberel); + if (ret == ENOENT) { + /* Create a dummy element with no values in order for the loop to just + * fall through and make sure the attrs array is not reallocated. + */ + memberel = talloc(attrs, struct ldb_message_element); + if (memberel == NULL) { + return ENOMEM; + } + memberel->num_values = 0; + memberel->values = NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error reading members: [%s]\n", strerror(ret)); + return ret; + } + + if (store_original_member) { + DEBUG(SSSDBG_TRACE_FUNC, "The group has %d members\n", memberel->num_values); + for (i = 0; i < memberel->num_values; i++) { + ret = sysdb_attrs_add_string(sysdb_attrs, SYSDB_ORIG_MEMBER, + (const char *) memberel->values[i].data); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add member [%s]\n", + (const char *) memberel->values[i].data); + return ret; + } + } + } + + if (populate_members) { + ret = sysdb_attrs_get_el(sysdb_attrs, SYSDB_MEMBER, &sysdb_memberel); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error reading group members from group_attrs: [%s]\n", + strerror(ret)); + return ret; + } + sysdb_memberel->values = memberel->values; + sysdb_memberel->num_values = memberel->num_values; + } + + ret = sysdb_attrs_get_el(sysdb_attrs, SYSDB_GHOST, &ghostel); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error getting ghost element: [%s]\n", strerror(ret)); + return ret; + } + ghostel->values = gh->values; + ghostel->num_values = gh->num_values; + + cnt = ghostel->num_values + memberel->num_values; + DEBUG(SSSDBG_TRACE_FUNC, "Group has %zu members\n", cnt); + + /* Now process RFC2307bis ghost hash table */ + if (ghosts && cnt > 0) { + ghostel->values = talloc_realloc(sysdb_attrs, ghostel->values, + struct ldb_val, cnt); + if (ghostel->values == NULL) { + return ENOMEM; + } + + for (i = 0; i < memberel->num_values; i++) { + key.type = HASH_KEY_STRING; + key.str = (char *) memberel->values[i].data; + hret = hash_lookup(ghosts, &key, &value); + if (hret == HASH_ERROR_KEY_NOT_FOUND) { + continue; + } else if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error checking hash table: [%s]\n", + hash_error_string(hret)); + return EFAULT; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Adding ghost member for group [%s]\n", (char *) value.ptr); + ghostel->values[ghostel->num_values].data = \ + (uint8_t *) talloc_strdup(ghostel->values, value.ptr); + if (ghostel->values[ghostel->num_values].data == NULL) { + return ENOMEM; + } + ghostel->values[ghostel->num_values].length = strlen(value.ptr); + ghostel->num_values++; + } + } + + return EOK; +} + +static int sdap_save_group(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + bool populate_members, + bool store_original_member, + hash_table_t *ghosts, + char **_usn_value, + time_t now) +{ + struct ldb_message_element *el; + struct sysdb_attrs *group_attrs; + const char *group_name = NULL; + gid_t gid = 0; + errno_t ret; + char *usn_value = NULL; + TALLOC_CTX *tmpctx = NULL; + bool posix_group; + bool use_id_mapping; + bool need_filter; + char *sid_str; + struct sss_domain_info *subdomain; + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + ret = ENOMEM; + goto done; + } + + group_attrs = sysdb_new_attrs(tmpctx); + if (group_attrs == NULL) { + ret = ENOMEM; + goto done; + } + + /* Always store SID string if available */ + ret = sdap_attrs_get_sid_str(tmpctx, opts->idmap_ctx, attrs, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].sys_name, + &sid_str); + if (ret == EOK) { + ret = sysdb_attrs_add_string(group_attrs, SYSDB_SID_STR, sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not add SID string: [%s]\n", + sss_strerror(ret)); + goto done; + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "objectSID: not available for group [%s].\n", + group_name); + sid_str = NULL; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not identify objectSID: [%s]\n", + sss_strerror(ret)); + sid_str = NULL; + } + + /* Always store UUID if available */ + ret = sysdb_handle_original_uuid( + opts->group_map[SDAP_AT_GROUP_UUID].def_name, + attrs, + opts->group_map[SDAP_AT_GROUP_UUID].sys_name, + group_attrs, SYSDB_UUID); + if (ret != EOK) { + DEBUG((ret == ENOENT) ? SSSDBG_TRACE_ALL : SSSDBG_MINOR_FAILURE, + "Failed to retrieve UUID [%d][%s].\n", ret, sss_strerror(ret)); + } + + /* If this object has a SID available, we will determine the correct + * domain by its SID. */ + if (sid_str != NULL) { + subdomain = sss_get_domain_by_sid_ldap_fallback(get_domains_head(dom), + sid_str); + if (subdomain) { + dom = subdomain; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "SID %s does not belong to any known " + "domain\n", sid_str); + } + } + + ret = sdap_get_group_primary_name(tmpctx, opts, attrs, dom, &group_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get group name\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Processing group %s\n", group_name); + + posix_group = true; + ret = sdap_check_ad_group_type(dom, opts, attrs, group_name, + &need_filter); + if (ret != EOK) { + goto done; + } + if (need_filter) { + posix_group = false; + gid = 0; + + ret = sysdb_attrs_add_bool(group_attrs, SYSDB_POSIX, false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Error: Failed to mark group as non-POSIX!\n"); + goto done; + } + } + + if (posix_group) { + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(opts->idmap_ctx, + dom->name, + sid_str); + if (use_id_mapping) { + posix_group = true; + + if (sid_str == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "SID not available, cannot map a " \ + "unix ID to group [%s].\n", group_name); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Mapping group [%s] objectSID [%s] to unix ID\n", + group_name, sid_str); + + /* Convert the SID into a UNIX group ID */ + ret = sdap_idmap_sid_to_unix(opts->idmap_ctx, sid_str, &gid); + if (ret == ENOTSUP) { + /* ENOTSUP is returned if built-in SID was provided + * => do not store the group, but return EOK */ + DEBUG(SSSDBG_TRACE_FUNC, "Skipping built-in object.\n"); + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert SID string: [%s]\n", + sss_strerror(ret)); + goto done; + } + + /* Store the GID in the ldap_attrs so it doesn't get + * treated as a missing attribute from LDAP and removed. + */ + ret = sdap_replace_id(attrs, SYSDB_GIDNUM, gid); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set the id-mapped GID\n"); + goto done; + } + } else { + ret = sysdb_attrs_get_bool(attrs, SYSDB_POSIX, &posix_group); + if (ret == ENOENT) { + posix_group = true; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error reading posix attribute: [%s]\n", + sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "This is%s a posix group\n", (posix_group)?"":" not"); + ret = sysdb_attrs_add_bool(group_attrs, SYSDB_POSIX, posix_group); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error setting posix attribute: [%s]\n", + sss_strerror(ret)); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(attrs, + opts->group_map[SDAP_AT_GROUP_GID].sys_name, + &gid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "no gid provided for [%s] in domain [%s].\n", + group_name, dom->name); + ret = EINVAL; + goto done; + } + } + } + + /* check that the gid is valid for this domain */ + if (posix_group) { + if (OUT_OF_ID_RANGE(gid, dom->id_min, dom->id_max)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Group [%s] filtered out! (id out of range)\n", group_name); + ret = EINVAL; + goto done; + } + /* Group ID OK */ + } + + ret = sdap_attrs_add_string(attrs, SYSDB_ORIG_DN, "original DN", + group_name, group_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error setting original DN: [%s]\n", + sss_strerror(ret)); + goto done; + } + + ret = sdap_attrs_add_string(attrs, + opts->group_map[SDAP_AT_GROUP_MODSTAMP].sys_name, + "original mod-Timestamp", + group_name, group_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error setting mod timestamp: [%s]\n", + sss_strerror(ret)); + goto done; + } + + ret = sysdb_attrs_get_el(attrs, + opts->group_map[SDAP_AT_GROUP_USN].sys_name, &el); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error looking up group USN: [%s]\n", + sss_strerror(ret)); + goto done; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Original USN value is not available for [%s].\n", group_name); + } else { + ret = sysdb_attrs_add_string(group_attrs, + opts->group_map[SDAP_AT_GROUP_USN].sys_name, + (const char*)el->values[0].data); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error setting group USN: [%s]\n", + sss_strerror(ret)); + goto done; + } + usn_value = talloc_strdup(tmpctx, (const char*)el->values[0].data); + if (!usn_value) { + ret = ENOMEM; + goto done; + } + } + + ret = sdap_process_ghost_members(attrs, opts, ghosts, + populate_members, store_original_member, + group_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to save ghost members\n"); + goto done; + } + + ret = sdap_save_all_names(group_name, attrs, dom, + SYSDB_MEMBER_GROUP, group_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to save group names\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Storing info for group %s\n", group_name); + + ret = sdap_store_group_with_gid(dom, group_name, gid, group_attrs, + dom->group_timeout, + posix_group, now); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not store group with GID: [%s]\n", + sss_strerror(ret)); + goto done; + } + + if (_usn_value) { + *_usn_value = talloc_steal(memctx, usn_value); + } + + talloc_steal(memctx, group_attrs); + ret = EOK; + +done: + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to save group [%s]: [%s]\n", + group_name ? group_name : "Unknown", + sss_strerror(ret)); + } + talloc_free(tmpctx); + return ret; +} + +static errno_t +are_sids_from_same_dom(const char *sid1, const char *sid2, bool *_result) +{ + size_t len_prefix_sid1; + size_t len_prefix_sid2; + char *rid1, *rid2; + bool result; + + rid1 = strrchr(sid1, '-'); + if (rid1 == NULL) { + return EINVAL; + } + + rid2 = strrchr(sid2, '-'); + if (rid2 == NULL) { + return EINVAL; + } + + len_prefix_sid1 = rid1 - sid1; + len_prefix_sid2 = rid2 - sid2; + + result = (len_prefix_sid1 == len_prefix_sid2) && + (strncmp(sid1, sid2, len_prefix_sid1) == 0); + + *_result = result; + + return EOK; +} + +static errno_t +retain_extern_members(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + const char *group_name, + const char *group_sid, + char ***_userdns, + size_t *_nuserdns) +{ + TALLOC_CTX *tmp_ctx; + const char **sids, **dns; + bool same_domain; + errno_t ret; + size_t i, n; + size_t nuserdns = 0; + const char **userdns = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_get_sids_of_members(tmp_ctx, dom, group_name, &sids, &dns, &n); + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, + "get_sids_of_members failed: %d [%s]\n", + ret, sss_strerror(ret)); + } + goto done; + } + + for (i=0; i < n; i++) { + ret = are_sids_from_same_dom(group_sid, sids[i], &same_domain); + if (ret == EOK && !same_domain) { + DEBUG(SSSDBG_TRACE_ALL, "extern member: %s\n", dns[i]); + nuserdns++; + userdns = talloc_realloc(tmp_ctx, userdns, const char*, nuserdns); + if (userdns == NULL) { + ret = ENOMEM; + goto done; + } + userdns[nuserdns-1] = talloc_steal(userdns, dns[i]); + } + } + *_nuserdns = nuserdns; + *_userdns = discard_const(talloc_steal(mem_ctx, userdns)); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +/* ==Save-Group-Members=================================================== */ + + /* FIXME: support non-legacy */ + /* FIXME: support storing additional attributes */ + +static int sdap_save_grpmem(TALLOC_CTX *memctx, + struct sysdb_ctx *ctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + hash_table_t *ghosts, + time_t now) +{ + struct ldb_message_element *el; + struct sysdb_attrs *group_attrs = NULL; + const char *group_sid; + const char *group_name; + char **userdns = NULL; + size_t nuserdns = 0; + struct sss_domain_info *group_dom = NULL; + int ret; + const char *remove_attrs[] = {SYSDB_MEMBER, SYSDB_ORIG_MEMBER, SYSDB_GHOST, + NULL}; + const char *check_dom; + const char *check_name; + + if (dom->ignore_group_members) { + DEBUG(SSSDBG_TRACE_FUNC, "Group members are ignored, nothing to do.\n"); + return EOK; + } + + ret = sysdb_attrs_get_string(attrs, SYSDB_SID_STR, &group_sid); + if (ret != EOK) { + /* Try harder. */ + ret = sdap_attrs_get_sid_str(memctx, opts->idmap_ctx, attrs, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].sys_name, + discard_const(&group_sid)); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Failed to get group sid\n"); + group_sid = NULL; + } + } + + if (group_sid != NULL) { + group_dom = sss_get_domain_by_sid_ldap_fallback(get_domains_head(dom), + group_sid); + if (group_dom == NULL) { + ret = well_known_sid_to_name(group_sid, &check_dom, &check_name); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Skipping group with SID [%s][%s\\%s] which is " + "currently not handled by SSSD.\n", + group_sid, check_dom, check_name); + return EOK; + } + + DEBUG(SSSDBG_TRACE_FUNC, "SID [%s] does not belong to any known " + "domain, using [%s].\n", group_sid, + dom->name); + } + } + + if (group_dom == NULL) { + group_dom = dom; + } + + ret = sdap_get_group_primary_name(memctx, opts, attrs, group_dom, + &group_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get group name\n"); + goto fail; + } + DEBUG(SSSDBG_TRACE_FUNC, "Processing group %s\n", group_name); + + /* With AD we also want to merge in parent groups of primary GID as they + * are reported with tokenGroups, too + */ + if (opts->schema_type == SDAP_SCHEMA_AD) { + ret = sdap_dn_by_primary_gid(memctx, attrs, group_dom, opts, + &userdns, &nuserdns); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_dn_by_primary_gid failed: [%d][%s].\n", + ret, strerror(ret)); + goto fail; + } + } + + /* This is a temporal solution until the IPA provider is able to + * resolve external group membership. + * https://fedorahosted.org/sssd/ticket/2522 + */ + if (opts->schema_type == SDAP_SCHEMA_IPA_V1) { + if (group_sid != NULL) { + ret = retain_extern_members(memctx, group_dom, group_name, + group_sid, &userdns, &nuserdns); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "retain_extern_members failed: %d:[%s].\n", + ret, sss_strerror(ret)); + } + } + } + + ret = sysdb_attrs_get_el(attrs, + opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, &el); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_attrs_get_el failed: [%d][%s].\n", + ret, strerror(ret)); + goto fail; + } + + if (el->num_values == 0 && nuserdns == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "No members for group [%s]\n", group_name); + + ret = sysdb_remove_attrs(group_dom, group_name, SYSDB_MEMBER_GROUP, + discard_const(remove_attrs)); + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_remove_attrs failed.\n"); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_remove_attrs failed for missing entry\n"); + } + goto fail; + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Adding member users to group [%s]\n", group_name); + + group_attrs = sysdb_new_attrs(memctx); + if (!group_attrs) { + DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_new_attrs failed\n"); + ret = ENOMEM; + goto fail; + } + + ret = sdap_fill_memberships(opts, group_attrs, ctx, group_dom, ghosts, + el->values, el->num_values, + userdns, nuserdns); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_fill_memberships failed with [%d]: %s\n", ret, + strerror(ret)); + goto fail; + } + } + + ret = sysdb_store_group(group_dom, group_name, 0, group_attrs, + group_dom->group_timeout, now); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_store_group failed: [%d][%s].\n", + ret, strerror(ret)); + goto fail; + } + + return EOK; + +fail: + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to save members of group %s\n", group_name); + return ret; +} + + +/* ==Generic-Function-to-save-multiple-groups============================= */ + +static int sdap_save_groups(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **groups, + int num_groups, + bool populate_members, + hash_table_t *ghosts, + bool save_orig_member, + char **_usn_value) +{ + TALLOC_CTX *tmpctx; + char *higher_usn = NULL; + char *usn_value; + bool twopass; + bool has_nesting = false; + int ret; + errno_t sret; + int i; + struct sysdb_attrs **saved_groups = NULL; + int nsaved_groups = 0; + time_t now; + bool in_transaction = false; + + switch (opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + twopass = false; + break; + + case SDAP_SCHEMA_RFC2307BIS: + case SDAP_SCHEMA_IPA_V1: + case SDAP_SCHEMA_AD: + twopass = true; + has_nesting = true; + break; + + default: + return EINVAL; + } + + tmpctx = talloc_new(memctx); + if (!tmpctx) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + if (twopass && !populate_members) { + saved_groups = talloc_array(tmpctx, struct sysdb_attrs *, + num_groups); + if (!saved_groups) { + ret = ENOMEM; + goto done; + } + } + + now = time(NULL); + for (i = 0; i < num_groups; i++) { + usn_value = NULL; + + /* if 2 pass savemembers = false */ + ret = sdap_save_group(tmpctx, opts, dom, groups[i], + populate_members, + has_nesting && save_orig_member, + ghosts, &usn_value, now); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store group %d. Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_ALL, "Group %d processed!\n", i); + if (twopass && !populate_members) { + saved_groups[nsaved_groups] = groups[i]; + nsaved_groups++; + } + } + + if (usn_value) { + if (higher_usn) { + if ((strlen(usn_value) > strlen(higher_usn)) || + (strcmp(usn_value, higher_usn) > 0)) { + talloc_zfree(higher_usn); + higher_usn = usn_value; + } else { + talloc_zfree(usn_value); + } + } else { + higher_usn = usn_value; + } + } + } + + if (twopass && !populate_members) { + + for (i = 0; i < nsaved_groups; i++) { + + ret = sdap_save_grpmem(tmpctx, sysdb, opts, dom, saved_groups[i], + ghosts, now); + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store group %d members: %d\n", i, ret); + } else { + DEBUG(SSSDBG_FUNC_DATA, + "Can't save members of missing group %d\n", i); + } + } else { + DEBUG(SSSDBG_TRACE_ALL, "Group %d members processed!\n", i); + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + if (_usn_value) { + *_usn_value = talloc_steal(memctx, higher_usn); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_zfree(tmpctx); + return ret; +} + + +/* ==Process-Groups======================================================= */ + +struct sdap_process_group_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + + struct sysdb_attrs *group; + struct ldb_message_element* sysdb_dns; + struct ldb_message_element* ghost_dns; + const char **attrs; + const char *filter; + size_t check_count; + + bool enumeration; +}; + +static void sdap_process_group_members(struct tevent_req *subreq); + +static int sdap_process_group_members_2307bis(struct tevent_req *req, + struct sdap_process_group_state *state, + struct ldb_message_element *memberel); +static int sdap_process_group_members_2307(struct sdap_process_group_state *state, + struct ldb_message_element *memberel, + struct ldb_message_element *ghostel); + +static errno_t sdap_process_group_create_dns(TALLOC_CTX *mem_ctx, + size_t num_values, + struct ldb_message_element **_dns) +{ + struct ldb_message_element *dns; + + dns = talloc(mem_ctx, struct ldb_message_element); + if (dns == NULL) { + return ENOMEM; + } + + dns->num_values = 0; + dns->values = talloc_array(dns, struct ldb_val, + num_values); + if (dns->values == NULL) { + talloc_zfree(dns); + return ENOMEM; + } + + *_dns = dns; + + return EOK; +} + +static struct tevent_req * +sdap_process_group_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sysdb_attrs *group, + bool enumeration) +{ + struct ldb_message_element *el; + struct ldb_message_element *ghostel; + struct sdap_process_group_state *grp_state; + struct tevent_req *req = NULL; + const char **attrs; + char* filter; + int ret; + + req = tevent_req_create(memctx, &grp_state, + struct sdap_process_group_state); + if (!req) return NULL; + + ret = build_attrs_from_map(grp_state, opts->user_map, opts->user_map_cnt, + NULL, &attrs, NULL); + if (ret) { + goto done; + } + + /* FIXME: we ignore nested rfc2307bis groups for now */ + filter = talloc_asprintf(grp_state, "(objectclass=%s)", + opts->user_map[SDAP_OC_USER].name); + if (!filter) { + talloc_zfree(req); + return NULL; + } + + grp_state->ev = ev; + grp_state->opts = opts; + grp_state->dom = dom; + grp_state->sh = sh; + grp_state->sysdb = sysdb; + grp_state->group = group; + grp_state->check_count = 0; + grp_state->filter = filter; + grp_state->attrs = attrs; + grp_state->enumeration = enumeration; + + ret = sysdb_attrs_get_el(group, + opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, + &el); + if (ret) { + goto done; + } + + /* Group without members */ + if (el->num_values == 0) { + DEBUG(SSSDBG_FUNC_DATA, "No Members. Done!\n"); + ret = EOK; + goto done; + } + + ret = sysdb_attrs_get_el(group, + SYSDB_GHOST, + &ghostel); + if (ret) { + goto done; + } + + if (ghostel->num_values == 0) { + /* Element was probably newly created, look for "member" again */ + ret = sysdb_attrs_get_el(group, + opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, + &el); + if (ret != EOK) { + goto done; + } + } + + + ret = sdap_process_group_create_dns(grp_state, el->num_values, + &grp_state->sysdb_dns); + if (ret != EOK) { + goto done; + } + + ret = sdap_process_group_create_dns(grp_state, el->num_values, + &grp_state->ghost_dns); + if (ret != EOK) { + goto done; + } + + switch (opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + ret = sdap_process_group_members_2307(grp_state, el, ghostel); + break; + + case SDAP_SCHEMA_IPA_V1: + case SDAP_SCHEMA_AD: + case SDAP_SCHEMA_RFC2307BIS: + /* Note that this code branch will be used only if + * ldap_nesting_level = 0 is set in config file + */ + ret = sdap_process_group_members_2307bis(req, grp_state, el); + break; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown schema type %d\n", opts->schema_type); + ret = EINVAL; + break; + } + +done: + /* We managed to process all the entries */ + /* EBUSY means we need to wait for entries in LDAP */ + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_LIBS, "All group members processed\n"); + tevent_req_done(req); + tevent_req_post(req, ev); + } + + if (ret != EOK && ret != EBUSY) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static int +sdap_process_missing_member_2307bis(struct tevent_req *req, + char *user_dn) +{ + struct sdap_process_group_state *grp_state = + tevent_req_data(req, struct sdap_process_group_state); + struct tevent_req *subreq; + + subreq = sdap_get_generic_send(grp_state, + grp_state->ev, + grp_state->opts, + grp_state->sh, + user_dn, + LDAP_SCOPE_BASE, + grp_state->filter, + grp_state->attrs, + grp_state->opts->user_map, + grp_state->opts->user_map_cnt, + dp_opt_get_int(grp_state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_process_group_members, req); + + grp_state->check_count++; + return EOK; +} + +static int +sdap_process_group_members_2307bis(struct tevent_req *req, + struct sdap_process_group_state *state, + struct ldb_message_element *memberel) +{ + char *member_dn; + char *strdn; + int ret; + int i; + int nesting_level; + bool is_group; + + nesting_level = dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL); + + for (i=0; i < memberel->num_values; i++) { + member_dn = (char *)memberel->values[i].data; + + ret = sdap_find_entry_by_origDN(state->sysdb_dns->values, + state->sysdb, + state->dom, + member_dn, + &strdn, + &is_group); + + if (ret == EOK) { + if (nesting_level == 0 && is_group) { + /* Ignore group members which are groups themselves. */ + continue; + } + + /* + * User already cached in sysdb. Remember the sysdb DN for later + * use by sdap_save_groups() + */ + DEBUG(SSSDBG_TRACE_LIBS, "sysdbdn: %s\n", strdn); + state->sysdb_dns->values[state->sysdb_dns->num_values].data = + (uint8_t*) strdn; + state->sysdb_dns->values[state->sysdb_dns->num_values].length = + strlen(strdn); + state->sysdb_dns->num_values++; + } else if (ret == ENOENT) { + if (!state->enumeration) { + /* The user is not in sysdb, need to add it + * We don't need to do this if we're in an enumeration, + * because all real members should all be populated + * already by the first pass of the enumeration. + * Also, we don't want to be holding the sysdb + * transaction while we're performing LDAP lookups. + */ + DEBUG(SSSDBG_TRACE_LIBS, + "Searching LDAP for missing user entry\n"); + ret = sdap_process_missing_member_2307bis(req, + member_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error processing missing member #%d (%s):\n", + i, member_dn); + return ret; + } + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error checking cache for member #%d (%s):\n", + i, (char *)memberel->values[i].data); + return ret; + } + } + + if (state->check_count == 0) { + /* + * All group members are already cached in sysdb, we are done + * with this group. To avoid redundant sysdb lookups, populate the + * "member" attribute of the group entry with the sysdb DNs of + * the members. + */ + ret = EOK; + memberel->values = talloc_steal(state->group, state->sysdb_dns->values); + memberel->num_values = state->sysdb_dns->num_values; + } else { + ret = EBUSY; + } + + return ret; +} + +static int +sdap_add_group_member_2307(struct ldb_message_element *sysdb_dns, + const char *username) +{ + sysdb_dns->values[sysdb_dns->num_values].data = + (uint8_t *) talloc_strdup(sysdb_dns->values, username); + if (sysdb_dns->values[sysdb_dns->num_values].data == NULL) { + return ENOMEM; + } + sysdb_dns->values[sysdb_dns->num_values].length = + strlen(username); + sysdb_dns->num_values++; + + return EOK; +} + +static int +sdap_process_missing_member_2307(struct sdap_process_group_state *state, + char *member_name) +{ + int ret; + TALLOC_CTX *tmp_ctx; + const char *filter; + const char *username; + const char *user_dn; + char *sanitized_name; + size_t count; + struct ldb_message **msgs = NULL; + static const char *attrs[] = { SYSDB_NAME, NULL }; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + ret = sss_filter_sanitize(tmp_ctx, member_name, &sanitized_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to sanitize the given name:'%s'.\n", member_name); + goto done; + } + + /* Check for the alias in the sysdb */ + filter = talloc_asprintf(tmp_ctx, "(%s=%s)", SYSDB_NAME_ALIAS, + sanitized_name); + if (!filter) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_users(tmp_ctx, state->dom, filter, + attrs, &count, &msgs); + if (ret == EOK && count > 0) { + /* Entry exists but the group references it with an alias. */ + + if (count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More than one entry with this alias?\n"); + ret = EIO; + goto done; + } + + /* fill username with primary name */ + username = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL); + if (username == NULL) { + ret = EINVAL; + DEBUG(SSSDBG_MINOR_FAILURE, "Inconsistent sysdb: user " + "without primary name?\n"); + goto done; + } + user_dn = sysdb_user_strdn(tmp_ctx, state->dom->name, username); + if (user_dn == NULL) { + return ENOMEM; + } + + ret = sdap_add_group_member_2307(state->sysdb_dns, user_dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group member %s\n", username); + } + } else if (ret == ENOENT) { + /* The entry really does not exist, add a ghost */ + DEBUG(SSSDBG_TRACE_FUNC, "Adding a ghost entry\n"); + ret = sdap_add_group_member_2307(state->ghost_dns, member_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group member %s\n", member_name); + } + } else { + ret = EIO; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int +sdap_process_group_members_2307(struct sdap_process_group_state *state, + struct ldb_message_element *memberel, + struct ldb_message_element *ghostel) +{ + struct ldb_message *msg; + char *member_attr_val; + char *member_name; + char *userdn; + int ret; + int i; + + for (i=0; i < memberel->num_values; i++) { + member_attr_val = (char *)memberel->values[i].data; + + /* We need to skip over zero-length usernames */ + if (member_attr_val[0] == '\0') continue; + + /* RFC2307 stores members as plain usernames in the member attribute. + * Internally, we use FQDNs in the cache. + */ + member_name = sss_create_internal_fqname(state, member_attr_val, + state->dom->name); + if (member_name == NULL) { + return ENOMEM; + } + + ret = sysdb_search_user_by_name(state, state->dom, member_name, + NULL, &msg); + if (ret == EOK) { + /* + * User already cached in sysdb. Remember the sysdb DN for later + * use by sdap_save_groups() + */ + DEBUG(SSSDBG_TRACE_LIBS, + "Member already cached in sysdb: %s\n", member_name); + + userdn = sysdb_user_strdn(state->sysdb_dns, state->dom->name, member_name); + if (userdn == NULL) { + return ENOMEM; + } + + ret = sdap_add_group_member_2307(state->sysdb_dns, userdn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add member %s into sysdb\n", member_name); + goto done; + } + } else if (ret == ENOENT) { + /* The user is not in sysdb, need to add it */ + DEBUG(SSSDBG_TRACE_LIBS, "member #%d (%s): not found in sysdb\n", + i, member_name); + + ret = sdap_process_missing_member_2307(state, member_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error processing missing member #%d (%s):\n", + i, member_name); + goto done; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error checking cache for member #%d (%s):\n", + i, (char *) memberel->values[i].data); + goto done; + } + } + + ret = EOK; + talloc_free(memberel->values); + memberel->values = talloc_steal(state->group, state->sysdb_dns->values); + memberel->num_values = state->sysdb_dns->num_values; + talloc_free(ghostel->values); + ghostel->values = talloc_steal(state->group, state->ghost_dns->values); + ghostel->num_values = state->ghost_dns->num_values; + +done: + return ret; +} + +static void sdap_process_group_members(struct tevent_req *subreq) +{ + struct sysdb_attrs **usr_attrs; + size_t count; + int ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_process_group_state *state = + tevent_req_data(req, struct sdap_process_group_state); + struct ldb_message_element *el; + char *name_string; + + state->check_count--; + DEBUG(SSSDBG_TRACE_ALL, "Members remaining: %zu\n", state->check_count); + + ret = sdap_get_generic_recv(subreq, state, &count, &usr_attrs); + talloc_zfree(subreq); + if (ret) { + goto next; + } + if (count != 1) { + ret = EINVAL; + DEBUG(SSSDBG_TRACE_LIBS, + "Expected one user entry and got %zu\n", count); + goto next; + } + ret = sysdb_attrs_get_el(usr_attrs[0], + state->opts->user_map[SDAP_AT_USER_NAME].sys_name, &el); + if (el->num_values == 0) { + ret = EINVAL; + } + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get the member's name\n"); + goto next; + } + + name_string = sss_create_internal_fqname(state, + (const char *) el[0].values[0].data, + state->dom->name); + if (name_string == NULL) { + ret = ENOMEM; + goto next; + } + + state->ghost_dns->values[state->ghost_dns->num_values].data = + talloc_steal(state->ghost_dns->values, (uint8_t *) name_string); + state->ghost_dns->values[state->ghost_dns->num_values].length = + strlen(name_string); + state->ghost_dns->num_values++; + +next: + if (ret) { + DEBUG(SSSDBG_TRACE_FUNC, + "Error reading group member[%d]: %s. Skipping\n", + ret, strerror(ret)); + } + + if (state->check_count == 0) { + /* + * To avoid redundant sysdb lookups, populate the "member" attribute + * of the group entry with the sysdb DNs of the members. + */ + ret = sysdb_attrs_get_el(state->group, + state->opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, + &el); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get the group member attribute [%d]: %s\n", + ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + el->values = talloc_steal(state->group, state->sysdb_dns->values); + el->num_values = state->sysdb_dns->num_values; + + ret = sysdb_attrs_get_el(state->group, SYSDB_GHOST, &el); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + el->values = talloc_steal(state->group, state->ghost_dns->values); + el->num_values = state->ghost_dns->num_values; + DEBUG(SSSDBG_TRACE_ALL, "Processed Group - Done\n"); + tevent_req_done(req); + } +} + +static int sdap_process_group_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +/* ==Search-Groups-with-filter============================================ */ + +struct sdap_get_groups_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sdap_domain *sdom; + struct sysdb_ctx *sysdb; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + enum sdap_entry_lookup_type lookup_type; + bool no_members; + + char *higher_usn; + struct sysdb_attrs **groups; + size_t count; + size_t check_count; + hash_table_t *missing_external; + + hash_table_t *user_hash; + hash_table_t *group_hash; + + size_t base_iter; + struct sdap_search_base **search_bases; + + struct sdap_handle *ldap_sh; + struct sdap_id_op *op; +}; + +static errno_t sdap_get_groups_next_base(struct tevent_req *req); +static void sdap_get_groups_ldap_connect_done(struct tevent_req *subreq); +static void sdap_get_groups_process(struct tevent_req *subreq); +static void sdap_get_groups_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_options *opts, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type, + bool no_members) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_get_groups_state *state; + struct sdap_id_conn_ctx *ldap_conn = NULL; + + req = tevent_req_create(memctx, &state, struct sdap_get_groups_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sdom = sdom; + state->dom = sdom->dom; + state->sh = sh; + state->sysdb = sdom->dom->sysdb; + state->attrs = attrs; + state->higher_usn = NULL; + state->groups = NULL; + state->count = 0; + state->timeout = timeout; + state->lookup_type = lookup_type; + state->no_members = no_members; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = sdom->group_search_bases; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Group lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + /* With AD by default the Global Catalog is used for lookup. But the GC + * group object might not have full group membership data. To make sure we + * connect to an LDAP server of the group's domain. */ + ldap_conn = get_ldap_conn_from_sdom_pvt(state->opts, sdom); + if (ldap_conn != NULL) { + state->op = sdap_id_op_create(state, ldap_conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, + sdap_get_groups_ldap_connect_done, + req); + return req; + } + + ret = sdap_get_groups_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_get_groups_ldap_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_get_groups_state *state; + int ret; + int dp_error; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_get_groups_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + state->ldap_sh = sdap_id_op_handle(state->op); + + ret = sdap_get_groups_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t sdap_get_groups_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_groups_state *state; + bool need_paging = false; + int sizelimit = 0; + + state = tevent_req_data(req, struct sdap_get_groups_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for groups with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + switch (state->lookup_type) { + case SDAP_LOOKUP_SINGLE: + break; + /* Only requests that can return multiple entries should require + * the paging control + */ + case SDAP_LOOKUP_WILDCARD: + sizelimit = dp_opt_get_int(state->opts->basic, SDAP_WILDCARD_LIMIT); + need_paging = true; + break; + case SDAP_LOOKUP_ENUMERATE: + need_paging = true; + break; + } + + subreq = sdap_get_and_parse_generic_send( + state, state->ev, state->opts, + state->ldap_sh != NULL ? state->ldap_sh : state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->group_map, SDAP_OPTS_GROUP, + 0, NULL, NULL, sizelimit, state->timeout, + need_paging); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_groups_process, req); + + return EOK; +} + +static void sdap_nested_done(struct tevent_req *req); +static void sdap_search_group_copy_batch(struct sdap_get_groups_state *state, + struct sysdb_attrs **groups, + size_t count); + +static void sdap_get_groups_process(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_get_groups_state *state = + tevent_req_data(req, struct sdap_get_groups_state); + int ret; + int i; + bool next_base = false; + size_t count; + struct sysdb_attrs **groups; + char **sysdb_groupnamelist; + + ret = sdap_get_and_parse_generic_recv(subreq, state, + &count, &groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for groups, returned %zu results.\n", count); + + if (state->lookup_type == SDAP_LOOKUP_WILDCARD || \ + state->lookup_type == SDAP_LOOKUP_ENUMERATE || \ + count == 0) { + /* No users found in this search or looking up multiple entries */ + next_base = true; + } + + /* Add this batch of groups to the list */ + if (count > 0) { + state->groups = + talloc_realloc(state, + state->groups, + struct sysdb_attrs *, + state->count + count + 1); + if (!state->groups) { + tevent_req_error(req, ENOMEM); + return; + } + + sdap_search_group_copy_batch(state, groups, count); + } + + if (next_base) { + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_groups_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + } + + /* No more search bases + * Return ENOENT if no groups were found + */ + if (state->count == 0) { + tevent_req_error(req, ENOENT); + return; + } + + if (state->no_members) { + ret = sdap_get_primary_fqdn_list(state->dom, state, + state->groups, state->count, + state->opts->group_map[SDAP_AT_GROUP_NAME].name, + state->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + state->opts->idmap_ctx, + &sysdb_groupnamelist); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_primary_name_list failed.\n"); + tevent_req_error(req, ret); + return; + } + + ret = sdap_add_incomplete_groups(state->sysdb, state->dom, state->opts, + sysdb_groupnamelist, state->groups, + state->count); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "Writing only group data without members was successful.\n"); + tevent_req_done(req); + } else { + DEBUG(SSSDBG_OP_FAILURE, "sdap_add_incomplete_groups failed.\n"); + tevent_req_error(req, ret); + } + return; + } + + /* Check whether we need to do nested searches + * for RFC2307bis/FreeIPA/ActiveDirectory + * We don't need to do this for enumeration, + * because all groups will be picked up anyway. + * + * We can also skip this if we're using the + * LDAP_MATCHING_RULE_IN_CHAIN available in + * AD 2008 and later + */ + if (state->lookup_type == SDAP_LOOKUP_SINGLE) { + if ((state->opts->schema_type != SDAP_SCHEMA_RFC2307) + && (dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0)) { + subreq = sdap_nested_group_send(state, state->ev, state->sdom, + state->opts, state->sh, + state->groups[0]); + if (!subreq) { + tevent_req_error(req, EIO); + return; + } + + tevent_req_set_callback(subreq, sdap_nested_done, req); + return; + } + } + + /* We have all of the groups. Save them to the sysdb */ + state->check_count = state->count; + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to start transaction\n"); + tevent_req_error(req, ret); + return; + } + + if ((state->lookup_type == SDAP_LOOKUP_ENUMERATE + || state->lookup_type == SDAP_LOOKUP_WILDCARD) + && state->opts->schema_type != SDAP_SCHEMA_RFC2307 + && dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0) { + DEBUG(SSSDBG_TRACE_ALL, "Saving groups without members first " + "to allow unrolling of nested groups.\n"); + ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts, + state->groups, state->count, false, + NULL, true, NULL); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store groups.\n"); + tevent_req_error(req, ret); + return; + } + } + + for (i = 0; i < state->count; i++) { + subreq = sdap_process_group_send(state, state->ev, state->dom, + state->sysdb, state->opts, + state->sh, state->groups[i], + state->lookup_type == SDAP_LOOKUP_ENUMERATE); + + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_groups_done, req); + } +} + +static void sdap_search_group_copy_batch(struct sdap_get_groups_state *state, + struct sysdb_attrs **groups, + size_t count) +{ + size_t copied; + bool filter; + + /* Always copy all objects for wildcard lookups. */ + filter = state->lookup_type == SDAP_LOOKUP_SINGLE ? true : false; + + copied = sdap_steal_objects_in_dom(state->opts, + state->groups, + state->count, + state->dom, + groups, count, filter); + + state->count += copied; + state->groups[state->count] = NULL; +} + +static void sdap_get_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_get_groups_state *state = + tevent_req_data(req, struct sdap_get_groups_state); + + int ret; + errno_t sysret; + + ret = sdap_process_group_recv(subreq); + talloc_zfree(subreq); + if (ret) { + sysret = sysdb_transaction_cancel(state->sysdb); + if (sysret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not cancel sysdb transaction\n"); + } + tevent_req_error(req, ret); + return; + } + + state->check_count--; + DEBUG(SSSDBG_TRACE_ALL, "Groups remaining: %zu\n", state->check_count); + + + if (state->check_count == 0) { + DEBUG(SSSDBG_TRACE_ALL, "All groups processed\n"); + + /* If ignore_group_members is set for the domain, don't update + * group memberships in the cache. + * + * If enumeration is on, don't overwrite orig_members as they've been + * saved earlier. + */ + ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts, + state->groups, state->count, + !state->dom->ignore_group_members, NULL, + state->lookup_type == SDAP_LOOKUP_SINGLE, + &state->higher_usn); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store groups.\n"); + tevent_req_error(req, ret); + return; + } + DEBUG(SSSDBG_TRACE_ALL, "Saving %zu Groups - Done\n", state->count); + sysret = sysdb_transaction_commit(state->sysdb); + if (sysret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Couldn't commit transaction\n"); + tevent_req_error(req, sysret); + } else { + tevent_req_done(req); + } + } +} + +static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs **users, + int num_users, + hash_table_t **_ghosts); + + +int sdap_get_groups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **usn_value) +{ + struct sdap_get_groups_state *state = tevent_req_data(req, + struct sdap_get_groups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (usn_value) { + *usn_value = talloc_steal(mem_ctx, state->higher_usn); + } + + return EOK; +} + +static void sdap_nested_ext_done(struct tevent_req *subreq); + +static void sdap_nested_done(struct tevent_req *subreq) +{ + errno_t ret, tret; + unsigned long user_count; + unsigned long group_count; + bool in_transaction = false; + struct sysdb_attrs **users = NULL; + struct sysdb_attrs **groups = NULL; + hash_table_t *ghosts; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_groups_state *state = tevent_req_data(req, + struct sdap_get_groups_state); + + ret = sdap_nested_group_recv(state, subreq, &user_count, &users, + &group_count, &groups, + &state->missing_external); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Nested group processing failed: [%d][%s]\n", + ret, strerror(ret)); + goto fail; + } + + /* Save all of the users first so that they are in + * place for the groups to add them. + */ + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + PROBE(SDAP_NESTED_GROUP_POPULATE_PRE); + ret = sdap_nested_group_populate_users(state, state->sysdb, + state->dom, state->opts, + users, user_count, &ghosts); + PROBE(SDAP_NESTED_GROUP_POPULATE_POST); + if (ret != EOK) { + goto fail; + } + + PROBE(SDAP_NESTED_GROUP_SAVE_PRE); + ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts, + groups, group_count, false, ghosts, true, + &state->higher_usn); + PROBE(SDAP_NESTED_GROUP_SAVE_POST); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + + if (hash_count(state->missing_external) == 0) { + /* No external members. Processing complete */ + DEBUG(SSSDBG_TRACE_INTERNAL, "No external members, done\n"); + tevent_req_done(req); + return; + } + + /* At the moment, we need to save the direct groups & members in one + * transaction and then query the others in a separate requests + */ + subreq = sdap_nested_group_lookup_external_send(state, state->ev, + state->dom, + state->opts->ext_ctx, + state->missing_external); + if (subreq == NULL) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sdap_nested_ext_done, req); + return; + +fail: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + tevent_req_error(req, ret); +} + +static void sdap_nested_ext_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_groups_state *state = tevent_req_data(req, + struct sdap_get_groups_state); + + ret = sdap_nested_group_lookup_external_recv(state, subreq); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot resolve external members [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs **users, + int num_users, + hash_table_t **_ghosts) +{ + int i; + errno_t ret, sret; + struct ldb_message_element *el; + const char *username; + const char *original_dn; + const char *hash_key_dn; + struct sss_domain_info *user_dom; + struct sdap_domain *sdap_dom; + + TALLOC_CTX *tmp_ctx; + struct ldb_message **msgs; + const char *sysdb_name; + struct sysdb_attrs *attrs; + static const char *search_attrs[] = { SYSDB_NAME, NULL }; + hash_table_t *ghosts; + hash_key_t key; + hash_value_t value; + size_t count; + bool in_transaction = false; + + if (_ghosts == NULL) { + return EINVAL; + } + + if (num_users == 0) { + /* Nothing to do if there are no users */ + *_ghosts = NULL; + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + ret = sss_hash_create(tmp_ctx, num_users, &ghosts); + if (ret != HASH_SUCCESS) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction!\n"); + goto done; + } + in_transaction = true; + + for (i = 0; i < num_users; i++) { + ret = sysdb_attrs_get_el(users[i], SYSDB_ORIG_DN, &el); + if (el->num_values == 0) { + ret = EINVAL; + } + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User entry %d has no originalDN attribute\n", i); + goto done; + } + original_dn = (const char *) el->values[0].data; + + sdap_dom = sdap_domain_get_by_dn(opts, original_dn); + user_dom = sdap_dom == NULL ? domain : sdap_dom->dom; + + ret = sdap_get_user_primary_name(tmp_ctx, opts, users[i], + user_dom, &username); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "User entry %d has no name attribute. Skipping\n", i); + continue; + } + + /* Check for the specified origDN in the sysdb */ + PROBE(SDAP_NESTED_GROUP_POPULATE_SEARCH_USERS_PRE); + ret = sysdb_search_users_by_orig_dn(tmp_ctx, user_dom, original_dn, + search_attrs, &count, &msgs); + PROBE(SDAP_NESTED_GROUP_POPULATE_SEARCH_USERS_POST); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error checking cache for user entry\n"); + goto done; + } else if (ret == EOK) { + /* The entry is cached but expired. Update the username + * if needed. */ + if (count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More than one entry with this origDN? Skipping\n"); + continue; + } + + sysdb_name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL); + if (strcmp(sysdb_name, username) == 0) { + /* Username is correct, continue */ + continue; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(attrs, SYSDB_NAME, username); + if (ret) goto done; + ret = sysdb_set_entry_attr(user_dom->sysdb, msgs[0]->dn, attrs, + SYSDB_MOD_REP); + if (ret != EOK) goto done; + } else { + /* The DN of the user object and the DN in the member attribute + * might differ, e.g. in case. Since we later search the hash with + * DNs from the member attribute we should try to use DN from the + * member attribute here as well. This should be added earlier in + * the SYSDB_DN_FOR_MEMBER_HASH_TABLE attribute. If this does not + * exists we fall-back to original_dn which should work in the + * most cases as well. */ + ret = sysdb_attrs_get_string(users[i], + SYSDB_DN_FOR_MEMBER_HASH_TABLE, + &hash_key_dn); + if (ret != EOK) { + hash_key_dn = original_dn; + } + + key.type = HASH_KEY_STRING; + key.str = talloc_steal(ghosts, discard_const(hash_key_dn)); + value.type = HASH_VALUE_PTR; + /* Already qualified from sdap_get_user_primary_name() */ + value.ptr = talloc_steal(ghosts, discard_const(username)); + ret = hash_enter(ghosts, &key, &value); + if (ret != HASH_SUCCESS) { + talloc_free(key.str); + talloc_free(value.ptr); + ret = ENOMEM; + goto done; + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + + if (ret != EOK) { + *_ghosts = NULL; + } else { + *_ghosts = talloc_steal(mem_ctx, ghosts); + } + talloc_zfree(tmp_ctx); + return ret; +} diff --git a/src/providers/ldap/sdap_async_hosts.c b/src/providers/ldap/sdap_async_hosts.c new file mode 100644 index 0000000..0633a3f --- /dev/null +++ b/src/providers/ldap/sdap_async_hosts.c @@ -0,0 +1,209 @@ +/* + SSSD + + Authors: + Jan Zeleny + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_host_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_options *opts; + const char **attrs; + struct sdap_attr_map *host_map; + + struct sdap_search_base **search_bases; + int search_base_iter; + + char *cur_filter; + char *host_filter; + + const char *hostname; + + /* Return values */ + size_t host_count; + struct sysdb_attrs **hosts; +}; + +static void +sdap_host_info_done(struct tevent_req *subreq); + +static errno_t +sdap_host_info_next(struct tevent_req *req, + struct sdap_host_state *state); + +/** + * hostname == NULL -> look up all hosts / host groups + * hostname != NULL -> look up only given host and groups + * it's member of + */ +struct tevent_req * +sdap_host_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *hostname, + struct sdap_attr_map *host_map, + struct sdap_search_base **search_bases) +{ + errno_t ret; + struct sdap_host_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct sdap_host_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->sh = sh; + state->opts = opts; + state->hostname = hostname; + state->search_bases = search_bases; + state->search_base_iter = 0; + state->cur_filter = NULL; + state->host_map = host_map; + + ret = build_attrs_from_map(state, host_map, SDAP_OPTS_HOST, + NULL, &state->attrs, NULL); + if (ret != EOK) { + goto immediate; + } + + if (hostname == NULL) { + state->host_filter = talloc_asprintf(state, "(objectClass=%s)", + host_map[SDAP_OC_HOST].name); + } else { + state->host_filter = talloc_asprintf(state, "(&(objectClass=%s)(%s=%s))", + host_map[SDAP_OC_HOST].name, + host_map[SDAP_AT_HOST_FQDN].name, + hostname); + } + if (state->host_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + ret = sdap_host_info_next(req, state); + if (ret == EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "No host search base configured?\n"); + ret = EINVAL; + } + + if (ret != EAGAIN) { + goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t sdap_host_info_next(struct tevent_req *req, + struct sdap_host_state *state) +{ + struct sdap_search_base *base; + struct tevent_req *subreq; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->host_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + subreq = sdap_get_generic_send(state, state->ev, state->opts, + state->sh, base->basedn, + base->scope, state->cur_filter, + state->attrs, state->host_map, + SDAP_OPTS_HOST, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error requesting host info\n"); + talloc_zfree(state->cur_filter); + return EIO; + } + tevent_req_set_callback(subreq, sdap_host_info_done, req); + + return EAGAIN; +} + +static void +sdap_host_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_host_state *state = tevent_req_data(req, struct sdap_host_state); + + ret = sdap_get_generic_recv(subreq, state, + &state->host_count, + &state->hosts); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->host_count == 0) { + state->search_base_iter++; + ret = sdap_host_info_next(req, state); + if (ret == EOK) { + /* No more search bases to try */ + tevent_req_error(req, ENOENT); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + return; + } + + /* Nothing else to do, just complete the req */ + tevent_req_done(req); +} + +errno_t sdap_host_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *host_count, + struct sysdb_attrs ***hosts) +{ + struct sdap_host_state *state = tevent_req_data(req, struct sdap_host_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *host_count = state->host_count; + *hosts = talloc_steal(mem_ctx, state->hosts); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_initgroups.c b/src/providers/ldap/sdap_async_initgroups.c new file mode 100644 index 0000000..97be594 --- /dev/null +++ b/src/providers/ldap/sdap_async_initgroups.c @@ -0,0 +1,3647 @@ +/* + SSSD + + Async LDAP Helper routines - initgroups operation + + Copyright (C) Simo Sorce - 2009 + Copyright (C) 2010, Ralf Haferkamp , Novell Inc. + Copyright (C) Jan Zeleny - 2011 + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_users.h" + +/* ==Save-fake-group-list=====================================*/ +errno_t sdap_add_incomplete_groups(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + char **sysdb_groupnames, + struct sysdb_attrs **ldap_groups, + int ldap_groups_count) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + int i, mi, ai; + const char *groupname; + const char *original_dn; + const char *uuid = NULL; + char **missing; + gid_t gid; + int ret; + errno_t sret; + bool in_transaction = false; + bool posix; + time_t now; + char *sid_str = NULL; + bool use_id_mapping; + bool need_filter; + struct sss_domain_info *subdomain; + + /* There are no groups in LDAP but we should add user to groups?? */ + if (ldap_groups_count == 0) return EOK; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + missing = talloc_array(tmp_ctx, char *, ldap_groups_count+1); + if (!missing) { + ret = ENOMEM; + goto done; + } + mi = 0; + + for (i=0; sysdb_groupnames[i]; i++) { + subdomain = find_domain_by_object_name(domain, sysdb_groupnames[i]); + if (subdomain == NULL) { + subdomain = domain; + } + ret = sysdb_search_group_by_name(tmp_ctx, subdomain, sysdb_groupnames[i], NULL, + &msg); + if (ret == EOK) { + continue; + } else if (ret == ENOENT) { + missing[mi] = talloc_strdup(missing, sysdb_groupnames[i]); + DEBUG(SSSDBG_TRACE_LIBS, "Group #%d [%s][%s] is not cached, " \ + "need to add a fake entry\n", + i, sysdb_groupnames[i], missing[mi]); + mi++; + continue; + } else if (ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "search for group failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + missing[mi] = NULL; + + /* All groups are cached, nothing to do */ + if (mi == 0) { + ret = EOK; + goto done; + } + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(opts->idmap_ctx, + domain->name, + domain->domain_id); + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot start sysdb transaction [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + in_transaction = true; + + + now = time(NULL); + for (i=0; missing[i]; i++) { + /* The group is not in sysdb, need to add a fake entry */ + for (ai=0; ai < ldap_groups_count; ai++) { + ret = sdap_get_group_primary_name(tmp_ctx, opts, ldap_groups[ai], + domain, &groupname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "The group has no name attribute\n"); + goto done; + } + + if (strcmp(groupname, missing[i]) == 0) { + posix = true; + + ret = sdap_attrs_get_sid_str( + tmp_ctx, opts->idmap_ctx, ldap_groups[ai], + opts->group_map[SDAP_AT_GROUP_OBJECTSID].sys_name, + &sid_str); + if (ret != EOK && ret != ENOENT) goto done; + + if (use_id_mapping) { + if (sid_str == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No SID for group [%s] " \ + "while id-mapping.\n", + groupname); + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Mapping group [%s] objectSID to unix ID\n", groupname); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Group [%s] has objectSID [%s]\n", + groupname, sid_str); + + /* Convert the SID into a UNIX group ID */ + ret = sdap_idmap_sid_to_unix(opts->idmap_ctx, sid_str, + &gid); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Group [%s] has mapped gid [%lu]\n", + groupname, (unsigned long)gid); + } else { + posix = false; + gid = 0; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Group [%s] cannot be mapped. " + "Treating as a non-POSIX group\n", + groupname); + } + + } else { + ret = sysdb_attrs_get_uint32_t(ldap_groups[ai], + SYSDB_GIDNUM, + &gid); + if (ret == ENOENT || (ret == EOK && gid == 0)) { + DEBUG(SSSDBG_TRACE_LIBS, "The group %s gid was %s\n", + groupname, ret == ENOENT ? "missing" : "zero"); + DEBUG(SSSDBG_TRACE_FUNC, + "Marking group %s as non-POSIX and setting GID=0!\n", + groupname); + gid = 0; + posix = false; + } else if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "The GID attribute is malformed\n"); + goto done; + } + } + + ret = sysdb_attrs_get_string(ldap_groups[ai], + SYSDB_ORIG_DN, + &original_dn); + if (ret) { + DEBUG(SSSDBG_FUNC_DATA, + "The group has no original DN\n"); + original_dn = NULL; + } + + ret = sysdb_handle_original_uuid( + opts->group_map[SDAP_AT_GROUP_UUID].def_name, + ldap_groups[ai], + opts->group_map[SDAP_AT_GROUP_UUID].sys_name, + ldap_groups[ai], "uniqueIDstr"); + if (ret != EOK) { + DEBUG((ret == ENOENT) ? SSSDBG_TRACE_ALL : SSSDBG_MINOR_FAILURE, + "Failed to retrieve UUID [%d][%s].\n", + ret, sss_strerror(ret)); + } + + ret = sysdb_attrs_get_string(ldap_groups[ai], + "uniqueIDstr", + &uuid); + if (ret) { + DEBUG(SSSDBG_FUNC_DATA, + "The group has no UUID\n"); + uuid = NULL; + } + + ret = sdap_check_ad_group_type(domain, opts, ldap_groups[ai], + groupname, &need_filter); + if (ret != EOK) { + goto done; + } + + if (need_filter) { + posix = false; + gid = 0; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Adding fake group %s to sysdb\n", groupname); + subdomain = find_domain_by_object_name(domain, groupname); + if (subdomain == NULL) { + subdomain = domain; + } + ret = sysdb_add_incomplete_group(subdomain, groupname, gid, + original_dn, sid_str, + uuid, posix, now); + if (ret == ERR_GID_DUPLICATED) { + /* In case o group id-collision, do: + * - Delete the group from sysdb + * - Add the new incomplete group + * - Notify the NSS responder that the entry has also to be + * removed from the memory cache + */ + ret = sdap_handle_id_collision_for_incomplete_groups( + opts->dp, subdomain, groupname, gid, + original_dn, sid_str, uuid, posix, + now); + } + + if (ret != EOK) { + goto done; + } + break; + } + } + + if (ai == ldap_groups_count) { + DEBUG(SSSDBG_OP_FAILURE, + "Group %s not present in LDAP\n", missing[i]); + ret = EINVAL; + goto done; + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_transaction_commit failed.\n"); + goto done; + } + in_transaction = false; + ret = EOK; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +int sdap_initgr_common_store(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + const char *name, + enum sysdb_member_type type, + char **sysdb_grouplist, + struct sysdb_attrs **ldap_groups, + int ldap_groups_count) +{ + TALLOC_CTX *tmp_ctx; + char **ldap_grouplist = NULL; + char **ldap_fqdnlist = NULL; + char **add_groups; + char **del_groups; + int ret, tret; + bool in_transaction = false; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + if (ldap_groups_count == 0) { + /* No groups for this user in LDAP. + * We need to ensure that there are no groups + * in the sysdb either. + */ + ldap_grouplist = NULL; + } else { + ret = sdap_get_primary_name_list(domain, tmp_ctx, ldap_groups, + ldap_groups_count, + opts->group_map[SDAP_AT_GROUP_NAME].name, + &ldap_grouplist); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_primary_name_list failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + /* Find the differences between the sysdb and LDAP lists + * Groups in the sysdb only must be removed. + */ + if (ldap_grouplist != NULL) { + ldap_fqdnlist = sss_create_internal_fqname_list( + tmp_ctx, + (const char * const *) ldap_grouplist, + domain->name); + if (ldap_fqdnlist == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = diff_string_lists(tmp_ctx, ldap_fqdnlist, sysdb_grouplist, + &add_groups, &del_groups, NULL); + if (ret != EOK) goto done; + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + /* Add fake entries for any groups the user should be added as + * member of but that are not cached in sysdb + */ + if (add_groups && add_groups[0]) { + ret = sdap_add_incomplete_groups(sysdb, domain, opts, + add_groups, ldap_groups, + ldap_groups_count); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Adding incomplete groups failed\n"); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Updating memberships for %s\n", name); + ret = sysdb_update_members(domain, name, type, + (const char *const *) add_groups, + (const char *const *) del_groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_zfree(tmp_ctx); + return ret; +} + +/* ==Initgr-call-(groups-a-user-is-member-of)-RFC2307===================== */ + +struct sdap_initgr_rfc2307_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct sdap_options *opts; + struct sdap_handle *sh; + const char **attrs; + const char *name; + char *base_filter; + const char *orig_dn; + char *filter; + int timeout; + + struct sdap_op *op; + + struct sysdb_attrs **ldap_groups; + size_t ldap_groups_count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t sdap_initgr_rfc2307_next_base(struct tevent_req *req); +static void sdap_initgr_rfc2307_process(struct tevent_req *subreq); +struct tevent_req *sdap_initgr_rfc2307_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_handle *sh, + const char *name) +{ + struct tevent_req *req; + struct sdap_initgr_rfc2307_state *state; + const char **attr_filter; + char *clean_name; + char *shortname; + errno_t ret; + char *oc_list; + + req = tevent_req_create(memctx, &state, struct sdap_initgr_rfc2307_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sysdb = sysdb; + state->domain = domain; + state->sh = sh; + state->op = NULL; + state->timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT); + state->ldap_groups = NULL; + state->ldap_groups_count = 0; + state->base_iter = 0; + state->search_bases = opts->sdom->group_search_bases; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Initgroups lookup request without a group search base\n"); + ret = EINVAL; + goto done; + } + + state->name = talloc_strdup(state, name); + if (!state->name) { + talloc_zfree(req); + return NULL; + } + + attr_filter = talloc_array(state, const char *, 2); + if (!attr_filter) { + talloc_free(req); + return NULL; + } + + attr_filter[0] = opts->group_map[SDAP_AT_GROUP_MEMBER].name; + attr_filter[1] = NULL; + + ret = build_attrs_from_map(state, opts->group_map, SDAP_OPTS_GROUP, + attr_filter, &state->attrs, NULL); + if (ret != EOK) { + talloc_free(req); + return NULL; + } + + ret = sss_parse_internal_fqname(state, name, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot parse %s\n", name); + goto done; + } + + ret = sss_filter_sanitize(state, shortname, &clean_name); + if (ret != EOK) { + talloc_free(req); + return NULL; + } + + oc_list = sdap_make_oc_list(state, opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto done; + } + + state->base_filter = talloc_asprintf(state, + "(&(%s=%s)(%s)(%s=*)", + opts->group_map[SDAP_AT_GROUP_MEMBER].name, + clean_name, oc_list, + opts->group_map[SDAP_AT_GROUP_NAME].name); + if (!state->base_filter) { + talloc_zfree(req); + return NULL; + } + talloc_zfree(clean_name); + + switch (domain->type) { + case DOM_TYPE_APPLICATION: + state->base_filter = talloc_asprintf_append(state->base_filter, ")"); + break; + case DOM_TYPE_POSIX: + state->base_filter = talloc_asprintf_append(state->base_filter, + "(&(%s=*)(!(%s=0))))", + opts->group_map[SDAP_AT_GROUP_GID].name, + opts->group_map[SDAP_AT_GROUP_GID].name); + break; + } + if (!state->base_filter) { + ret = ENOMEM; + goto done; + } + + ret = sdap_initgr_rfc2307_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t sdap_initgr_rfc2307_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_initgr_rfc2307_state *state; + + state = tevent_req_data(req, struct sdap_initgr_rfc2307_state); + + talloc_zfree(state->filter); + + state->filter = sdap_combine_filters( state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for groups with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->group_map, SDAP_OPTS_GROUP, + state->timeout, + true); + if (!subreq) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_initgr_rfc2307_process, req); + + return EOK; +} + +static void sdap_initgr_rfc2307_process(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_initgr_rfc2307_state *state; + struct sysdb_attrs **ldap_groups; + char **sysdb_grouplist = NULL; + size_t count; + int ret; + int i; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_initgr_rfc2307_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &ldap_groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + /* Add this batch of groups to the list */ + if (count > 0) { + state->ldap_groups = + talloc_realloc(state, + state->ldap_groups, + struct sysdb_attrs *, + state->ldap_groups_count + count + 1); + if (!state->ldap_groups) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Copy the new groups into the list. + */ + for (i = 0; i < count; i++) { + state->ldap_groups[state->ldap_groups_count + i] = + talloc_steal(state->ldap_groups, ldap_groups[i]); + } + + state->ldap_groups_count += count; + + state->ldap_groups[state->ldap_groups_count] = NULL; + } + + state->base_iter++; + + /* Check for additional search bases, and iterate + * through again. + */ + if (state->search_bases[state->base_iter] != NULL) { + ret = sdap_initgr_rfc2307_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + /* Search for all groups for which this user is a member */ + ret = get_sysdb_grouplist(state, state->sysdb, state->domain, + state->name, &sysdb_grouplist); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* There are no nested groups here so we can just update the + * memberships */ + ret = sdap_initgr_common_store(state->sysdb, + state->domain, + state->opts, + state->name, + SYSDB_MEMBER_USER, + sysdb_grouplist, + state->ldap_groups, + state->ldap_groups_count); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int sdap_initgr_rfc2307_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Common code for pure RFC2307bis and IPA/AD========================= */ +errno_t +sdap_nested_groups_store(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs **groups, + unsigned long count) +{ + errno_t ret, tret; + TALLOC_CTX *tmp_ctx; + char **groupnamelist = NULL; + bool in_transaction = false; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + if (count > 0) { + ret = sdap_get_primary_fqdn_list(domain, tmp_ctx, groups, count, + opts->group_map[SDAP_AT_GROUP_NAME].name, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + opts->idmap_ctx, + &groupnamelist); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_attrs_primary_name_list failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = sdap_add_incomplete_groups(sysdb, domain, opts, groupnamelist, + groups, count); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Could not add incomplete groups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + + talloc_free(tmp_ctx); + return ret; +} + +struct membership_diff { + struct membership_diff *prev; + struct membership_diff *next; + + const char *name; + char **add; + char **del; +}; + +static errno_t +build_membership_diff(TALLOC_CTX *mem_ctx, const char *name, + char **ldap_parent_names, char **sysdb_parent_names, + struct membership_diff **_mdiff) +{ + TALLOC_CTX *tmp_ctx; + struct membership_diff *mdiff; + errno_t ret; + char **add_groups; + char **del_groups; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + mdiff = talloc_zero(tmp_ctx, struct membership_diff); + if (!mdiff) { + ret = ENOMEM; + goto done; + } + mdiff->name = talloc_strdup(mdiff, name); + if (!mdiff->name) { + ret = ENOMEM; + goto done; + } + + /* Find the differences between the sysdb and ldap lists + * Groups in ldap only must be added to the sysdb; + * groups in the sysdb only must be removed. + */ + ret = diff_string_lists(tmp_ctx, + ldap_parent_names, sysdb_parent_names, + &add_groups, &del_groups, NULL); + if (ret != EOK) { + goto done; + } + mdiff->add = talloc_steal(mdiff, add_groups); + mdiff->del = talloc_steal(mdiff, del_groups); + + ret = EOK; + *_mdiff = talloc_steal(mem_ctx, mdiff); +done: + talloc_free(tmp_ctx); + return ret; +} + +/* ==Initgr-call-(groups-a-user-is-member-of)-nested-groups=============== */ + +struct sdap_initgr_nested_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + struct sdap_handle *sh; + + struct sysdb_attrs *user; + const char *username; + const char *orig_dn; + + const char **grp_attrs; + + struct ldb_message_element *memberof; + char *filter; + char **group_dns; + int cur; + + struct sdap_op *op; + + struct sysdb_attrs **groups; + int groups_cur; +}; + +static errno_t sdap_initgr_nested_deref_search(struct tevent_req *req); +static errno_t sdap_initgr_nested_noderef_search(struct tevent_req *req); +static void sdap_initgr_nested_search(struct tevent_req *subreq); +static void sdap_initgr_nested_store(struct tevent_req *req); +static struct tevent_req *sdap_initgr_nested_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_handle *sh, + struct sysdb_attrs *user, + const char **grp_attrs) +{ + struct tevent_req *req; + struct sdap_initgr_nested_state *state; + errno_t ret; + int deref_threshold; + + req = tevent_req_create(memctx, &state, struct sdap_initgr_nested_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sysdb = sysdb; + state->dom = dom; + state->sh = sh; + state->grp_attrs = grp_attrs; + state->user = user; + state->op = NULL; + + ret = sdap_get_user_primary_name(memctx, opts, user, dom, &state->username); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "User entry had no username\n"); + goto immediate; + } + + ret = sysdb_attrs_get_el(state->user, SYSDB_MEMBEROF, &state->memberof); + if (ret || !state->memberof || state->memberof->num_values == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "User entry lacks original memberof ?\n"); + /* We can't find any groups for this user, so we'll + * have to assume there aren't any. Just return + * success here. + */ + ret = EOK; + goto immediate; + } + + state->groups = talloc_zero_array(state, struct sysdb_attrs *, + state->memberof->num_values + 1); + if (!state->groups) { + ret = ENOMEM; + goto immediate; + } + state->groups_cur = 0; + + deref_threshold = dp_opt_get_int(state->opts->basic, + SDAP_DEREF_THRESHOLD); + if (sdap_has_deref_support(state->sh, state->opts) && + deref_threshold < state->memberof->num_values) { + ret = sysdb_attrs_get_string(user, SYSDB_ORIG_DN, + &state->orig_dn); + if (ret != EOK) goto immediate; + + ret = sdap_initgr_nested_deref_search(req); + if (ret != EAGAIN) goto immediate; + } else { + ret = sdap_initgr_nested_noderef_search(req); + if (ret != EAGAIN) goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t sdap_initgr_nested_noderef_search(struct tevent_req *req) +{ + int i; + struct tevent_req *subreq; + struct sdap_initgr_nested_state *state; + char *oc_list; + + state = tevent_req_data(req, struct sdap_initgr_nested_state); + + state->group_dns = talloc_array(state, char *, + state->memberof->num_values + 1); + if (!state->group_dns) { + return ENOMEM; + } + for (i = 0; i < state->memberof->num_values; i++) { + state->group_dns[i] = talloc_strdup(state->group_dns, + (char *)state->memberof->values[i].data); + if (!state->group_dns[i]) { + return ENOMEM; + } + } + state->group_dns[i] = NULL; /* terminate */ + state->cur = 0; + + oc_list = sdap_make_oc_list(state, state->opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + return ENOMEM; + } + + state->filter = talloc_asprintf(state, "(&(%s)(%s=*))", oc_list, + state->opts->group_map[SDAP_AT_GROUP_NAME].name); + if (!state->filter) { + return ENOMEM; + } + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + state->group_dns[state->cur], + LDAP_SCOPE_BASE, + state->filter, state->grp_attrs, + state->opts->group_map, SDAP_OPTS_GROUP, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_initgr_nested_search, req); + + return EAGAIN; +} + +static void sdap_initgr_nested_deref_done(struct tevent_req *subreq); + +static errno_t sdap_initgr_nested_deref_search(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_attr_map_info *maps; + const int num_maps = 1; + const char **sdap_attrs; + errno_t ret; + int timeout; + struct sdap_initgr_nested_state *state; + + state = tevent_req_data(req, struct sdap_initgr_nested_state); + + maps = talloc_array(state, struct sdap_attr_map_info, num_maps+1); + if (!maps) return ENOMEM; + + maps[0].map = state->opts->group_map; + maps[0].num_attrs = SDAP_OPTS_GROUP; + maps[1].map = NULL; + + ret = build_attrs_from_map(state, state->opts->group_map, SDAP_OPTS_GROUP, + NULL, &sdap_attrs, NULL); + if (ret != EOK) goto fail; + + timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT); + + subreq = sdap_deref_search_send(state, state->ev, state->opts, + state->sh, state->orig_dn, + state->opts->user_map[SDAP_AT_USER_MEMBEROF].name, + sdap_attrs, num_maps, maps, timeout); + if (!subreq) { + ret = EIO; + goto fail; + } + talloc_steal(subreq, sdap_attrs); + talloc_steal(subreq, maps); + + tevent_req_set_callback(subreq, sdap_initgr_nested_deref_done, req); + return EAGAIN; + +fail: + talloc_free(sdap_attrs); + talloc_free(maps); + return ret; +} + +static void sdap_initgr_nested_deref_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_initgr_nested_state *state; + size_t num_results; + size_t i; + struct sdap_deref_attrs **deref_result; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_initgr_nested_state); + + ret = sdap_deref_search_recv(subreq, state, + &num_results, + &deref_result); + talloc_zfree(subreq); + if (ret == ENOTSUP) { + ret = sdap_initgr_nested_noderef_search(req); + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + } + return; + } else if (ret != EOK && ret != ENOENT) { + tevent_req_error(req, ret); + return; + } else if (ret == ENOENT || deref_result == NULL) { + /* Nothing could be dereferenced. Done. */ + tevent_req_done(req); + return; + } + + for (i=0; i < num_results; i++) { + state->groups[i] = talloc_steal(state->groups, + deref_result[i]->attrs); + } + + state->groups_cur = num_results; + sdap_initgr_nested_store(req); +} + +static void sdap_initgr_nested_search(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_initgr_nested_state *state; + struct sysdb_attrs **groups; + size_t count; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_initgr_nested_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (count == 1) { + state->groups[state->groups_cur] = talloc_steal(state->groups, + groups[0]); + state->groups_cur++; + } else if (count == 0) { + /* this might be HBAC or sudo rule */ + DEBUG(SSSDBG_FUNC_DATA, "Object %s not found. Skipping\n", + state->group_dns[state->cur]); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Search for group %s, returned %zu results. Skipping\n", + state->group_dns[state->cur], count); + } + + state->cur++; + /* note that state->memberof->num_values is the count of original + * memberOf which might not be only groups, but permissions, etc. + * Use state->groups_cur for group index cap */ + if (state->cur < state->memberof->num_values) { + subreq = sdap_get_generic_send(state, state->ev, + state->opts, state->sh, + state->group_dns[state->cur], + LDAP_SCOPE_BASE, + state->filter, state->grp_attrs, + state->opts->group_map, + SDAP_OPTS_GROUP, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_initgr_nested_search, req); + } else { + sdap_initgr_nested_store(req); + } +} + +static errno_t +sdap_initgr_store_groups(struct sdap_initgr_nested_state *state); +static errno_t +sdap_initgr_store_group_memberships(struct sdap_initgr_nested_state *state); +static errno_t +sdap_initgr_store_user_memberships(struct sdap_initgr_nested_state *state); + +static void sdap_initgr_nested_store(struct tevent_req *req) +{ + errno_t ret; + struct sdap_initgr_nested_state *state; + bool in_transaction = false; + errno_t tret; + + state = tevent_req_data(req, struct sdap_initgr_nested_state); + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + /* save the groups if they are not already */ + ret = sdap_initgr_store_groups(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + /* save the group memberships */ + ret = sdap_initgr_store_group_memberships(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not save group memberships [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + /* save the user memberships */ + ret = sdap_initgr_store_user_memberships(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not save user memberships [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + + tevent_req_done(req); + return; + +fail: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + tevent_req_error(req, ret); + return; +} + +static errno_t +sdap_initgr_store_groups(struct sdap_initgr_nested_state *state) +{ + return sdap_nested_groups_store(state->sysdb, state->dom, + state->opts, state->groups, + state->groups_cur); +} + +static errno_t +sdap_initgr_nested_get_membership_diff(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *group, + struct sysdb_attrs **all_groups, + int groups_count, + struct membership_diff **mdiff); + +static int sdap_initgr_nested_get_direct_parents(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *attrs, + struct sysdb_attrs **groups, + int ngroups, + struct sysdb_attrs ***_direct_parents, + int *_ndirect); + +static errno_t +sdap_initgr_store_group_memberships(struct sdap_initgr_nested_state *state) +{ + errno_t ret; + int i, tret; + TALLOC_CTX *tmp_ctx; + struct membership_diff *miter = NULL; + struct membership_diff *memberships = NULL; + bool in_transaction = false; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + /* Compute the diffs first in order to keep the transaction as small + * as possible + */ + for (i=0; i < state->groups_cur; i++) { + ret = sdap_initgr_nested_get_membership_diff(tmp_ctx, state->sysdb, + state->opts, state->dom, + state->groups[i], + state->groups, + state->groups_cur, + &miter); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not compute memberships for group %d [%d]: %s\n", + i, ret, strerror(ret)); + goto done; + } + + DLIST_ADD(memberships, miter); + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + DLIST_FOR_EACH(miter, memberships) { + ret = sysdb_update_members(state->dom, miter->name, + SYSDB_MEMBER_GROUP, + (const char *const *) miter->add, + (const char *const *) miter->del); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to update memberships\n"); + goto done; + } + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sdap_initgr_store_user_memberships(struct sdap_initgr_nested_state *state) +{ + errno_t ret; + int tret; + const char *orig_dn; + + char **sysdb_parent_name_list = NULL; + char **ldap_parent_name_list = NULL; + char **ldap_fqdnlist = NULL; + + int nparents; + struct sysdb_attrs **ldap_parentlist; + struct ldb_message_element *el; + int i, mi; + char **add_groups; + char **del_groups; + TALLOC_CTX *tmp_ctx; + bool in_transaction = false; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + /* Get direct LDAP parents */ + ret = sysdb_attrs_get_string(state->user, SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "The user has no original DN\n"); + goto done; + } + + ldap_parentlist = talloc_zero_array(tmp_ctx, struct sysdb_attrs *, + state->groups_cur + 1); + if (!ldap_parentlist) { + ret = ENOMEM; + goto done; + } + nparents = 0; + + for (i=0; i < state->groups_cur ; i++) { + ret = sysdb_attrs_get_el(state->groups[i], SYSDB_MEMBER, &el); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "A group with no members during initgroups?\n"); + goto done; + } + + for (mi = 0; mi < el->num_values; mi++) { + if (strcasecmp((const char *) el->values[mi].data, orig_dn) != 0) { + continue; + } + + ldap_parentlist[nparents] = state->groups[i]; + nparents++; + } + } + + DEBUG(SSSDBG_TRACE_LIBS, + "The user %s is a direct member of %d LDAP groups\n", + state->username, nparents); + + if (nparents == 0) { + ldap_parent_name_list = NULL; + } else { + ret = sdap_get_primary_name_list(state->dom, tmp_ctx, ldap_parentlist, + nparents, + state->opts->group_map[SDAP_AT_GROUP_NAME].name, + &ldap_parent_name_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_primary_name_list failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + if (ldap_parent_name_list) { + ldap_fqdnlist = sss_create_internal_fqname_list( + tmp_ctx, + (const char * const *) ldap_parent_name_list, + state->dom->name); + if (ldap_fqdnlist == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = sysdb_get_direct_parents(tmp_ctx, state->dom, state->dom, + SYSDB_MEMBER_USER, + state->username, &sysdb_parent_name_list); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not get direct sysdb parents for %s: %d [%s]\n", + state->username, ret, strerror(ret)); + goto done; + } + + ret = diff_string_lists(tmp_ctx, + ldap_fqdnlist, sysdb_parent_name_list, + &add_groups, &del_groups, NULL); + if (ret != EOK) { + goto done; + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Updating memberships for %s\n", state->username); + ret = sysdb_update_members(state->dom, state->username, SYSDB_MEMBER_USER, + (const char *const *) add_groups, + (const char *const *) del_groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not update sysdb memberships for %s: %d [%s]\n", + state->username, ret, strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_zfree(tmp_ctx); + return ret; +} + +static errno_t +sdap_initgr_nested_get_membership_diff(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *group, + struct sysdb_attrs **all_groups, + int groups_count, + struct membership_diff **_mdiff) +{ + errno_t ret; + struct membership_diff *mdiff; + const char *group_name; + + struct sysdb_attrs **ldap_parentlist; + int parents_count; + + char **ldap_parent_names_list = NULL; + char **sysdb_parents_names_list = NULL; + + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + /* Get direct sysdb parents */ + ret = sdap_get_group_primary_name(tmp_ctx, opts, group, dom, &group_name); + if (ret != EOK) { + goto done; + } + + ret = sysdb_get_direct_parents(tmp_ctx, dom, dom, SYSDB_MEMBER_GROUP, + group_name, &sysdb_parents_names_list); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not get direct sysdb parents for %s: %d [%s]\n", + group_name, ret, strerror(ret)); + goto done; + } + + /* For each group, filter only parents from full set */ + ret = sdap_initgr_nested_get_direct_parents(tmp_ctx, + group, + all_groups, + groups_count, + &ldap_parentlist, + &parents_count); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get parent groups for %s [%d]: %s\n", + group_name, ret, strerror(ret)); + goto done; + } + DEBUG(SSSDBG_TRACE_LIBS, + "The group %s is a direct member of %d LDAP groups\n", + group_name, parents_count); + + if (parents_count > 0) { + ret = sdap_get_primary_fqdn_list(dom, tmp_ctx, ldap_parentlist, + parents_count, + opts->group_map[SDAP_AT_GROUP_NAME].name, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + opts->idmap_ctx, + &ldap_parent_names_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_primary_name_list failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + ret = build_membership_diff(tmp_ctx, group_name, ldap_parent_names_list, + sysdb_parents_names_list, &mdiff); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not build membership diff for %s [%d]: %s\n", + group_name, ret, strerror(ret)); + goto done; + } + + ret = EOK; + *_mdiff = talloc_steal(mem_ctx, mdiff); +done: + talloc_free(tmp_ctx); + return ret; +} + +static int sdap_initgr_nested_get_direct_parents(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *attrs, + struct sysdb_attrs **groups, + int ngroups, + struct sysdb_attrs ***_direct_parents, + int *_ndirect) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *member; + int i, mi; + int ret; + const char *orig_dn; + + int ndirect; + struct sysdb_attrs **direct_groups; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + direct_groups = talloc_zero_array(tmp_ctx, struct sysdb_attrs *, + ngroups + 1); + if (!direct_groups) { + ret = ENOMEM; + goto done; + } + ndirect = 0; + + ret = sysdb_attrs_get_string(attrs, SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Missing originalDN\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, + "Looking up direct parents for group [%s]\n", orig_dn); + + /* FIXME - Filter only parents from full set to avoid searching + * through all members of huge groups. That requires asking for memberOf + * with the group LDAP search + */ + + /* Filter only direct parents from the list of all groups */ + for (i=0; i < ngroups; i++) { + ret = sysdb_attrs_get_el(groups[i], SYSDB_MEMBER, &member); + if (ret) { + DEBUG(SSSDBG_TRACE_LIBS, + "A group with no members during initgroups?\n"); + continue; + } + + for (mi = 0; mi < member->num_values; mi++) { + if (strcasecmp((const char *) member->values[mi].data, orig_dn) != 0) { + continue; + } + + direct_groups[ndirect] = groups[i]; + ndirect++; + } + } + direct_groups[ndirect] = NULL; + + DEBUG(SSSDBG_TRACE_ALL, + "The group [%s] has %d direct parents\n", orig_dn, ndirect); + + *_direct_parents = talloc_steal(mem_ctx, direct_groups); + *_ndirect = ndirect; + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + +static int sdap_initgr_nested_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Initgr-call-(groups-a-user-is-member-of)-RFC2307-BIS================= */ +struct sdap_initgr_rfc2307bis_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + struct sdap_handle *sh; + const char *name; + char *base_filter; + char *filter; + const char **attrs; + const char *orig_dn; + + int timeout; + + size_t base_iter; + struct sdap_search_base **search_bases; + + struct sdap_op *op; + + hash_table_t *group_hash; + size_t num_direct_parents; + struct sysdb_attrs **direct_groups; +}; + +struct sdap_nested_group { + struct sysdb_attrs *group; + struct sysdb_attrs **ldap_parents; + size_t parents_count; +}; + +static errno_t sdap_initgr_rfc2307bis_next_base(struct tevent_req *req); +static void sdap_initgr_rfc2307bis_process(struct tevent_req *subreq); +static void sdap_initgr_rfc2307bis_done(struct tevent_req *subreq); +errno_t save_rfc2307bis_user_memberships( + struct sdap_initgr_rfc2307bis_state *state); + +static struct tevent_req *sdap_initgr_rfc2307bis_send( + TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_domain *sdom, + struct sdap_handle *sh, + const char *name, + const char *orig_dn) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_initgr_rfc2307bis_state *state; + const char **attr_filter; + char *clean_orig_dn; + bool use_id_mapping; + char *oc_list; + + req = tevent_req_create(memctx, &state, struct sdap_initgr_rfc2307bis_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sysdb = sdom->dom->sysdb; + state->dom = sdom->dom; + state->sh = sh; + state->op = NULL; + state->name = name; + state->direct_groups = NULL; + state->num_direct_parents = 0; + state->timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT); + state->base_iter = 0; + state->search_bases = sdom->group_search_bases; + state->orig_dn = orig_dn; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Initgroups lookup request without a group search base\n"); + ret = EINVAL; + goto done; + } + + ret = sss_hash_create(state, 0, &state->group_hash); + if (ret != EOK) { + talloc_free(req); + return NULL; + } + + attr_filter = talloc_array(state, const char *, 2); + if (!attr_filter) { + ret = ENOMEM; + goto done; + } + + attr_filter[0] = opts->group_map[SDAP_AT_GROUP_MEMBER].name; + attr_filter[1] = NULL; + + ret = build_attrs_from_map(state, opts->group_map, SDAP_OPTS_GROUP, + attr_filter, &state->attrs, NULL); + if (ret != EOK) goto done; + + ret = sss_filter_sanitize_dn(state, orig_dn, &clean_orig_dn); + if (ret != EOK) goto done; + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + + oc_list = sdap_make_oc_list(state, opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto done; + } + + state->base_filter = + talloc_asprintf(state, + "(&(%s=%s)(%s)(%s=*)", + opts->group_map[SDAP_AT_GROUP_MEMBER].name, + clean_orig_dn, oc_list, + opts->group_map[SDAP_AT_GROUP_NAME].name); + if (!state->base_filter) { + ret = ENOMEM; + goto done; + } + + if (use_id_mapping) { + /* When mapping IDs or looking for SIDs, we don't want to limit + * ourselves to groups with a GID value. But there must be a SID to map + * from. + */ + state->base_filter = talloc_asprintf_append(state->base_filter, + "(%s=*))", + opts->group_map[SDAP_AT_GROUP_OBJECTSID].name); + } else { + state->base_filter = talloc_asprintf_append(state->base_filter, ")"); + } + if (!state->base_filter) { + talloc_zfree(req); + return NULL; + } + + + talloc_zfree(clean_orig_dn); + + ret = sdap_initgr_rfc2307bis_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static errno_t sdap_initgr_rfc2307bis_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_initgr_rfc2307bis_state *state; + + state = tevent_req_data(req, struct sdap_initgr_rfc2307bis_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for parent groups for user [%s] with base [%s]\n", + state->orig_dn, state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->group_map, SDAP_OPTS_GROUP, + state->timeout, + true); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_initgr_rfc2307bis_process, req); + + return EOK; +} + +static void sdap_initgr_rfc2307bis_process(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_initgr_rfc2307bis_state *state; + struct sysdb_attrs **ldap_groups; + size_t count; + size_t i; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_initgr_rfc2307bis_state); + + ret = sdap_get_generic_recv(subreq, state, + &count, + &ldap_groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + DEBUG(SSSDBG_TRACE_LIBS, + "Found %zu parent groups for user [%s]\n", count, state->name); + + /* Add this batch of groups to the list */ + if (count > 0) { + state->direct_groups = + talloc_realloc(state, + state->direct_groups, + struct sysdb_attrs *, + state->num_direct_parents + count + 1); + if (!state->direct_groups) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Copy the new groups into the list. + */ + for (i = 0; i < count; i++) { + state->direct_groups[state->num_direct_parents + i] = + talloc_steal(state->direct_groups, ldap_groups[i]); + } + + state->num_direct_parents += count; + + state->direct_groups[state->num_direct_parents] = NULL; + } + + state->base_iter++; + + /* Check for additional search bases, and iterate + * through again. + */ + if (state->search_bases[state->base_iter] != NULL) { + ret = sdap_initgr_rfc2307bis_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + if (state->num_direct_parents == 0) { + /* Start a transaction to look up the groups in the sysdb + * and update them with LDAP data + */ + ret = save_rfc2307bis_user_memberships(state); + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + return; + } + + subreq = rfc2307bis_nested_groups_send(state, state->ev, state->opts, + state->sysdb, state->dom, + state->sh, + state->search_bases, + state->direct_groups, + state->num_direct_parents, + state->group_hash, 0); + if (!subreq) { + tevent_req_error(req, EIO); + return; + } + tevent_req_set_callback(subreq, sdap_initgr_rfc2307bis_done, req); +} + +static errno_t +save_rfc2307bis_groups(struct sdap_initgr_rfc2307bis_state *state); +static errno_t +save_rfc2307bis_group_memberships(struct sdap_initgr_rfc2307bis_state *state); + +static void sdap_initgr_rfc2307bis_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_initgr_rfc2307bis_state *state = + tevent_req_data(req, struct sdap_initgr_rfc2307bis_state); + bool in_transaction = false; + errno_t tret; + + ret = rfc2307bis_nested_groups_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + /* save the groups if they are not cached */ + ret = save_rfc2307bis_groups(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not save groups memberships [%d]\n", ret); + goto fail; + } + + /* save the group membership */ + ret = save_rfc2307bis_group_memberships(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not save group memberships [%d]\n", ret); + goto fail; + } + + /* save the user memberships */ + ret = save_rfc2307bis_user_memberships(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not save user memberships [%d]\n", ret); + goto fail; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + + tevent_req_done(req); + return; + +fail: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + tevent_req_error(req, ret); + return; +} + +static int sdap_initgr_rfc2307bis_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct rfc2307bis_group_memberships_state { + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + + hash_table_t *group_hash; + + struct membership_diff *memberships; + + int ret; +}; + +static errno_t +save_rfc2307bis_groups(struct sdap_initgr_rfc2307bis_state *state) +{ + struct sysdb_attrs **groups = NULL; + unsigned long count; + hash_value_t *values; + int hret, i; + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct sdap_nested_group *gr; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + hret = hash_values(state->group_hash, &count, &values); + if (hret != HASH_SUCCESS) { + ret = EIO; + goto done; + } + + groups = talloc_array(tmp_ctx, struct sysdb_attrs *, count); + if (!groups) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < count; i++) { + gr = talloc_get_type(values[i].ptr, + struct sdap_nested_group); + groups[i] = gr->group; + } + talloc_zfree(values); + + ret = sdap_nested_groups_store(state->sysdb, state->dom, state->opts, + groups, count); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static bool rfc2307bis_group_memberships_build(hash_entry_t *item, void *user_data); + +static errno_t +save_rfc2307bis_group_memberships(struct sdap_initgr_rfc2307bis_state *state) +{ + errno_t ret, tret; + int hret; + TALLOC_CTX *tmp_ctx; + struct rfc2307bis_group_memberships_state *membership_state; + struct membership_diff *iter; + struct membership_diff *iter_start; + struct membership_diff *iter_tmp; + bool in_transaction = false; + int num_added; + int i; + int grp_count; + char **add = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + membership_state = talloc_zero(tmp_ctx, + struct rfc2307bis_group_memberships_state); + if (!membership_state) { + ret = ENOMEM; + goto done; + } + + membership_state->sysdb = state->sysdb; + membership_state->dom = state->dom; + membership_state->opts = state->opts; + membership_state->group_hash = state->group_hash; + + hret = hash_iterate(state->group_hash, + rfc2307bis_group_memberships_build, + membership_state); + if (hret != HASH_SUCCESS) { + ret = membership_state->ret; + goto done; + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + iter_start = membership_state->memberships; + + DLIST_FOR_EACH(iter, membership_state->memberships) { + /* Create a copy of iter->add array but do not include groups outside + * nesting limit. This array must be NULL terminated. + */ + for (grp_count = 0; iter->add[grp_count]; grp_count++); + add = talloc_zero_array(tmp_ctx, char *, grp_count + 1); + if (add == NULL) { + ret = ENOMEM; + goto done; + } + + num_added = 0; + for (i = 0; i < grp_count; i++) { + DLIST_FOR_EACH(iter_tmp, iter_start) { + if (!strcmp(iter_tmp->name,iter->add[i])) { + add[num_added] = iter->add[i]; + num_added++; + break; + } + } + } + + if (num_added == 0) { + add = NULL; + } else { + add[num_added] = NULL; + } + ret = sysdb_update_members(state->dom, iter->name, + SYSDB_MEMBER_GROUP, + (const char *const *) add, + (const char *const *) iter->del); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to update memberships\n"); + goto done; + } + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static bool +rfc2307bis_group_memberships_build(hash_entry_t *item, void *user_data) +{ + struct rfc2307bis_group_memberships_state *mstate = talloc_get_type( + user_data, struct rfc2307bis_group_memberships_state); + struct sdap_nested_group *group; + char *group_name; + TALLOC_CTX *tmp_ctx; + errno_t ret; + char **sysdb_parents_names_list; + char **ldap_parents_names_list = NULL; + + struct membership_diff *mdiff; + + group_name = (char *) item->key.str; + group = (struct sdap_nested_group *) item->value.ptr; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_get_direct_parents(tmp_ctx, mstate->dom, mstate->dom, + SYSDB_MEMBER_GROUP, + group_name, &sysdb_parents_names_list); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not get direct sysdb parents for %s: %d [%s]\n", + group_name, ret, strerror(ret)); + goto done; + } + + if (group->parents_count > 0) { + ret = sdap_get_primary_fqdn_list(mstate->dom, tmp_ctx, + group->ldap_parents, group->parents_count, + mstate->opts->group_map[SDAP_AT_GROUP_NAME].name, + mstate->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + mstate->opts->idmap_ctx, + &ldap_parents_names_list); + if (ret != EOK) { + goto done; + } + } + + ret = build_membership_diff(tmp_ctx, group_name, ldap_parents_names_list, + sysdb_parents_names_list, &mdiff); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not build membership diff for %s [%d]: %s\n", + group_name, ret, strerror(ret)); + goto done; + } + + talloc_steal(mstate, mdiff); + DLIST_ADD(mstate->memberships, mdiff); + ret = EOK; +done: + talloc_free(tmp_ctx); + mstate->ret = ret; + return ret == EOK ? true : false; +} + +errno_t save_rfc2307bis_user_memberships( + struct sdap_initgr_rfc2307bis_state *state) +{ + errno_t ret, tret; + char **ldap_grouplist; + char **sysdb_parent_name_list; + char **add_groups; + char **del_groups; + bool in_transaction = false; + + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + if(!tmp_ctx) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Save parent groups to sysdb\n"); + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto error; + } + in_transaction = true; + + ret = sysdb_get_direct_parents(tmp_ctx, state->dom, state->dom, + SYSDB_MEMBER_USER, + state->name, &sysdb_parent_name_list); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not get direct sysdb parents for %s: %d [%s]\n", + state->name, ret, strerror(ret)); + goto error; + } + + if (state->num_direct_parents == 0) { + ldap_grouplist = NULL; + } + else { + ret = sdap_get_primary_fqdn_list(state->dom, tmp_ctx, + state->direct_groups, state->num_direct_parents, + state->opts->group_map[SDAP_AT_GROUP_NAME].name, + state->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + state->opts->idmap_ctx, + &ldap_grouplist); + if (ret != EOK) { + goto error; + } + } + + /* Find the differences between the sysdb and ldap lists + * Groups in ldap only must be added to the sysdb; + * groups in the sysdb only must be removed. + */ + ret = diff_string_lists(tmp_ctx, + ldap_grouplist, sysdb_parent_name_list, + &add_groups, &del_groups, NULL); + if (ret != EOK) { + goto error; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Updating memberships for %s\n", state->name); + ret = sysdb_update_members(state->dom, state->name, SYSDB_MEMBER_USER, + (const char *const *)add_groups, + (const char *const *)del_groups); + if (ret != EOK) { + goto error; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto error; + } + in_transaction = false; + + talloc_free(tmp_ctx); + return EOK; + +error: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +struct sdap_rfc2307bis_nested_ctx { + struct tevent_context *ev; + struct sdap_options *opts; + struct sysdb_ctx *sysdb; + struct sss_domain_info *dom; + struct sdap_handle *sh; + int timeout; + const char *base_filter; + char *filter; + const char *orig_dn; + const char **attrs; + struct sysdb_attrs **groups; + size_t num_groups; + + size_t nesting_level; + + size_t group_iter; + struct sdap_nested_group **processed_groups; + + hash_table_t *group_hash; + const char *primary_name; + + struct sysdb_handle *handle; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t rfc2307bis_nested_groups_step(struct tevent_req *req); +struct tevent_req *rfc2307bis_nested_groups_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct sdap_options *opts, struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, struct sdap_handle *sh, + struct sdap_search_base **search_bases, + struct sysdb_attrs **groups, size_t num_groups, + hash_table_t *group_hash, size_t nesting) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_rfc2307bis_nested_ctx *state; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "About to process %zu groups in nesting level %zu\n", + num_groups, nesting); + + req = tevent_req_create(mem_ctx, &state, + struct sdap_rfc2307bis_nested_ctx); + if (!req) return NULL; + + if ((num_groups == 0) || + (nesting > dp_opt_get_int(opts->basic, SDAP_NESTING_LEVEL))) { + /* No parent groups to process or too deep*/ + ret = EOK; + goto done; + } + + state->ev = ev; + state->opts = opts; + state->sysdb = sysdb; + state->dom = dom; + state->sh = sh; + state->groups = groups; + state->num_groups = num_groups; + state->group_iter = 0; + state->nesting_level = nesting; + state->group_hash = group_hash; + state->filter = NULL; + state->timeout = dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT); + state->base_iter = 0; + state->search_bases = search_bases; + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Initgroups nested lookup request " + "without a group search base\n"); + ret = EINVAL; + goto done; + } + + state->processed_groups = talloc_array(state, + struct sdap_nested_group *, + state->num_groups); + if (state->processed_groups == NULL) { + ret = ENOMEM; + goto done; + } + + while (state->group_iter < state->num_groups) { + ret = rfc2307bis_nested_groups_step(req); + if (ret == EOK) { + /* This group had already been looked up. Continue to + * another group in the same level + */ + state->group_iter++; + continue; + } else { + goto done; + } + } + + ret = EOK; + +done: + if (ret == EOK) { + /* All parent groups were already processed */ + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + /* EAGAIN means a lookup is in progress */ + return req; +} + +static errno_t rfc2307bis_nested_groups_next_base(struct tevent_req *req); +static void rfc2307bis_nested_groups_process(struct tevent_req *subreq); +static errno_t rfc2307bis_nested_groups_step(struct tevent_req *req) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + const char **attr_filter; + char *clean_orig_dn; + hash_key_t key; + hash_value_t value; + struct sdap_rfc2307bis_nested_ctx *state = + tevent_req_data(req, struct sdap_rfc2307bis_nested_ctx); + char *oc_list; + const char *class; + + tmp_ctx = talloc_new(state); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(state->groups[state->group_iter], + SYSDB_OBJECTCATEGORY, &class); + if (ret == EOK) { + /* If there is a objectClass attribute the object is coming from the + * cache and the name attribute of the object already has the primary + * name. + * If the objectClass attribute is missing the object is coming from + * LDAP and we have to find the primary name first. */ + ret = sysdb_attrs_get_string(state->groups[state->group_iter], + SYSDB_NAME, &state->primary_name); + } else { + ret = sdap_get_group_primary_name(state, state->opts, + state->groups[state->group_iter], + state->dom, &state->primary_name); + } + if (ret != EOK) { + goto done; + } + + key.type = HASH_KEY_STRING; + key.str = talloc_strdup(state, state->primary_name); + if (!key.str) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Processing group [%s]\n", state->primary_name); + + ret = hash_lookup(state->group_hash, &key, &value); + if (ret == HASH_SUCCESS) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Group [%s] was already processed, " + "taking a shortcut\n", state->primary_name); + state->processed_groups[state->group_iter] = + talloc_get_type(value.ptr, struct sdap_nested_group); + talloc_free(key.str); + ret = EOK; + goto done; + } + + /* Need to try to find parent groups for this group. */ + state->processed_groups[state->group_iter] = + talloc_zero(state->processed_groups, struct sdap_nested_group); + if (!state->processed_groups[state->group_iter]) { + ret = ENOMEM; + goto done; + } + + /* this steal doesn't change much now, but will be helpful later on + * if we steal the whole processed_group on the hash table */ + state->processed_groups[state->group_iter]->group = + talloc_steal(state->processed_groups[state->group_iter], + state->groups[state->group_iter]); + + /* Get any parent groups for this group */ + ret = sysdb_attrs_get_string(state->groups[state->group_iter], + SYSDB_ORIG_DN, + &state->orig_dn); + if (ret != EOK) { + goto done; + } + + attr_filter = talloc_array(state, const char *, 2); + if (!attr_filter) { + ret = ENOMEM; + goto done; + } + + attr_filter[0] = state->opts->group_map[SDAP_AT_GROUP_MEMBER].name; + attr_filter[1] = NULL; + + ret = build_attrs_from_map(state, state->opts->group_map, SDAP_OPTS_GROUP, + attr_filter, &state->attrs, NULL); + if (ret != EOK) { + goto done; + } + + ret = sss_filter_sanitize_dn(tmp_ctx, state->orig_dn, &clean_orig_dn); + if (ret != EOK) { + goto done; + } + + oc_list = sdap_make_oc_list(state, state->opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto done; + } + + state->base_filter = talloc_asprintf( + state, "(&(%s=%s)(%s)(%s=*))", + state->opts->group_map[SDAP_AT_GROUP_MEMBER].name, + clean_orig_dn, oc_list, + state->opts->group_map[SDAP_AT_GROUP_NAME].name); + if (!state->base_filter) { + ret = ENOMEM; + goto done; + } + + ret = rfc2307bis_nested_groups_next_base(req); + if (ret != EOK) goto done; + + /* Still processing parent groups */ + ret = EAGAIN; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t rfc2307bis_nested_groups_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_rfc2307bis_nested_ctx *state; + + state = tevent_req_data(req, struct sdap_rfc2307bis_nested_ctx); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for parent groups of group [%s] with base [%s]\n", + state->orig_dn, + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->group_map, SDAP_OPTS_GROUP, + state->timeout, + true); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, + rfc2307bis_nested_groups_process, + req); + + return EOK; +} + +static void +rfc2307bis_nested_groups_iterate(struct tevent_req *req, + struct sdap_rfc2307bis_nested_ctx *state) +{ + errno_t ret; + + state->group_iter++; + while (state->group_iter < state->num_groups) { + ret = rfc2307bis_nested_groups_step(req); + if (ret == EAGAIN) { + /* Looking up parent groups.. */ + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* EOK means this group has already been processed + * in another nesting level */ + state->group_iter++; + } + + if (state->group_iter == state->num_groups) { + /* All groups processed. Done. */ + tevent_req_done(req); + } +} + +static void rfc2307bis_nested_groups_done(struct tevent_req *subreq); +static void rfc2307bis_nested_groups_process(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_rfc2307bis_nested_ctx *state = + tevent_req_data(req, struct sdap_rfc2307bis_nested_ctx); + size_t count; + size_t i; + struct sysdb_attrs **ldap_groups; + struct sdap_nested_group *ngr; + hash_value_t value; + hash_key_t key; + int hret; + + ret = sdap_get_generic_recv(subreq, state, + &count, + &ldap_groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Found %zu parent groups of [%s]\n", count, state->orig_dn); + ngr = state->processed_groups[state->group_iter]; + + /* Add this batch of groups to the list */ + if (count > 0) { + ngr->ldap_parents = + talloc_realloc(ngr, + ngr->ldap_parents, + struct sysdb_attrs *, + ngr->parents_count + count + 1); + if (!ngr->ldap_parents) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Copy the new groups into the list. + * They're allocated on 'state' so we need to move them + * onto ldap_parents so that the data won't disappear when + * we finish this nesting level. + */ + for (i = 0; i < count; i++) { + ngr->ldap_parents[ngr->parents_count + i] = + talloc_steal(ngr->ldap_parents, ldap_groups[i]); + } + + ngr->parents_count += count; + + ngr->ldap_parents[ngr->parents_count] = NULL; + DEBUG(SSSDBG_TRACE_INTERNAL, + "Total of %zu direct parents after this iteration\n", + ngr->parents_count); + } + + state->base_iter++; + + /* Check for additional search bases, and iterate + * through again. + */ + if (state->search_bases[state->base_iter] != NULL) { + ret = rfc2307bis_nested_groups_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + /* Reset the base iterator for future lookups */ + state->base_iter = 0; + + /* Save the group into the hash table */ + key.type = HASH_KEY_STRING; + key.str = talloc_strdup(state, state->primary_name); + if (!key.str) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Steal the nested group entry on the group_hash context so it can + * outlive this request */ + talloc_steal(state->group_hash, ngr); + + value.type = HASH_VALUE_PTR; + value.ptr = ngr; + + hret = hash_enter(state->group_hash, &key, &value); + if (hret != HASH_SUCCESS) { + talloc_free(key.str); + tevent_req_error(req, EIO); + return; + } + talloc_free(key.str); + + if (ngr->parents_count == 0) { + /* No parent groups for this group in LDAP + * Move on to the next group + */ + rfc2307bis_nested_groups_iterate(req, state); + return; + } + + /* Otherwise, recurse into the groups */ + subreq = rfc2307bis_nested_groups_send( + state, state->ev, state->opts, state->sysdb, + state->dom, state->sh, + state->search_bases, + ngr->ldap_parents, + ngr->parents_count, + state->group_hash, + state->nesting_level+1); + if (!subreq) { + tevent_req_error(req, EIO); + return; + } + tevent_req_set_callback(subreq, rfc2307bis_nested_groups_done, req); +} + +errno_t rfc2307bis_nested_groups_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static void rfc2307bis_nested_groups_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_rfc2307bis_nested_ctx *state = + tevent_req_data(req, struct sdap_rfc2307bis_nested_ctx); + + ret = rfc2307bis_nested_groups_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "rfc2307bis_nested failed [%d][%s]\n", + ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + rfc2307bis_nested_groups_iterate(req, state); +} + +/* ==Initgr-call-(groups-a-user-is-member-of)============================= */ + +struct sdap_get_initgr_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + struct sdap_domain *sdom; + struct sdap_handle *sh; + struct sdap_id_ctx *id_ctx; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *user_op; + const char *filter_value; + const char **grp_attrs; + const char **user_attrs; + char *user_base_filter; + char *shortname; + char *filter; + int timeout; + bool non_posix; + + struct sysdb_attrs *orig_user; + + size_t user_base_iter; + struct sdap_search_base **user_search_bases; + + bool use_id_mapping; +}; + +static errno_t sdap_get_initgr_next_base(struct tevent_req *req); +static errno_t sdap_get_initgr_user_connect(struct tevent_req *req); +static void sdap_get_initgr_user_connect_done(struct tevent_req *subreq); +static void sdap_get_initgr_user(struct tevent_req *subreq); +static void sdap_get_initgr_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_handle *sh, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + const char *extra_value, + const char **grp_attrs, + bool set_non_posix) +{ + struct tevent_req *req; + struct sdap_get_initgr_state *state; + int ret; + char *clean_name; + bool use_id_mapping; + const char *search_attr = NULL; + char *ep_filter; + + DEBUG(SSSDBG_TRACE_ALL, "Retrieving info for initgroups call\n"); + + req = tevent_req_create(memctx, &state, struct sdap_get_initgr_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = id_ctx->opts; + state->dom = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->sdom = sdom; + state->sh = sh; + state->id_ctx = id_ctx; + state->conn = conn; + state->filter_value = filter_value; + state->grp_attrs = grp_attrs; + state->orig_user = NULL; + state->timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT); + state->user_base_iter = 0; + state->user_search_bases = sdom->user_search_bases; + if (!state->user_search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Initgroups lookup request without a user search base\n"); + ret = EINVAL; + goto done; + } + + if (state->dom->type == DOM_TYPE_APPLICATION || set_non_posix) { + state->non_posix = true; + } + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + id_ctx->opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + + switch (filter_type) { + case BE_FILTER_SECID: + search_attr = state->opts->user_map[SDAP_AT_USER_OBJECTSID].name; + + ret = sss_filter_sanitize(state, state->filter_value, &clean_name); + if (ret != EOK) { + talloc_zfree(req); + return NULL; + } + break; + case BE_FILTER_UUID: + search_attr = state->opts->user_map[SDAP_AT_USER_UUID].name; + + ret = sss_filter_sanitize(state, state->filter_value, &clean_name); + if (ret != EOK) { + talloc_zfree(req); + return NULL; + } + break; + case BE_FILTER_NAME: + if (extra_value && strcmp(extra_value, EXTRA_NAME_IS_UPN) == 0) { + + ret = sss_filter_sanitize(state, state->filter_value, &clean_name); + if (ret != EOK) { + talloc_zfree(req); + return NULL; + } + + ep_filter = get_enterprise_principal_string_filter(state, + state->opts->user_map[SDAP_AT_USER_PRINC].name, + clean_name, state->opts->basic); + state->user_base_filter = + talloc_asprintf(state, + "(&(|(%s=%s)(%s=%s)%s)(objectclass=%s)", + state->opts->user_map[SDAP_AT_USER_PRINC].name, + clean_name, + state->opts->user_map[SDAP_AT_USER_EMAIL].name, + clean_name, + ep_filter == NULL ? "" : ep_filter, + state->opts->user_map[SDAP_OC_USER].name); + if (state->user_base_filter == NULL) { + talloc_zfree(req); + return NULL; + } + } else { + search_attr = state->opts->user_map[SDAP_AT_USER_NAME].name; + + ret = sss_parse_internal_fqname(state, filter_value, + &state->shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot parse %s\n", filter_value); + goto done; + } + + ret = sss_filter_sanitize(state, state->shortname, &clean_name); + if (ret != EOK) { + talloc_zfree(req); + return NULL; + } + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported filter type [%d].\n", + filter_type); + return NULL; + } + + if (search_attr == NULL && state->user_base_filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing search attribute name or filter.\n"); + talloc_zfree(req); + return NULL; + } + + if (state->user_base_filter == NULL) { + state->user_base_filter = + talloc_asprintf(state, "(&(%s=%s)(objectclass=%s)", + search_attr, clean_name, + state->opts->user_map[SDAP_OC_USER].name); + if (!state->user_base_filter) { + talloc_zfree(req); + return NULL; + } + } + + if (state->non_posix) { + state->user_base_filter = talloc_asprintf_append(state->user_base_filter, + ")"); + } else if (use_id_mapping) { + /* When mapping IDs or looking for SIDs, we don't want to limit + * ourselves to users with a UID value. But there must be a SID to map + * from. + */ + state->user_base_filter = talloc_asprintf_append(state->user_base_filter, + "(%s=*))", + id_ctx->opts->user_map[SDAP_AT_USER_OBJECTSID].name); + } else { + /* When not ID-mapping or looking up app users, make sure there + * is a non-NULL UID */ + state->user_base_filter = talloc_asprintf_append(state->user_base_filter, + "(&(%s=*)(!(%s=0))))", + id_ctx->opts->user_map[SDAP_AT_USER_UID].name, + id_ctx->opts->user_map[SDAP_AT_USER_UID].name); + } + if (!state->user_base_filter) { + talloc_zfree(req); + return NULL; + } + + ret = build_attrs_from_map(state, + state->opts->user_map, + state->opts->user_map_cnt, + NULL, &state->user_attrs, NULL); + if (ret) { + talloc_zfree(req); + return NULL; + } + + state->use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + state->opts->idmap_ctx, + state->dom->name, + state->dom->domain_id); + + ret = sdap_get_initgr_user_connect(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t sdap_get_initgr_user_connect(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_initgr_state *state; + int ret = EOK; + struct sdap_id_conn_ctx *user_conn = NULL; + + state = tevent_req_data(req, struct sdap_get_initgr_state); + + /* Prefer LDAP over GC for users */ + user_conn = get_ldap_conn_from_sdom_pvt(state->id_ctx->opts, state->sdom); + state->user_op = sdap_id_op_create(state, user_conn == NULL + ? state->conn->conn_cache + : user_conn->conn_cache); + if (state->user_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + return ENOMEM; + } + + subreq = sdap_id_op_connect_send(state->user_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed\n"); + return ret; + } + + tevent_req_set_callback(subreq, sdap_get_initgr_user_connect_done, req); + return EOK; +} + +static void sdap_get_initgr_user_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sdap_get_initgr_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; +} + +static errno_t sdap_get_initgr_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_initgr_state *state; + + state = tevent_req_data(req, struct sdap_get_initgr_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->user_base_filter, + state->user_search_bases[state->user_base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for users with base [%s]\n", + state->user_search_bases[state->user_base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, sdap_id_op_handle(state->user_op), + state->user_search_bases[state->user_base_iter]->basedn, + state->user_search_bases[state->user_base_iter]->scope, + state->filter, state->user_attrs, + state->opts->user_map, state->opts->user_map_cnt, + state->timeout, + false); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_initgr_user, req); + return EOK; +} + +static int sdap_search_initgr_user_in_batch(struct sdap_get_initgr_state *state, + struct sysdb_attrs **users, + size_t count) +{ + int ret = EINVAL; + + for (size_t i = 0; i < count; i++) { + if (sdap_object_in_domain(state->opts, users[i], state->dom) == false) { + continue; + } + + state->orig_user = talloc_steal(state, users[i]); + ret = EOK; + break; + } + + return ret; +} + +static void sdap_get_initgr_user(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_initgr_state *state = tevent_req_data(req, + struct sdap_get_initgr_state); + struct sysdb_attrs **usr_attrs; + size_t count; + int ret; + errno_t sret; + const char *orig_dn; + const char *cname; + bool in_transaction = false; + + DEBUG(SSSDBG_TRACE_ALL, "Receiving info for the user\n"); + + ret = sdap_get_generic_recv(subreq, state, &count, &usr_attrs); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (count == 0) { + /* No users found in this search */ + state->user_base_iter++; + if (state->user_search_bases[state->user_base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_initgr_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + /* fallback to fetch a local user if required */ + if ((state->opts->schema_type == SDAP_SCHEMA_RFC2307) && + (dp_opt_get_bool(state->opts->basic, + SDAP_RFC2307_FALLBACK_TO_LOCAL_USERS) == true)) { + ret = sdap_fallback_local_user(state, state->shortname, -1, &usr_attrs); + if (ret == EOK) { + state->orig_user = usr_attrs[0]; + } + } else { + ret = ENOENT; + } + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + } else if (count == 1) { + state->orig_user = usr_attrs[0]; + } else { + DEBUG(SSSDBG_FUNC_DATA, + "The search returned %zu entries, need to match the correct one\n", + count); + + /* When matching against a search base, it's sufficient to pick only + * the first search base because all bases in a single domain would + * have the same DC= components + */ + ret = sdap_search_initgr_user_in_batch(state, usr_attrs, count); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_search_initgr_user_in_batch failed [%d]: %s :" + "SSSD can't select a user that matches domain %s\n", + ret, sss_strerror(ret), state->dom->name); + tevent_req_error(req, ret); + return; + } + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + DEBUG(SSSDBG_TRACE_ALL, "Storing the user\n"); + + ret = sdap_save_user(state, state->opts, state->dom, state->orig_user, + NULL, NULL, 0, state->non_posix); + if (ret) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_ALL, "Commit change\n"); + + ret = sysdb_transaction_commit(state->sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + + ret = sysdb_get_real_name(state, state->dom, state->filter_value, &cname); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot canonicalize username\n"); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_ALL, "Process user's groups\n"); + + switch (state->opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + subreq = sdap_initgr_rfc2307_send(state, state->ev, state->opts, + state->sysdb, state->dom, state->sh, + cname); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_initgr_done, req); + break; + + case SDAP_SCHEMA_RFC2307BIS: + case SDAP_SCHEMA_AD: + ret = sysdb_attrs_get_string(state->orig_user, + SYSDB_ORIG_DN, + &orig_dn); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->opts->dc_functional_level >= DS_BEHAVIOR_WIN2003 + && dp_opt_get_bool(state->opts->basic, SDAP_AD_USE_TOKENGROUPS)) { + /* Take advantage of AD's tokenGroups mechanism to look up all + * parent groups in a single request. + */ + subreq = sdap_ad_tokengroups_initgroups_send(state, state->ev, + state->id_ctx, + state->conn, + state->opts, + state->sysdb, + state->dom, + state->sh, + cname, orig_dn, + state->timeout, + state->use_id_mapping); + } else { + subreq = sdap_initgr_rfc2307bis_send( + state, state->ev, state->opts, + state->sdom, state->sh, + cname, orig_dn); + } + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + + talloc_steal(subreq, orig_dn); + tevent_req_set_callback(subreq, sdap_get_initgr_done, req); + break; + + case SDAP_SCHEMA_IPA_V1: + subreq = sdap_initgr_nested_send(state, state->ev, state->opts, + state->sysdb, state->dom, state->sh, + state->orig_user, state->grp_attrs); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_initgr_done, req); + return; + + default: + tevent_req_error(req, EINVAL); + return; + } + + return; +fail: + if (in_transaction) { + sret = sysdb_transaction_cancel(state->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + tevent_req_error(req, ret); +} + +static void sdap_ad_check_domain_local_groups_done(struct tevent_req *subreq); + +errno_t sdap_ad_check_domain_local_groups(struct tevent_req *req) +{ + struct sdap_get_initgr_state *state = tevent_req_data(req, + struct sdap_get_initgr_state); + int ret; + struct sdap_domain *local_sdom; + const char *orig_name; + const char *sysdb_name; + struct ldb_result *res; + struct tevent_req *subreq; + struct sysdb_attrs **groups; + + /* We only need to check for domain local groups in the AD case and if the + * user is not from our domain, i.e. if the user comes from a sub-domain. + */ + if (state->opts->schema_type != SDAP_SCHEMA_AD + || !IS_SUBDOMAIN(state->dom) + || !dp_target_enabled(state->id_ctx->be->provider, "ad", DPT_ID)) { + return EOK; + } + + local_sdom = sdap_domain_get(state->id_ctx->opts, state->dom->parent); + if (local_sdom == NULL || local_sdom->pvt == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n", + state->dom->parent->name); + return EINVAL; + } + + ret = sysdb_attrs_get_string(state->orig_user, SYSDB_NAME, &orig_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing name in user object.\n"); + return ret; + } + + sysdb_name = sss_create_internal_fqname(state, orig_name, state->dom->name); + if (sysdb_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed.\n"); + return ENOMEM; + } + + ret = sysdb_initgroups(state, state->dom, sysdb_name, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_initgroups failed for user [%s].\n", + sysdb_name); + return ret; + } + + if (res->count == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_initgroups returned no results for user [%s].\n", + sysdb_name); + return EINVAL; + } + + /* The user object, the first entry in the res->msgs, is included as well + * to cover the case where the remote user is directly added to + * a domain local group. */ + ret = sysdb_msg2attrs(state, res->count, res->msgs, &groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_msg2attrs failed.\n"); + return ret; + } + + subreq = sdap_ad_get_domain_local_groups_send(state, state->ev, local_sdom, + state->opts, state->sysdb, state->dom->parent, + groups, res->count); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_ad_get_domain_local_groups_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_ad_check_domain_local_groups_done, + req); + + return EAGAIN; +} + +static void sdap_ad_check_domain_local_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + int ret; + + ret = sdap_ad_get_domain_local_groups_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + + return; +} + +static void sdap_get_initgr_pgid(struct tevent_req *req); +static void sdap_get_initgr_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_initgr_state *state = tevent_req_data(req, + struct sdap_get_initgr_state); + int ret; + TALLOC_CTX *tmp_ctx; + gid_t primary_gid; + char *gid; + char *sid_str; + char *dom_sid_str; + char *group_sid_str; + struct sdap_options *opts = state->opts; + struct ldb_message *msg; + + DEBUG(SSSDBG_TRACE_ALL, "Initgroups done\n"); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + tevent_req_error(req, ENOMEM); + return; + } + + switch (state->opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + ret = sdap_initgr_rfc2307_recv(subreq); + break; + + case SDAP_SCHEMA_RFC2307BIS: + case SDAP_SCHEMA_AD: + if (state->opts->dc_functional_level >= DS_BEHAVIOR_WIN2003 + && dp_opt_get_bool(state->opts->basic, SDAP_AD_USE_TOKENGROUPS)) { + + ret = sdap_ad_tokengroups_initgroups_recv(subreq); + } else { + ret = sdap_initgr_rfc2307bis_recv(subreq); + } + break; + + case SDAP_SCHEMA_IPA_V1: + ret = sdap_initgr_nested_recv(subreq); + break; + + default: + + ret = EINVAL; + break; + } + + talloc_zfree(subreq); + if (ret) { + DEBUG(SSSDBG_TRACE_ALL, "Error in initgroups: [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + /* We also need to update the user's primary group, since + * the user may not be an explicit member of that group + */ + + if (state->use_id_mapping) { + DEBUG(SSSDBG_TRACE_LIBS, + "Mapping primary group to unix ID\n"); + + /* The primary group ID is just the RID part of the objectSID + * of the group. Generate the GID by adding this to the domain + * SID value. + */ + + /* Get the user SID so we can extract the domain SID + * from it. + */ + ret = sdap_attrs_get_sid_str( + tmp_ctx, opts->idmap_ctx, state->orig_user, + opts->user_map[SDAP_AT_USER_OBJECTSID].sys_name, + &sid_str); + if (ret != EOK) goto done; + + /* Get the domain SID from the user SID */ + ret = sdap_idmap_get_dom_sid_from_object(tmp_ctx, sid_str, + &dom_sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not parse domain SID from [%s]\n", sid_str); + goto done; + } + + ret = sysdb_attrs_get_uint32_t( + state->orig_user, + opts->user_map[SDAP_AT_USER_PRIMARY_GROUP].sys_name, + &primary_gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "no primary group ID provided\n"); + ret = EINVAL; + goto done; + } + + /* Add the RID to the end */ + group_sid_str = talloc_asprintf(tmp_ctx, "%s-%lu", + dom_sid_str, + (unsigned long)primary_gid); + if (!group_sid_str) { + ret = ENOMEM; + goto done; + } + + /* Convert the SID into a UNIX group ID */ + ret = sdap_idmap_sid_to_unix(opts->idmap_ctx, group_sid_str, + &primary_gid); + if (ret != EOK) goto done; + } else { + ret = sysdb_attrs_get_uint32_t(state->orig_user, SYSDB_GIDNUM, + &primary_gid); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Could not find user's primary GID\n"); + goto done; + } + } + + ret = sysdb_search_group_by_gid(tmp_ctx, state->dom, primary_gid, NULL, + &msg); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Primary group already cached, nothing to do.\n"); + } else { + gid = talloc_asprintf(state, "%lu", (unsigned long)primary_gid); + if (gid == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = groups_get_send(req, state->ev, state->id_ctx, + state->id_ctx->opts->sdom, state->conn, + gid, BE_FILTER_IDNUM, false, + false, false); + if (!subreq) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, sdap_get_initgr_pgid, req); + + talloc_free(tmp_ctx); + return; + } + + ret = sdap_ad_check_domain_local_groups(req); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_ALL, + "Checking for domain local group memberships.\n"); + talloc_free(tmp_ctx); + return; + } else if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "No need to check for domain local group memberships.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_check_domain_local_groups failed, " + "memberships to domain local groups might be missing.\n"); + /* do not let the request fail completely because we already have at + * least "some" groups */ + ret = EOK; + } + +done: + talloc_free(tmp_ctx); + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + return; +} + +static void sdap_get_initgr_pgid(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + errno_t ret; + + ret = groups_get_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sdap_ad_check_domain_local_groups(req); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_ALL, + "Checking for domain local group memberships.\n"); + return; + } else if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "No need to check for domain local group memberships.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_check_domain_local_groups failed, " + "memberships to domain local groups might be missing.\n"); + /* do not let the request fail completely because we already have at + * least "some" groups */ + } + + tevent_req_done(req); + return; +} + +int sdap_get_initgr_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t get_sysdb_grouplist_ex(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + char ***grouplist, + bool get_dn) +{ + errno_t ret; + const char *attrs[2]; + struct ldb_message *msg; + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *groups; + char **sysdb_grouplist = NULL; + unsigned int i; + + attrs[0] = SYSDB_MEMBEROF; + attrs[1] = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, + attrs, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error searching user [%s] by name: [%s]\n", + name, strerror(ret)); + goto done; + } + + groups = ldb_msg_find_element(msg, SYSDB_MEMBEROF); + if (!groups || groups->num_values == 0) { + /* No groups for this user in sysdb currently */ + sysdb_grouplist = NULL; + } else { + sysdb_grouplist = talloc_array(tmp_ctx, char *, groups->num_values+1); + if (!sysdb_grouplist) { + ret = ENOMEM; + goto done; + } + + if (get_dn) { + /* Get distinguish name */ + for (i=0; i < groups->num_values; i++) { + sysdb_grouplist[i] = talloc_strdup(sysdb_grouplist, + (const char *)groups->values[i].data); + if (sysdb_grouplist[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + } else { + /* Get a list of the groups by groupname only */ + for (i=0; i < groups->num_values; i++) { + ret = sysdb_group_dn_name(sysdb, + sysdb_grouplist, + (const char *)groups->values[i].data, + &sysdb_grouplist[i]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not determine group name from [%s]: [%s]\n", + (const char *)groups->values[i].data, strerror(ret)); + goto done; + } + } + } + + sysdb_grouplist[groups->num_values] = NULL; + } + + *grouplist = talloc_steal(mem_ctx, sysdb_grouplist); + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t get_sysdb_grouplist(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + char ***grouplist) +{ + return get_sysdb_grouplist_ex(mem_ctx, sysdb, domain, + name, grouplist, false); +} + +errno_t get_sysdb_grouplist_dn(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + char ***grouplist) +{ + return get_sysdb_grouplist_ex(mem_ctx, sysdb, domain, + name, grouplist, true); +} + +errno_t +sdap_handle_id_collision_for_incomplete_groups(struct data_provider *dp, + struct sss_domain_info *domain, + const char *name, + gid_t gid, + const char *original_dn, + const char *sid_str, + const char *uuid, + bool posix, + time_t now) +{ + errno_t ret; + + ret = sysdb_delete_group(domain, NULL, gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Due to an id collision, the new group with gid [\"%"PRIu32"\"] " + "will not be added as the old group (with the same gid) could " + "not be removed from the sysdb!\n", + gid); + return ret; + } + + ret = sysdb_add_incomplete_group(domain, name, gid, original_dn, sid_str, + uuid, posix, now); + if (ret != EOK) { + return ret; + } + + dp_sbus_invalidate_group_memcache(dp, gid); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_initgroups_ad.c b/src/providers/ldap/sdap_async_initgroups_ad.c new file mode 100644 index 0000000..fb80c92 --- /dev/null +++ b/src/providers/ldap/sdap_async_initgroups_ad.c @@ -0,0 +1,1742 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_async_ad.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ad/ad_common.h" +#include "lib/idmap/sss_idmap.h" + +struct sdap_get_ad_tokengroups_state { + struct tevent_context *ev; + struct sss_idmap_ctx *idmap_ctx; + const char *username; + + char **sids; + size_t num_sids; +}; + +static void sdap_get_ad_tokengroups_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_get_ad_tokengroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *name, + const char *orig_dn, + int timeout) +{ + struct sdap_get_ad_tokengroups_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + const char *attrs[] = {AD_TOKENGROUPS_ATTR, NULL}; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_get_ad_tokengroups_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->idmap_ctx = opts->idmap_ctx->map; + state->ev = ev; + state->username = talloc_strdup(state, name); + if (state->username == NULL) { + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_get_generic_send(state, state->ev, opts, sh, orig_dn, + LDAP_SCOPE_BASE, NULL, attrs, + NULL, 0, timeout, false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_get_ad_tokengroups_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void sdap_get_ad_tokengroups_done(struct tevent_req *subreq) +{ + struct sdap_get_ad_tokengroups_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **users = NULL; + struct ldb_message_element *el = NULL; + enum idmap_error_code err; + char *sid_str = NULL; + size_t num_users; + size_t i; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_get_ad_tokengroups_state); + + ret = sdap_get_generic_recv(subreq, state, &num_users, &users); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "LDAP search failed: [%s]\n", sss_strerror(ret)); + goto done; + } + + if (num_users != 1) { + DEBUG(SSSDBG_MINOR_FAILURE, + "More than one result on a base search!\n"); + ret = EINVAL; + goto done; + } + + /* get the list of sids from tokengroups */ + ret = sysdb_attrs_get_el_ext(users[0], AD_TOKENGROUPS_ATTR, false, &el); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_LIBS, "No tokenGroups entries for [%s]\n", + state->username); + + state->sids = NULL; + state->num_sids = 0; + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not read tokenGroups attribute: " + "[%s]\n", strerror(ret)); + goto done; + } + + state->num_sids = 0; + state->sids = talloc_zero_array(state, char*, el->num_values); + if (state->sids == NULL) { + ret = ENOMEM; + goto done; + } + + /* convert binary sid to string */ + for (i = 0; i < el->num_values; i++) { + err = sss_idmap_bin_sid_to_sid(state->idmap_ctx, el->values[i].data, + el->values[i].length, &sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert binary SID to string: [%s]. Skipping\n", + idmap_error_string(err)); + continue; + } + + state->sids[i] = talloc_move(state->sids, &sid_str); + state->num_sids++; + } + + /* shrink array to final number of elements */ + state->sids = talloc_realloc(state, state->sids, char*, state->num_sids); + if (state->sids == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sdap_get_ad_tokengroups_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + size_t *_num_sids, + char ***_sids) +{ + struct sdap_get_ad_tokengroups_state *state = NULL; + state = tevent_req_data(req, struct sdap_get_ad_tokengroups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_num_sids != NULL) { + *_num_sids = state->num_sids; + } + + if (_sids != NULL) { + *_sids = talloc_steal(mem_ctx, state->sids); + } + + return EOK; +} + +errno_t +sdap_ad_tokengroups_update_members(const char *username, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + char **ldap_groups) +{ + TALLOC_CTX *tmp_ctx = NULL; + char **sysdb_groups = NULL; + char **add_groups = NULL; + char **del_groups = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* Get the current sysdb group list for this user so we can update it. */ + ret = get_sysdb_grouplist_dn(tmp_ctx, sysdb, domain, + username, &sysdb_groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not get the list of groups for " + "[%s] in the sysdb: [%s]\n", username, strerror(ret)); + goto done; + } + + /* Find the differences between the sysdb and LDAP lists. + * Groups in the sysdb only must be removed. */ + ret = diff_string_lists(tmp_ctx, ldap_groups, sysdb_groups, + &add_groups, &del_groups, NULL); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Updating memberships for [%s]\n", username); + + ret = sysdb_update_members_dn(domain, username, SYSDB_MEMBER_USER, + (const char *const *) add_groups, + (const char *const *) del_groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +struct sdap_ad_resolve_sids_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_conn_ctx *conn; + struct sdap_options *opts; + struct sss_domain_info *domain; + char **sids; + + const char *current_sid; + int index; +}; + +static errno_t sdap_ad_resolve_sids_step(struct tevent_req *req); +static void sdap_ad_resolve_sids_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_ad_resolve_sids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + struct sdap_options *opts, + struct sss_domain_info *domain, + char **sids) +{ + struct sdap_ad_resolve_sids_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ad_resolve_sids_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->id_ctx = id_ctx; + state->conn = conn; + state->opts = opts; + state->domain = get_domains_head(domain); + state->sids = sids; + state->index = 0; + + if (state->sids == NULL || state->sids[0] == NULL) { + ret = EOK; + goto immediately; + } + + ret = sdap_ad_resolve_sids_step(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t sdap_ad_resolve_sids_step(struct tevent_req *req) +{ + struct sdap_ad_resolve_sids_state *state = NULL; + struct tevent_req *subreq = NULL; + struct sdap_domain *sdap_domain = NULL; + struct sss_domain_info *domain = NULL; + + state = tevent_req_data(req, struct sdap_ad_resolve_sids_state); + + do { + state->current_sid = state->sids[state->index]; + if (state->current_sid == NULL) { + return EOK; + } + state->index++; + + domain = sss_get_domain_by_sid_ldap_fallback(state->domain, + state->current_sid); + + if (domain == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "SID %s does not belong to any known " + "domain\n", state->current_sid); + } + } while (domain == NULL); + + sdap_domain = sdap_domain_get(state->opts, domain); + if (sdap_domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "SDAP domain does not exist?\n"); + return ERR_INTERNAL; + } + + subreq = groups_get_send(state, state->ev, state->id_ctx, sdap_domain, + state->conn, state->current_sid, + BE_FILTER_SECID, false, true, false); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_ad_resolve_sids_done, req); + + return EAGAIN; +} + +static void sdap_ad_resolve_sids_done(struct tevent_req *subreq) +{ + struct sdap_ad_resolve_sids_state *state = NULL; + struct tevent_req *req = NULL; + int dp_error; + int sdap_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ad_resolve_sids_state); + + ret = groups_get_recv(subreq, &dp_error, &sdap_error); + talloc_zfree(subreq); + + if (ret == EOK && sdap_error == ENOENT && dp_error == DP_ERR_OK) { + /* Group was not found, we will ignore the error and continue with + * next group. This may happen for example if the group is built-in, + * but a custom search base is provided. */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to resolve SID %s - will try next sid.\n", + state->current_sid); + } else if (ret != EOK || sdap_error != EOK || dp_error != DP_ERR_OK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to resolve SID %s [dp_error: %d, " + "sdap_error: %d, ret: %d]: %s\n", state->current_sid, dp_error, + sdap_error, ret, strerror(ret)); + goto done; + } + + ret = sdap_ad_resolve_sids_step(req); + if (ret == EAGAIN) { + /* continue with next SID */ + return; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sdap_ad_resolve_sids_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +struct sdap_ad_tokengroups_initgr_mapping_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sdap_idmap_ctx *idmap_ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + const char *orig_dn; + int timeout; + const char *username; + + struct sdap_id_op *op; +}; + +static void +sdap_ad_tokengroups_initgr_mapping_connect_done(struct tevent_req *subreq); +static void sdap_ad_tokengroups_initgr_mapping_done(struct tevent_req *subreq); +static errno_t handle_missing_pvt(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + const char *orig_dn, + int timeout, + const char *username, + struct sdap_handle *sh, + struct tevent_req *req, + tevent_req_fn callback); + +static struct tevent_req * +sdap_ad_tokengroups_initgr_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_handle *sh, + const char *name, + const char *orig_dn, + int timeout) +{ + struct sdap_ad_tokengroups_initgr_mapping_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_domain *sdom; + struct ad_id_ctx *subdom_id_ctx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ad_tokengroups_initgr_mapping_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->idmap_ctx = opts->idmap_ctx; + state->sysdb = sysdb; + state->domain = domain; + state->timeout = timeout; + state->orig_dn = orig_dn; + state->username = talloc_strdup(state, name); + if (state->username == NULL) { + ret = ENOMEM; + goto immediately; + } + + sdom = sdap_domain_get(opts, domain); + if (sdom == NULL || sdom->pvt == NULL) { + ret = handle_missing_pvt(mem_ctx, ev, opts, orig_dn, timeout, + state->username, sh, req, + sdap_ad_tokengroups_initgr_mapping_done); + if (ret == EOK) { + return req; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n", + domain->name); + goto immediately; + } + } + + subdom_id_ctx = talloc_get_type(sdom->pvt, struct ad_id_ctx); + state->op = sdap_id_op_create(state, subdom_id_ctx->ldap_ctx->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, + sdap_ad_tokengroups_initgr_mapping_connect_done, + req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void +sdap_ad_tokengroups_initgr_mapping_connect_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgr_mapping_state *state = NULL; + struct tevent_req *req = NULL; + int ret; + int dp_error = DP_ERR_FATAL; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, + struct sdap_ad_tokengroups_initgr_mapping_state); + + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_ad_tokengroups_send(state, state->ev, state->opts, + sdap_id_op_handle(state->op), + state->username, + state->orig_dn, state->timeout); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgr_mapping_done, + req); + + return; +} + +errno_t sdap_ad_save_group_membership_with_idmapping(const char *username, + struct sdap_options *opts, + struct sss_domain_info *user_dom, + struct sdap_idmap_ctx *idmap_ctx, + size_t num_sids, + char **sids) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct sss_domain_info *domain = NULL; + struct ldb_message *msg = NULL; + const char *attrs[] = {SYSDB_NAME, NULL}; + const char *name = NULL; + const char *sid = NULL; + size_t i; + time_t now; + gid_t gid; + char **groups = NULL; + size_t num_groups; + errno_t ret; + errno_t sret; + bool in_transaction = false; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + num_groups = 0; + groups = talloc_zero_array(tmp_ctx, char*, num_sids + 1); + if (groups == NULL) { + ret = ENOMEM; + goto done; + } + + now = time(NULL); + ret = sysdb_transaction_start(user_dom->sysdb); + if (ret != EOK) { + goto done; + } + in_transaction = true; + + for (i = 0; i < num_sids; i++) { + sid = sids[i]; + DEBUG(SSSDBG_TRACE_LIBS, "Processing membership SID [%s]\n", sid); + + ret = sdap_idmap_sid_to_unix(idmap_ctx, sid, &gid); + if (ret == ENOTSUP) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping built-in object.\n"); + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not convert SID to GID: [%s]. " + "Skipping\n", strerror(ret)); + continue; + } + + domain = sss_get_domain_by_sid_ldap_fallback(user_dom, sid); + if (domain == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Domain not found for SID %s\n", sid); + continue; + } + + DEBUG(SSSDBG_TRACE_LIBS, "SID [%s] maps to GID [%"SPRIgid"]\n", + sid, gid); + + /* Check whether this GID already exists in the sysdb */ + ret = sysdb_search_group_by_gid(tmp_ctx, domain, gid, attrs, &msg); + if (ret == EOK) { + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not retrieve group name from sysdb\n"); + ret = EINVAL; + goto done; + } + } else if (ret == ENOENT) { + /* This is a new group. For now, we will store it under the name + * of its SID. When a direct lookup of the group or its GID occurs, + * it will replace this temporary entry. */ + name = sss_create_internal_fqname(tmp_ctx, sid, domain->name); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_add_incomplete_group(domain, name, gid, + NULL, sid, NULL, false, now); + if (ret == ERR_GID_DUPLICATED) { + /* In case o group id-collision, do: + * - Delete the group from sysdb + * - Add the new incomplete group + * - Notify the NSS responder that the entry has also to be + * removed from the memory cache + */ + ret = sdap_handle_id_collision_for_incomplete_groups( + idmap_ctx->id_ctx->be->provider, + domain, name, gid, NULL, sid, NULL, + false, now); + } + + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not create incomplete " + "group: [%s]\n", strerror(ret)); + goto done; + } + } else { + /* Unexpected error */ + DEBUG(SSSDBG_MINOR_FAILURE, "Could not look up group in sysdb: " + "[%s]\n", strerror(ret)); + goto done; + } + + groups[num_groups] = sysdb_group_strdn(tmp_ctx, domain->name, name); + if (groups[num_groups] == NULL) { + ret = ENOMEM; + goto done; + } + num_groups++; + } + + groups[num_groups] = NULL; + + ret = sdap_ad_tokengroups_update_members(username, + user_dom->sysdb, user_dom, + groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(user_dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not commit transaction! [%s]\n", + strerror(ret)); + goto done; + } + in_transaction = false; + +done: + talloc_free(tmp_ctx); + + if (in_transaction) { + sret = sysdb_transaction_cancel(user_dom->sysdb); + DEBUG(SSSDBG_FATAL_FAILURE, "Could not cancel transaction! [%s]\n", + strerror(sret)); + } + + return ret; +} + +static void sdap_ad_tokengroups_initgr_mapping_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgr_mapping_state *state = NULL; + struct tevent_req *req = NULL; + char **sids = NULL; + size_t num_sids = 0; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ad_tokengroups_initgr_mapping_state); + + ret = sdap_get_ad_tokengroups_recv(state, subreq, &num_sids, &sids); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to acquire tokengroups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sdap_ad_save_group_membership_with_idmapping(state->username, + state->opts, + state->domain, + state->idmap_ctx, + num_sids, + sids); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_save_group_membership_with_idmapping failed.\n"); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int sdap_ad_tokengroups_initgr_mapping_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_ad_tokengroups_initgr_posix_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_conn_ctx *conn; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + const char *orig_dn; + int timeout; + const char *username; + + struct sdap_id_op *op; + char **missing_sids; + size_t num_missing_sids; + char **cached_groups; + size_t num_cached_groups; +}; + +static void +sdap_ad_tokengroups_initgr_posix_tg_done(struct tevent_req *subreq); + +static void +sdap_ad_tokengroups_initgr_posix_sids_connect_done(struct tevent_req *subreq); +static void +sdap_ad_tokengroups_initgr_posix_sids_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_ad_tokengroups_initgr_posix_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_handle *sh, + const char *name, + const char *orig_dn, + int timeout) +{ + struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_domain *sdom; + struct ad_id_ctx *subdom_id_ctx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ad_tokengroups_initgr_posix_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->id_ctx = id_ctx; + state->conn = conn; + state->opts = opts; + state->sh = sh; + state->sysdb = sysdb; + state->domain = domain; + state->orig_dn = orig_dn; + state->timeout = timeout; + state->username = talloc_strdup(state, name); + if (state->username == NULL) { + ret = ENOMEM; + goto immediately; + } + + sdom = sdap_domain_get(opts, domain); + if (sdom == NULL || sdom->pvt == NULL) { + ret = handle_missing_pvt(mem_ctx, ev, opts, orig_dn, timeout, + state->username, sh, req, + sdap_ad_tokengroups_initgr_posix_tg_done); + if (ret == EOK) { + return req; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n", + domain->name); + goto immediately; + } + } + subdom_id_ctx = talloc_get_type(sdom->pvt, struct ad_id_ctx); + state->op = sdap_id_op_create(state, subdom_id_ctx->ldap_ctx->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, + sdap_ad_tokengroups_initgr_posix_sids_connect_done, + req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void +sdap_ad_tokengroups_initgr_posix_sids_connect_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; + struct tevent_req *req = NULL; + int ret; + int dp_error = DP_ERR_FATAL; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, + struct sdap_ad_tokengroups_initgr_posix_state); + + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_ad_tokengroups_send(state, state->ev, state->opts, + sdap_id_op_handle(state->op), + state->username, state->orig_dn, + state->timeout); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgr_posix_tg_done, + req); + + return; +} + +errno_t +sdap_ad_tokengroups_get_posix_members(TALLOC_CTX *mem_ctx, + struct sss_domain_info *user_domain, + size_t num_sids, + char **sids, + size_t *_num_missing, + char ***_missing, + size_t *_num_valid, + char ***_valid_groups) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct sss_domain_info *domain = NULL; + struct ldb_message *msg = NULL; + const char *attrs[] = {SYSDB_NAME, NULL}; + const char *name = NULL; + char *sid = NULL; + char **valid_groups = NULL; + size_t num_valid_groups; + char **missing_sids = NULL; + size_t num_missing_sids; + size_t i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + ret = ENOMEM; + goto done; + } + + num_valid_groups = 0; + valid_groups = talloc_zero_array(tmp_ctx, char*, num_sids + 1); + if (valid_groups == NULL) { + ret = ENOMEM; + goto done; + } + + num_missing_sids = 0; + missing_sids = talloc_zero_array(tmp_ctx, char*, num_sids + 1); + if (missing_sids == NULL) { + ret = ENOMEM; + goto done; + } + + /* For each SID check if it is already present in the cache. If yes, we + * will get name of the group and update the membership. Otherwise we need + * to remember the SID and download missing groups one by one. */ + for (i = 0; i < num_sids; i++) { + sid = sids[i]; + DEBUG(SSSDBG_TRACE_LIBS, "Processing membership SID [%s]\n", sid); + + domain = sss_get_domain_by_sid_ldap_fallback(user_domain, sid); + if (domain == NULL) { + const char *check_dom; + const char *check_name; + + ret = well_known_sid_to_name(sid, &check_dom, &check_name); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Skipping SID [%s][%s\\%s] which is " + "currently not handled by SSSD.\n", + sid, check_dom, check_name); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Domain not found for SID %s\n", sid); + } + continue; + } + + ret = sysdb_search_group_by_sid_str(tmp_ctx, domain, sid, attrs, &msg); + if (ret == EOK) { + /* we will update membership of this group */ + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not retrieve group name from sysdb\n"); + ret = EINVAL; + goto done; + } + + valid_groups[num_valid_groups] = sysdb_group_strdn(valid_groups, + domain->name, + name); + if (valid_groups[num_valid_groups] == NULL) { + ret = ENOMEM; + goto done; + } + num_valid_groups++; + } else if (ret == ENOENT) { + if (_missing != NULL) { + /* we need to download this group */ + missing_sids[num_missing_sids] = talloc_steal(missing_sids, + sid); + num_missing_sids++; + + DEBUG(SSSDBG_TRACE_FUNC, "Missing SID %s will be downloaded\n", + sid); + } + + /* else: We have downloaded missing groups but some of them may + * remained missing because they are outside of search base. We + * will just ignore them and continue with the next group. */ + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not look up SID %s in sysdb: " + "[%s]\n", sid, strerror(ret)); + goto done; + } + } + + valid_groups[num_valid_groups] = NULL; + missing_sids[num_missing_sids] = NULL; + + /* return list of missing groups */ + if (_missing != NULL) { + *_missing = talloc_steal(mem_ctx, missing_sids); + *_num_missing = num_missing_sids; + } + + /* return list of missing groups */ + if (_valid_groups != NULL) { + *_valid_groups = talloc_steal(mem_ctx, valid_groups); + *_num_valid = num_valid_groups; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void +sdap_ad_tokengroups_initgr_posix_tg_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; + struct tevent_req *req = NULL; + char **sids = NULL; + size_t num_sids = 0; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ad_tokengroups_initgr_posix_state); + + ret = sdap_get_ad_tokengroups_recv(state, subreq, &num_sids, &sids); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to acquire tokengroups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sdap_ad_tokengroups_get_posix_members(state, state->domain, + num_sids, sids, + &state->num_missing_sids, + &state->missing_sids, + &state->num_cached_groups, + &state->cached_groups); + if (ret != EOK) { + goto done; + } + + /* download missing SIDs */ + subreq = sdap_ad_resolve_sids_send(state, state->ev, state->id_ctx, + state->conn, + state->opts, state->domain, + state->missing_sids); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgr_posix_sids_done, + req); + + return; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void +sdap_ad_tokengroups_initgr_posix_sids_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + char **cached_groups; + size_t num_cached_groups; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ad_tokengroups_initgr_posix_state); + + ret = sdap_ad_resolve_sids_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to resolve missing SIDs " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + ret = sdap_ad_tokengroups_get_posix_members(state, state->domain, + state->num_missing_sids, + state->missing_sids, + NULL, NULL, + &num_cached_groups, + &cached_groups); + if (ret != EOK){ + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_ad_tokengroups_get_posix_members failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + state->cached_groups = concatenate_string_array(state, + state->cached_groups, + state->num_cached_groups, + cached_groups, + num_cached_groups); + if (state->cached_groups == NULL) { + ret = ENOMEM; + goto done; + } + + /* update membership of existing groups */ + ret = sdap_ad_tokengroups_update_members(state->username, + state->sysdb, state->domain, + state->cached_groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sdap_ad_tokengroups_initgr_posix_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_ad_get_domain_local_groups_state { + struct tevent_context *ev; + struct sdap_id_conn_ctx *conn; + struct sdap_options *opts; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *dom; + int dp_error; + + struct sdap_search_base **search_bases; + struct sysdb_attrs **groups; + size_t num_groups; + hash_table_t *group_hash; +}; + +static void +sdap_ad_get_domain_local_groups_connect_done(struct tevent_req *subreq); +static void sdap_ad_get_domain_local_groups_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_ad_get_domain_local_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_domain *local_sdom, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sysdb_attrs **groups, + size_t num_groups) +{ + struct sdap_ad_get_domain_local_groups_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct ad_id_ctx *ad_id_ctx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ad_get_domain_local_groups_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + ad_id_ctx = talloc_get_type(local_sdom->pvt, struct ad_id_ctx); + state->conn = ad_id_ctx->ldap_ctx; + state->opts = opts; + state->sysdb = sysdb; + state->dom = dom; + state->search_bases = state->conn->id_ctx->opts->sdom->group_search_bases; + state->groups = groups; + state->num_groups = num_groups; + + ret = sss_hash_create(state, 0, &state->group_hash); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n"); + goto fail; + } + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (state->op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed.\n"); + goto fail; + } + + tevent_req_set_callback(subreq, + sdap_ad_get_domain_local_groups_connect_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +sdap_ad_get_domain_local_groups_connect_done(struct tevent_req *subreq) +{ + + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_ad_get_domain_local_groups_state *state = tevent_req_data(req, + struct sdap_ad_get_domain_local_groups_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + subreq = rfc2307bis_nested_groups_send(state, state->ev, state->opts, + state->sysdb, state->dom, + sdap_id_op_handle(state->op), + state->search_bases, + state->groups, state->num_groups, + state->group_hash, 0); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rfc2307bis_nested_groups_send failed.\n"); + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, + sdap_ad_get_domain_local_groups_done, req); + + return; +} + +struct sdap_nested_group { + struct sysdb_attrs *group; + struct sysdb_attrs **ldap_parents; + size_t parents_count; +}; + +static errno_t +sdap_ad_get_domain_local_groups_parse_parents(TALLOC_CTX *mem_ctx, + struct sdap_nested_group *gr, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + const char **_sysdb_name, + enum sysdb_member_type *_type, + char ***_add_list, + char ***_del_list) +{ + int ret; + size_t c; + char **groupnamelist = NULL; + struct sysdb_attrs *groups[1]; + enum sysdb_member_type type; + const char *sysdb_name; + const char *group_name; + const char *class; + struct sss_domain_info *obj_dom; + char *local_groups_base_dn; + char **cached_local_parents = NULL; + char **add_list = NULL; + char **del_list = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + local_groups_base_dn = talloc_asprintf(tmp_ctx, SYSDB_TMPL_GROUP_BASE, + dom->name); + if (local_groups_base_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + if (gr->parents_count != 0) { + /* Store the parents if needed */ + ret = sdap_nested_groups_store(sysdb, dom, opts, + gr->ldap_parents, gr->parents_count); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sdap_get_primary_fqdn_list(dom, tmp_ctx, gr->ldap_parents, + gr->parents_count, + opts->group_map[SDAP_AT_GROUP_NAME].name, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + opts->idmap_ctx, + &groupnamelist); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_primary_fqdn_list failed.\n"); + goto done; + } + } + + ret = sysdb_attrs_get_string(gr->group, SYSDB_NAME, &sysdb_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string failed to get SYSDB_NAME, " + "skipping.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(gr->group, SYSDB_OBJECTCATEGORY, &class); + if (ret != EOK) { + /* If objectcategory is missing, gr->group is a nested parent found during + * the nested group lookup. It might not already be stored in the cache. + */ + DEBUG(SSSDBG_TRACE_LIBS, + "sysdb_attrs_get_string failed to get %s for [%s], assuming " + "group.\n", SYSDB_OBJECTCATEGORY, sysdb_name); + + /* make sure group exists in cache */ + groups[0]= gr->group; + ret = sdap_nested_groups_store(sysdb, dom, opts, groups, 1); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + /* Since the object is coming from LDAP it cannot have the internal + * fully-qualified name, so we can expand it unconditionally. */ + group_name = NULL; + ret = sdap_get_primary_name(opts->group_map[SDAP_AT_GROUP_NAME].name, + gr->group, &group_name); + if (ret != EOK || group_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Could not determine primary name\n"); + group_name = sysdb_name; + } + + group_name = sss_create_internal_fqname(tmp_ctx, group_name, + dom->name); + if (group_name != NULL) { + sysdb_name = group_name; + } + + type = SYSDB_MEMBER_GROUP; + } else { + if (class != NULL && strcmp(class, SYSDB_USER_CLASS) == 0) { + type = SYSDB_MEMBER_USER; + } else { + type = SYSDB_MEMBER_GROUP; + } + } + + /* We need to get the cached list of groups form the local domain the + * object is a member of to compare them with the current list just + * retrieved (groupnamelist). Even if this list is empty we have to + * proceed because the membership might have been removed recently on the + * server. */ + + obj_dom = find_domain_by_object_name(get_domains_head(dom), + sysdb_name); + if (obj_dom == NULL) { + obj_dom = dom; + DEBUG(SSSDBG_OP_FAILURE, "Cannot find domain for [%s], " + "trying with local domain [%s].\n", + sysdb_name, obj_dom->name); + } + + ret = sysdb_get_direct_parents(tmp_ctx, obj_dom, dom, type, sysdb_name, + &cached_local_parents); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE,"sysdb_get_direct_parents failed.\n"); + goto done; + } + + if (cached_local_parents != NULL && cached_local_parents[0] == NULL) { + talloc_zfree(cached_local_parents); + } + + if (DEBUG_IS_SET(SSSDBG_TRACE_ALL)) { + if (cached_local_parents != NULL) { + for (c = 0; cached_local_parents[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] cached_local_parents [%s].\n", + sysdb_name, cached_local_parents[c]); + } + } + + if (groupnamelist != NULL) { + for (c = 0; groupnamelist[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] groupnamelist [%s].\n", + sysdb_name, groupnamelist[c]); + } + } + } + + ret = diff_string_lists(tmp_ctx, cached_local_parents, groupnamelist, + &del_list, &add_list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "diff_string_lists failed.\n"); + goto done; + } + + if (DEBUG_IS_SET(SSSDBG_TRACE_ALL)) { + if (add_list != NULL) { + for (c = 0; add_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "add: [%s] will be member of [%s].\n", + sysdb_name, add_list[c]); + } + } + if (del_list != NULL) { + for (c = 0; del_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "del: [%s] was member of [%s].\n", + sysdb_name, del_list[c]); + } + } + } + + *_type = type; + *_sysdb_name = talloc_steal(mem_ctx, sysdb_name); + *_add_list = talloc_steal(mem_ctx, groupnamelist); + *_del_list = talloc_steal(mem_ctx, del_list); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static void sdap_ad_get_domain_local_groups_done(struct tevent_req *subreq) +{ + + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_ad_get_domain_local_groups_state *state = tevent_req_data(req, + struct sdap_ad_get_domain_local_groups_state); + int ret; + int hret; + unsigned long count; + hash_value_t *values = NULL; + struct sdap_nested_group *gr; + size_t c; + const char *sysdb_name = NULL; + enum sysdb_member_type type; + char **add_list = NULL; + char **del_list = NULL; + + ret = rfc2307bis_nested_groups_recv(subreq); + talloc_zfree(subreq); + if (ret == ENOENT) { + /* In case of ENOENT we can just proceed without making + * sdap_get_initgr_user() fail because there's no nested + * groups for this user/group. */ + ret = EOK; + goto done; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + hret = hash_values(state->group_hash, &count, &values); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_values failed.\n"); + ret = EIO; + goto done; + } + + for (c = 0; c < count; c++) { + gr = talloc_get_type(values[c].ptr, + struct sdap_nested_group); + + /* The values from the hash are either user or group objects returned + * by sysdb_initgroups() which where used to start the request or + * nested parents found during the request. The nested parents contain + * the processed LDAP data and can be identified by a missing + * objectclass attribute. */ + ret = sdap_ad_get_domain_local_groups_parse_parents(state, gr, + state->dom, + state->sysdb, + state->opts, + &sysdb_name, + &type, + &add_list, + &del_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_get_domain_local_groups_parse_parents failed.\n"); + continue; + } + + if ((add_list == NULL && del_list == NULL) + || (add_list == NULL && del_list != NULL && del_list[0] == NULL) + || (add_list != NULL && add_list[0] == NULL && del_list == NULL) + || (add_list != NULL && add_list[0] == NULL + && del_list != NULL && del_list[0] == NULL) ) { + continue; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Updating domain local memberships for %s\n", + sysdb_name); + ret = sysdb_update_members(state->dom, sysdb_name, type, + (const char *const *) add_list, + (const char *const *) del_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_members failed.\n"); + goto done; + } + } + + ret = EOK; +done: + talloc_zfree(values); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +errno_t sdap_ad_get_domain_local_groups_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct sdap_ad_tokengroups_initgroups_state { + bool use_id_mapping; + bool use_shortcut; + struct sss_domain_info *domain; +}; + +static void sdap_ad_tokengroups_initgroups_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_ad_tokengroups_initgroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_handle *sh, + const char *name, + const char *orig_dn, + int timeout, + bool use_id_mapping) +{ + struct sdap_ad_tokengroups_initgroups_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + char **param = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ad_tokengroups_initgroups_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->use_id_mapping = use_id_mapping; + state->domain = domain; + + /* We can compute the gidNumber attribute from SIDs obtained from + * the tokenGroups lookup in case ID mapping is used for a user from the + * parent domain. For trusted domains, we need to know the group type + * to be able to filter out domain-local groups. Additionally, as a + * temporary workaround until https://fedorahosted.org/sssd/ticket/2656 + * is fixed, we also fetch the group object if group members are ignored + * to avoid having to transfer and retain members when the fake + * tokengroups object without name is replaced by the full group object + */ + state->use_shortcut = false; + if (state->use_id_mapping + && !IS_SUBDOMAIN(state->domain) + && !state->domain->ignore_group_members) { + ret = confdb_get_param(id_ctx->be->cdb, mem_ctx, id_ctx->be->conf_path, + CONFDB_NSS_FILTER_GROUPS, ¶m); + if (ret == EOK) { + state->use_shortcut = (param == NULL || param[0] == NULL); + talloc_free(param); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to access %s: %i (%s)\n", + CONFDB_NSS_FILTER_GROUPS, ret, sss_strerror(ret)); + /* Continue without using the shortcut. Safest option. */ + } + } + if (state->use_shortcut) { + subreq = sdap_ad_tokengroups_initgr_mapping_send(state, ev, opts, + sysdb, domain, sh, + name, orig_dn, + timeout); + } else { + subreq = sdap_ad_tokengroups_initgr_posix_send(state, ev, id_ctx, conn, + opts, sysdb, domain, sh, + name, orig_dn, + timeout); + } + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } else { + tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgroups_done, req); + } + + return req; +} + +static void sdap_ad_tokengroups_initgroups_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgroups_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ad_tokengroups_initgroups_state); + + if (state->use_shortcut) { + ret = sdap_ad_tokengroups_initgr_mapping_recv(subreq); + } else { + ret = sdap_ad_tokengroups_initgr_posix_recv(subreq); + } + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sdap_ad_tokengroups_initgroups_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t handle_missing_pvt(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + const char *orig_dn, + int timeout, + const char *username, + struct sdap_handle *sh, + struct tevent_req *req, + tevent_req_fn callback) +{ + struct tevent_req *subreq = NULL; + errno_t ret; + + if (sh != NULL) { + /* plain LDAP provider already has a sdap_handle */ + subreq = sdap_get_ad_tokengroups_send(mem_ctx, ev, opts, sh, username, + orig_dn, timeout); + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + goto done; + } + + tevent_req_set_callback(subreq, callback, req); + ret = EOK; + goto done; + + } else { + ret = EINVAL; + goto done; + } + +done: + return ret; +} + +struct sdap_id_conn_ctx *get_ldap_conn_from_sdom_pvt(struct sdap_options *opts, + struct sdap_domain *sdom) +{ + struct ad_id_ctx *ad_id_ctx; + struct sdap_id_conn_ctx *user_conn = NULL; + + if (opts->schema_type == SDAP_SCHEMA_AD && sdom->pvt != NULL) { + ad_id_ctx = talloc_get_type(sdom->pvt, struct ad_id_ctx); + if (ad_id_ctx != NULL && ad_id_ctx->ldap_ctx != NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "Returning LDAP connection for user lookup.\n"); + user_conn = ad_id_ctx->ldap_ctx; + } + } + + return user_conn; +} diff --git a/src/providers/ldap/sdap_async_iphost.c b/src/providers/ldap/sdap_async_iphost.c new file mode 100644 index 0000000..4b4dcad --- /dev/null +++ b/src/providers/ldap/sdap_async_iphost.c @@ -0,0 +1,640 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" +#include "db/sysdb_iphosts.h" + +struct sdap_get_iphost_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + bool enumeration; + + char *higher_usn; + struct sysdb_attrs **iphosts; + size_t count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t +sdap_get_iphost_next_base(struct tevent_req *req); + +struct tevent_req * +sdap_get_iphost_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration) +{ + struct sdap_get_iphost_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_get_iphost_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->sysdb = sysdb; + state->attrs = attrs; + state->higher_usn = NULL; + state->iphosts = NULL; + state->count = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + state->enumeration = enumeration; + + if (state->search_bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "IP host lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + ret = sdap_get_iphost_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + + return req; +} + +static void +sdap_get_iphost_process(struct tevent_req *subreq); + +static errno_t +sdap_get_iphost_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_iphost_state *state; + + state = tevent_req_data(req, struct sdap_get_iphost_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (state->filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for IP host with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->iphost_map, SDAP_OPTS_IPHOST, + state->timeout, + state->enumeration); /* If we're enumerating, we need paging */ + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_iphost_process, req); + + return EOK; +} + +static errno_t +sdap_save_iphosts(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **iphosts, + size_t num_hosts, + char **_usn_value); +static void +sdap_get_iphost_process(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_get_iphost_state *state; + int ret; + size_t count, i; + struct sysdb_attrs **hosts; + bool next_base = false; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_get_iphost_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &hosts); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Search for IP hosts returned %zu results.\n", + count); + + if (state->enumeration || count == 0) { + /* No hosts found in this search or enumerating */ + next_base = true; + } + + /* Add this batch of sevices to the list */ + if (count > 0) { + state->iphosts = talloc_realloc(state, state->iphosts, + struct sysdb_attrs *, + state->count + count + 1); + if (state->iphosts == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Steal the new hosts into the list */ + for (i = 0; i < count; i++) { + state->iphosts[state->count + i] = + talloc_steal(state->iphosts, hosts[i]); + } + + state->count += count; + state->iphosts[state->count] = NULL; + } + + if (next_base) { + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_iphost_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + } + + /* No more search bases + * Return ENOENT if no hosts were found + */ + if (state->count == 0) { + tevent_req_error(req, ENOENT); + return; + } + + ret = sdap_save_iphosts(state, state->sysdb, state->dom, state->opts, + state->iphosts, state->count, &state->higher_usn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to store IP hosts.\n"); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Saved %zu IP hosts\n", state->count); + + tevent_req_done(req); +} + +static errno_t +sdap_save_iphost(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now); + +static errno_t +sdap_save_iphosts(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **hosts, + size_t num_hosts, + char **_usn_value) +{ + errno_t ret, sret; + time_t now; + size_t i; + bool in_transaction = false; + char *higher_usn = NULL; + char *usn_value; + TALLOC_CTX *tmp_ctx; + + if (num_hosts == 0) { + /* Nothing to do */ + return ENOENT; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + + in_transaction = true; + + now = time(NULL); + for (i = 0; i < num_hosts; i++) { + usn_value = NULL; + + ret = sdap_save_iphost(tmp_ctx, sysdb, opts, dom, hosts[i], + &usn_value, now); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store IP host %zu. Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "IP host [%zu/%zu] saved\n", i, num_hosts); + } + + if (usn_value != NULL) { + if (higher_usn != NULL) { + if ((strlen(usn_value) > strlen(higher_usn)) || + (strcmp(usn_value, higher_usn) > 0)) { + talloc_zfree(higher_usn); + higher_usn = usn_value; + } else { + talloc_zfree(usn_value); + } + } else { + higher_usn = usn_value; + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + if (_usn_value != NULL) { + *_usn_value = talloc_steal(mem_ctx, higher_usn); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to cancel transaction!\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sdap_save_iphost(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct sysdb_attrs *host_attrs; + struct ldb_message_element *el; + char *usn_value = NULL; + const char *name = NULL; + const char **aliases = NULL; + const char **addresses = NULL; + const char **cased_aliases = NULL; + const char **cased_addresses = NULL; + const char **store_aliases = NULL; + const char **store_addresses = NULL; + char **missing; + uint64_t cache_timeout; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + host_attrs = sysdb_new_attrs(tmp_ctx); + if (host_attrs == NULL) { + ret = ENOMEM; + goto done; + } + + /* Identify the primary name of this hosts */ + ret = sdap_get_primary_name(opts->iphost_map[SDAP_AT_IPHOST_NAME].name, + attrs, &name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not determine the primary name of the IP host\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "IP host primary name: [%s]\n", name); + + /* Handle any available aliases */ + ret = sysdb_attrs_get_aliases(tmp_ctx, attrs, name, + !dom->case_sensitive, + &aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify IP host aliases: [%s]\n", + strerror(ret)); + goto done; + } + + /* Get the addresses */ + ret = sysdb_attrs_get_string_array(attrs, SYSDB_IP_HOST_ATTR_ADDRESS, + tmp_ctx, &addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify IP host addresses: [%s]\n", + strerror(ret)); + goto done; + } + + if (dom->case_sensitive == false) { + /* Don't perform the extra mallocs if not necessary */ + ret = sss_get_cased_name_list(tmp_ctx, aliases, + dom->case_sensitive, &cased_aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get case_sensitive aliases: [%s]\n", + strerror(ret)); + goto done; + } + + ret = sss_get_cased_name_list(tmp_ctx, addresses, + dom->case_sensitive, &cased_addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get case_sensitive addresses: [%s]\n", + strerror(ret)); + goto done; + } + } + + store_aliases = dom->case_sensitive ? aliases : cased_aliases; + store_addresses = dom->case_sensitive ? addresses : cased_addresses; + + /* Get the USN value, if available */ + ret = sysdb_attrs_get_el(attrs, + opts->iphost_map[SDAP_AT_IPHOST_USN].sys_name, + &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to retrieve USN value: [%s]\n", + strerror(ret)); + goto done; + } + + if (ret == ENOENT || el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Original USN value is not available for [%s].\n", + name); + } else { + ret = sysdb_attrs_add_string(host_attrs, + opts->iphost_map[SDAP_AT_IPHOST_USN].sys_name, + (const char*)el->values[0].data); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add USN value: [%s]\n", + strerror(ret)); + goto done; + } + usn_value = talloc_strdup(tmp_ctx, (const char*)el->values[0].data); + if (usn_value == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* Make sure to remove any extra attributes from the sysdb + * that have been removed from LDAP + */ + ret = list_missing_attrs(host_attrs, opts->iphost_map, SDAP_OPTS_IPHOST, + attrs, &missing); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify removed attributes: [%s]\n", + strerror(ret)); + goto done; + } + + cache_timeout = dom->resolver_timeout; + + ret = sysdb_store_host(dom, name, store_aliases, store_addresses, + host_attrs, missing, cache_timeout, now); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store IP host in the sysdb: [%s]\n", + strerror(ret)); + goto done; + } + + *_usn_value = talloc_steal(mem_ctx, usn_value); + +done: + talloc_free(tmp_ctx); + return ret; + +} + +errno_t +sdap_get_iphost_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value) +{ + struct sdap_get_iphost_state *state; + + state = tevent_req_data(req, struct sdap_get_iphost_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (usn_value != NULL) { + *usn_value = talloc_steal(mem_ctx, state->higher_usn); + } + + return EOK; +} + +/* Enumeration routines */ + +struct enum_iphosts_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_op *op; + struct sss_domain_info *domain; + struct sysdb_ctx *sysdb; + + char *filter; + const char **attrs; +}; + +static void +enum_iphosts_op_done(struct tevent_req *subreq); + +struct tevent_req * +enum_iphosts_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct enum_iphosts_state *state; + + req = tevent_req_create(memctx, &state, struct enum_iphosts_state); + if (!req) return NULL; + + state->ev = ev; + state->id_ctx = id_ctx; + state->domain = id_ctx->be->domain; + state->sysdb = id_ctx->be->domain->sysdb; + state->op = op; + + if (id_ctx->srv_opts && id_ctx->srv_opts->max_iphost_value && !purge) { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*)(%s>=%s)(!(%s=%s)))", + id_ctx->opts->iphost_map[SDAP_OC_IPHOST].name, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NAME].name, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NUMBER].name, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_USN].name, + id_ctx->srv_opts->max_iphost_value, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_USN].name, + id_ctx->srv_opts->max_iphost_value); + } else { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*))", + id_ctx->opts->iphost_map[SDAP_OC_IPHOST].name, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NAME].name, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NUMBER].name); + } + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, id_ctx->opts->iphost_map, + SDAP_OPTS_IPHOST, NULL, + &state->attrs, NULL); + if (ret != EOK) { + goto fail; + } + + subreq = sdap_get_iphost_send(state, state->ev, + state->domain, state->sysdb, + state->id_ctx->opts, + state->id_ctx->opts->sdom->iphost_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_iphosts_op_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +enum_iphosts_op_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct enum_iphosts_state *state = + tevent_req_data(req, struct enum_iphosts_state); + char *usn_value = NULL; + char *endptr = NULL; + unsigned usn_number; + int ret; + + ret = sdap_get_iphost_recv(state, subreq, &usn_value); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (usn_value) { + talloc_zfree(state->id_ctx->srv_opts->max_iphost_value); + state->id_ctx->srv_opts->max_iphost_value = + talloc_steal(state->id_ctx, usn_value); + errno = 0; + usn_number = strtoul(usn_value, &endptr, 10); + if (!errno && endptr && (*endptr == '\0') && (endptr != usn_value) + && (usn_number > state->id_ctx->srv_opts->last_usn)) { + state->id_ctx->srv_opts->last_usn = usn_number; + } + } + + DEBUG(SSSDBG_FUNC_DATA, "IP host higher USN value: [%s]\n", + state->id_ctx->srv_opts->max_iphost_value); + + tevent_req_done(req); +} + +errno_t +enum_iphosts_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_ipnetwork.c b/src/providers/ldap/sdap_async_ipnetwork.c new file mode 100644 index 0000000..5e5b181 --- /dev/null +++ b/src/providers/ldap/sdap_async_ipnetwork.c @@ -0,0 +1,625 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" +#include "db/sysdb_ipnetworks.h" + +struct sdap_get_ipnetwork_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + bool enumeration; + + char *higher_usn; + struct sysdb_attrs **entries; + size_t num_entries; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t +sdap_get_ipnetwork_next_base(struct tevent_req *req); + +struct tevent_req * +sdap_get_ipnetwork_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration) +{ + struct sdap_get_ipnetwork_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_get_ipnetwork_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->sysdb = sysdb; + state->attrs = attrs; + state->higher_usn = NULL; + state->entries = NULL; + state->num_entries = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + state->enumeration = enumeration; + + if (state->search_bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "IP network lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + ret = sdap_get_ipnetwork_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + + return req; +} + +static void +sdap_get_ipnetwork_process(struct tevent_req *subreq); + +static errno_t +sdap_get_ipnetwork_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_ipnetwork_state *state; + + state = tevent_req_data(req, struct sdap_get_ipnetwork_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (state->filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for IP network with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->ipnetwork_map, + SDAP_OPTS_IPNETWORK, + state->timeout, + state->enumeration); /* If we're enumerating, we need paging */ + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_ipnetwork_process, req); + + return EOK; +} + +static errno_t +sdap_save_ipnetworks(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **entries, + size_t num_entries, + char **_usn_value); + +static void +sdap_get_ipnetwork_process(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_get_ipnetwork_state *state; + int ret; + size_t count, i; + struct sysdb_attrs **entries; + bool next_base = false; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_get_ipnetwork_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &entries); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Search for IP networks returned %zu results.\n", + count); + + if (state->enumeration || count == 0) { + /* No entries found in this search or enumerating */ + next_base = true; + } + + /* Add this batch of entries to the list */ + if (count > 0) { + state->entries = talloc_realloc(state, state->entries, + struct sysdb_attrs *, + state->num_entries + count + 1); + if (state->entries == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Steal the new entries into the list */ + for (i = 0; i < count; i++) { + state->entries[state->num_entries + i] = + talloc_steal(state->entries, entries[i]); + } + + state->num_entries += count; + state->entries[state->num_entries] = NULL; + } + + if (next_base) { + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_ipnetwork_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + } + + /* No more search bases + * Return ENOENT if no entries were found + */ + if (state->num_entries == 0) { + tevent_req_error(req, ENOENT); + return; + } + + ret = sdap_save_ipnetworks(state, state->sysdb, state->dom, state->opts, + state->entries, state->num_entries, + &state->higher_usn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to store IP networks.\n"); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Saved %zu IP networks\n", state->num_entries); + + tevent_req_done(req); +} + + +static errno_t +sdap_save_ipnetwork(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now); + +static errno_t +sdap_save_ipnetworks(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **entries, + size_t num_entries, + char **_usn_value) +{ + errno_t ret, sret; + time_t now; + size_t i; + bool in_transaction = false; + char *higher_usn = NULL; + char *usn_value; + TALLOC_CTX *tmp_ctx; + + if (num_entries == 0) { + /* Nothing to do */ + return ENOENT; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + + in_transaction = true; + + now = time(NULL); + for (i = 0; i < num_entries; i++) { + usn_value = NULL; + + ret = sdap_save_ipnetwork(tmp_ctx, sysdb, opts, dom, entries[i], + &usn_value, now); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store IP network %zu. Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "IP network [%zu/%zu] saved\n", i, num_entries); + } + + if (usn_value != NULL) { + if (higher_usn != NULL) { + if ((strlen(usn_value) > strlen(higher_usn)) || + (strcmp(usn_value, higher_usn) > 0)) { + talloc_zfree(higher_usn); + higher_usn = usn_value; + } else { + talloc_zfree(usn_value); + } + } else { + higher_usn = usn_value; + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + if (_usn_value != NULL) { + *_usn_value = talloc_steal(mem_ctx, higher_usn); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to cancel transaction!\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sdap_save_ipnetwork(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct sysdb_attrs *net_attrs; + struct ldb_message_element *el; + char *usn_value = NULL; + const char *name = NULL; + const char *address = NULL; + const char **aliases = NULL; + const char **cased_aliases = NULL; + const char **store_aliases = NULL; + char **missing; + uint64_t cache_timeout; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + net_attrs = sysdb_new_attrs(tmp_ctx); + if (net_attrs == NULL) { + ret = ENOMEM; + goto done; + } + + /* Identify the primary name of this network */ + ret = sdap_get_primary_name(opts->ipnetwork_map[SDAP_AT_IPNETWORK_NAME].name, + attrs, &name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not determine the primary name of the IP network\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "IP network primary name: [%s]\n", name); + + /* Handle any available aliases */ + ret = sysdb_attrs_get_aliases(tmp_ctx, attrs, name, + !dom->case_sensitive, + &aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify IP network aliases: [%s]\n", + strerror(ret)); + goto done; + } + + /* Get the address */ + ret = sysdb_attrs_get_string(attrs, SYSDB_IP_NETWORK_ATTR_NUMBER, &address); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify IP network number: [%s]\n", + strerror(ret)); + goto done; + } + + if (dom->case_sensitive == false) { + /* Don't perform the extra mallocs if not necessary */ + ret = sss_get_cased_name_list(tmp_ctx, aliases, + dom->case_sensitive, &cased_aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get case_sensitive aliases: [%s]\n", + strerror(ret)); + goto done; + } + } + + store_aliases = dom->case_sensitive ? aliases : cased_aliases; + + /* Get the USN value, if available */ + ret = sysdb_attrs_get_el(attrs, + opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].sys_name, + &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to retrieve USN value: [%s]\n", + strerror(ret)); + goto done; + } + + if (ret == ENOENT || el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Original USN value is not available for [%s].\n", + name); + } else { + ret = sysdb_attrs_add_string(net_attrs, + opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].sys_name, + (const char*)el->values[0].data); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add USN value: [%s]\n", + strerror(ret)); + goto done; + } + usn_value = talloc_strdup(tmp_ctx, (const char*)el->values[0].data); + if (usn_value == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* Make sure to remove any extra attributes from the sysdb + * that have been removed from LDAP + */ + ret = list_missing_attrs(net_attrs, opts->ipnetwork_map, + SDAP_OPTS_IPNETWORK, + attrs, &missing); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify removed attributes: [%s]\n", + strerror(ret)); + goto done; + } + + cache_timeout = dom->resolver_timeout; + + ret = sysdb_store_ipnetwork(dom, name, store_aliases, address, + net_attrs, missing, cache_timeout, now); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store IP network in the sysdb: [%s]\n", + strerror(ret)); + goto done; + } + + *_usn_value = talloc_steal(mem_ctx, usn_value); + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +sdap_get_ipnetwork_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value) +{ + /* TODO */ + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* Enumeration routines */ + +struct enum_ipnetworks_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_op *op; + struct sss_domain_info *domain; + struct sysdb_ctx *sysdb; + + char *filter; + const char **attrs; +}; + +static void +enum_ipnetworks_op_done(struct tevent_req *subreq); + +struct tevent_req * +enum_ipnetworks_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct enum_ipnetworks_state *state; + + req = tevent_req_create(memctx, &state, struct enum_ipnetworks_state); + if (!req) return NULL; + + state->ev = ev; + state->id_ctx = id_ctx; + state->domain = id_ctx->be->domain; + state->sysdb = id_ctx->be->domain->sysdb; + state->op = op; + + if (id_ctx->srv_opts && id_ctx->srv_opts->max_ipnetwork_value && !purge) { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*)(%s>=%s)(!(%s=%s)))", + id_ctx->opts->ipnetwork_map[SDAP_OC_IPNETWORK].name, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NAME].name, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NUMBER].name, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].name, + id_ctx->srv_opts->max_ipnetwork_value, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].name, + id_ctx->srv_opts->max_ipnetwork_value); + } else { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*))", + id_ctx->opts->ipnetwork_map[SDAP_OC_IPNETWORK].name, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NAME].name, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NUMBER].name); + } + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, id_ctx->opts->ipnetwork_map, + SDAP_OPTS_IPNETWORK, NULL, + &state->attrs, NULL); + if (ret != EOK) { + goto fail; + } + + subreq = sdap_get_ipnetwork_send(state, state->ev, + state->domain, state->sysdb, state->id_ctx->opts, + state->id_ctx->opts->sdom->ipnetwork_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_ipnetworks_op_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +enum_ipnetworks_op_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct enum_ipnetworks_state *state = + tevent_req_data(req, struct enum_ipnetworks_state); + char *usn_value = NULL; + char *endptr = NULL; + unsigned usn_number; + int ret; + + ret = sdap_get_ipnetwork_recv(state, subreq, &usn_value); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (usn_value) { + talloc_zfree(state->id_ctx->srv_opts->max_ipnetwork_value); + state->id_ctx->srv_opts->max_ipnetwork_value = + talloc_steal(state->id_ctx, usn_value); + errno = 0; + usn_number = strtoul(usn_value, &endptr, 10); + if (!errno && endptr && (*endptr == '\0') && (endptr != usn_value) + && (usn_number > state->id_ctx->srv_opts->last_usn)) { + state->id_ctx->srv_opts->last_usn = usn_number; + } + } + + DEBUG(SSSDBG_FUNC_DATA, "IP network higher USN value: [%s]\n", + state->id_ctx->srv_opts->max_ipnetwork_value); + + tevent_req_done(req); +} + +errno_t +enum_ipnetworks_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_nested_groups.c b/src/providers/ldap/sdap_async_nested_groups.c new file mode 100644 index 0000000..2e3b0c4 --- /dev/null +++ b/src/providers/ldap/sdap_async_nested_groups.c @@ -0,0 +1,2997 @@ +/* + SSSD + + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "util/probes.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ipa/ipa_dn.h" + +#define sdap_nested_group_sysdb_search_users(domain, dn) \ + sdap_nested_group_sysdb_search((domain), (dn), true) + +#define sdap_nested_group_sysdb_search_groups(domain, dn) \ + sdap_nested_group_sysdb_search((domain), (dn), false) + +enum sdap_nested_group_dn_type { + SDAP_NESTED_GROUP_DN_USER, + SDAP_NESTED_GROUP_DN_GROUP, + SDAP_NESTED_GROUP_DN_UNKNOWN +}; + +struct sdap_nested_group_member { + enum sdap_nested_group_dn_type type; + const char *dn; + const char *user_filter; + const char *group_filter; +}; + +#ifndef EXTERNAL_MEMBERS_CHUNK +#define EXTERNAL_MEMBERS_CHUNK 16 +#endif /* EXTERNAL_MEMBERS_CHUNK */ + +struct sdap_external_missing_member { + const char **parent_group_dns; + size_t parent_dn_idx; +}; + +struct sdap_nested_group_ctx { + struct sss_domain_info *domain; + struct sdap_options *opts; + struct sdap_search_base **user_search_bases; + struct sdap_search_base **group_search_bases; + struct sdap_search_base **ignore_user_search_bases; + struct sdap_handle *sh; + hash_table_t *users; + hash_table_t *groups; + hash_table_t *missing_external; + bool try_deref; + int deref_threshold; + int max_nesting_level; +}; + +static struct tevent_req * +sdap_nested_group_process_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + int nesting_level, + struct sysdb_attrs *group); + +static errno_t sdap_nested_group_process_recv(struct tevent_req *req); + +static struct tevent_req * +sdap_nested_group_single_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *members, + int num_members, + int num_groups_max, + int nesting_level); + +static errno_t sdap_nested_group_single_recv(struct tevent_req *req); + +static struct tevent_req * +sdap_nested_group_lookup_user_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member); + +static errno_t sdap_nested_group_lookup_user_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_user); + +static struct tevent_req * +sdap_nested_group_lookup_group_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member); + +static errno_t sdap_nested_group_lookup_group_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_group); + +static struct tevent_req * +sdap_nested_group_lookup_unknown_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member); + +static errno_t +sdap_nested_group_lookup_unknown_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_entry, + enum sdap_nested_group_dn_type *_type); + +static struct tevent_req * +sdap_nested_group_deref_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct ldb_message_element *members, + const char *group_dn, + int nesting_level); + +static errno_t sdap_nested_group_deref_recv(struct tevent_req *req); + +static errno_t +sdap_nested_group_extract_hash_table(TALLOC_CTX *mem_ctx, + hash_table_t *table, + unsigned long *_num_entries, + struct sysdb_attrs ***_entries) +{ + struct sysdb_attrs **entries = NULL; + struct sysdb_attrs *entry = NULL; + hash_value_t *values; + unsigned long num_entries; + unsigned int i; + bool hret; + errno_t ret; + + hret = hash_values(table, &num_entries, &values); + if (hret != HASH_SUCCESS) { + ret = EIO; + goto done; + } + + if (num_entries > 0) { + entries = talloc_array(mem_ctx, struct sysdb_attrs *, num_entries); + if (entries == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_entries; i++) { + entry = talloc_get_type(values[i].ptr, struct sysdb_attrs); + entries[i] = talloc_steal(entries, entry); + } + } + + if (_num_entries != NULL) { + *_num_entries = num_entries; + } + + if (_entries != NULL) { + *_entries = entries; + } + + ret = EOK; + +done: + talloc_free(values); + + if (ret != EOK) { + talloc_free(entries); + } + + return ret; +} + +static errno_t sdap_nested_group_hash_insert(hash_table_t *table, + const char *entry_key, + void *entry_value, + bool overwrite, + const char *table_name) +{ + hash_key_t key; + hash_value_t value; + int hret; + + DEBUG(SSSDBG_TRACE_ALL, "Inserting [%s] into hash table [%s]\n", + entry_key, table_name); + + key.type = HASH_KEY_STRING; + key.c_str = discard_const(entry_key); /* hash_enter() will make a copy */ + + if (overwrite == false && hash_has_key(table, &key)) { + return EEXIST; + } + + value.type = HASH_VALUE_PTR; + value.ptr = entry_value; + + hret = hash_enter(table, &key, &value); + if (hret != HASH_SUCCESS) { + return EIO; + } + + talloc_steal(table, value.ptr); + + return EOK; +} + +static errno_t sdap_nested_group_hash_entry(hash_table_t *table, + struct sysdb_attrs *entry, + const char *table_name) +{ + const char *name = NULL; + errno_t ret; + + ret = sysdb_attrs_get_string(entry, SYSDB_DN_FOR_MEMBER_HASH_TABLE, &name); + if (ret != EOK) { + ret = sysdb_attrs_get_string(entry, SYSDB_ORIG_DN, &name); + if (ret != EOK) { + return ret; + } + } + + return sdap_nested_group_hash_insert(table, name, entry, false, table_name); +} + +static errno_t +sdap_nested_group_hash_user(struct sdap_nested_group_ctx *group_ctx, + struct sysdb_attrs *user) +{ + return sdap_nested_group_hash_entry(group_ctx->users, user, "users"); +} + +static errno_t +sdap_nested_group_hash_group(struct sdap_nested_group_ctx *group_ctx, + struct sysdb_attrs *group) +{ + struct sdap_attr_map *map = group_ctx->opts->group_map; + gid_t gid = 0; + errno_t ret; + bool posix_group = true; + bool use_id_mapping; + bool can_find_gid; + bool need_filter; + + ret = sdap_check_ad_group_type(group_ctx->domain, group_ctx->opts, + group, "", &need_filter); + if (ret != EOK) { + return ret; + } + + if (need_filter) { + posix_group = false; + gid = 0; + } + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + group_ctx->opts->idmap_ctx, + group_ctx->domain->name, + group_ctx->domain->domain_id); + + can_find_gid = posix_group && !use_id_mapping; + if (can_find_gid) { + ret = sysdb_attrs_get_uint32_t(group, map[SDAP_AT_GROUP_GID].sys_name, + &gid); + } + if (!can_find_gid || ret == ENOENT || (ret == EOK && gid == 0)) { + DEBUG(SSSDBG_TRACE_ALL, + "The group's gid was %s\n", ret == ENOENT ? "missing" : "zero"); + DEBUG(SSSDBG_TRACE_INTERNAL, + "Marking group as non-POSIX and setting GID=0!\n"); + + if (ret == ENOENT || !posix_group) { + ret = sysdb_attrs_add_uint32(group, + map[SDAP_AT_GROUP_GID].sys_name, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add a GID to non-POSIX group!\n"); + return ret; + } + } + + ret = sysdb_attrs_add_bool(group, SYSDB_POSIX, false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Error: Failed to mark group as non-POSIX!\n"); + return ret; + } + } else if (ret != EOK) { + return ret; + } + + return sdap_nested_group_hash_entry(group_ctx->groups, group, "groups"); +} + +static errno_t sdap_nested_group_external_add(hash_table_t *table, + const char *ext_member, + const char *parent_group_dn) +{ + hash_key_t key; + hash_value_t value; + int hret; + int ret; + struct sdap_external_missing_member *ext_mem; + + key.type = HASH_KEY_STRING; + key.str = discard_const(ext_member); + + DEBUG(SSSDBG_TRACE_ALL, + "Inserting external member [%s] into external members hash table\n", + ext_member); + + hret = hash_lookup(table, &key, &value); + switch (hret) { + case HASH_ERROR_KEY_NOT_FOUND: + ext_mem = talloc_zero(table, struct sdap_external_missing_member); + if (ext_mem == NULL) { + return ENOMEM; + } + ext_mem->parent_group_dns = talloc_zero_array(ext_mem, + const char *, + EXTERNAL_MEMBERS_CHUNK); + if (ext_mem->parent_group_dns == NULL) { + talloc_free(ext_mem); + return ENOMEM; + } + + ret = sdap_nested_group_hash_insert(table, ext_member, ext_mem, + true, "missing external users"); + if (ret != EOK) { + return ret; + } + break; + + case HASH_SUCCESS: + ext_mem = talloc_get_type(value.ptr, + struct sdap_external_missing_member); + if (ext_mem->parent_dn_idx == \ + talloc_array_length(ext_mem->parent_group_dns)) { + ext_mem->parent_group_dns = talloc_realloc(ext_mem, + ext_mem->parent_group_dns, + const char *, + ext_mem->parent_dn_idx + \ + EXTERNAL_MEMBERS_CHUNK); + if (ext_mem->parent_group_dns == NULL) { + talloc_free(ext_mem); + return ENOMEM; + } + } + break; + default: + return EIO; + } + + ext_mem->parent_group_dns[ext_mem->parent_dn_idx] = \ + talloc_strdup(ext_mem->parent_group_dns, + parent_group_dn); + if (ext_mem->parent_group_dns[ext_mem->parent_dn_idx] == NULL) { + return ENOMEM; + } + ext_mem->parent_dn_idx++; + + return EOK; +} + +static errno_t sdap_nested_group_sysdb_search(struct sss_domain_info *domain, + const char *dn, + bool user) +{ + static const char *attrs[] = {SYSDB_CACHE_EXPIRE, + SYSDB_UIDNUM, + NULL}; + struct ldb_message **msgs = NULL; + size_t count; + time_t now = time(NULL); + uint64_t expire; + uid_t uid; + errno_t ret; + + if (user) { + ret = sysdb_search_users_by_orig_dn(NULL, domain, dn, attrs, + &count, &msgs); + } else { + ret = sysdb_search_groups_by_orig_dn(NULL, domain, dn, attrs, + &count, &msgs); + } + if (ret != EOK) { + goto done; + } + + if (count != 1) { + DEBUG(SSSDBG_OP_FAILURE, "More than one entry found?\n"); + ret = EFAULT; + goto done; + } + + /* we found an object with this origDN in the sysdb, + * check if it is valid */ + if (user) { + uid = ldb_msg_find_attr_as_uint64(msgs[0], SYSDB_UIDNUM, 0); + if (uid == 0) { + DEBUG(SSSDBG_OP_FAILURE, "User with no UID?\n"); + ret = EINVAL; + goto done; + } + } + + expire = ldb_msg_find_attr_as_uint64(msgs[0], SYSDB_CACHE_EXPIRE, 0); + if (expire != 0 && expire <= now) { + /* needs refresh */ + ret = EAGAIN; + goto done; + } + + /* valid object */ + ret = EOK; + +done: + talloc_zfree(msgs); + return ret; +} + +static errno_t +sdap_nested_group_check_cache(struct sdap_options *opts, + struct sss_domain_info *domain, + const char *member_dn, + enum sdap_nested_group_dn_type *_type) +{ + struct sdap_domain *sdap_domain = NULL; + struct sss_domain_info *member_domain = NULL; + errno_t ret; + + /* determine correct domain of this member */ + sdap_domain = sdap_domain_get_by_dn(opts, member_dn); + member_domain = sdap_domain == NULL ? domain : sdap_domain->dom; + + /* search in users */ + PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_USERS_PRE); + ret = sdap_nested_group_sysdb_search_users(member_domain, member_dn); + PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_USERS_POST); + if (ret == EOK || ret == EAGAIN) { + /* user found */ + *_type = SDAP_NESTED_GROUP_DN_USER; + goto done; + } else if (ret != ENOENT) { + /* error */ + goto done; + } + + /* search in groups */ + PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_GROUPS_PRE); + ret = sdap_nested_group_sysdb_search_groups(member_domain, member_dn); + PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_GROUPS_POST); + if (ret == EOK || ret == EAGAIN) { + /* group found */ + *_type = SDAP_NESTED_GROUP_DN_GROUP; + goto done; + } else if (ret != ENOENT) { + /* error */ + goto done; + } + + /* not found in the sysdb */ + ret = ENOENT; + +done: + return ret; +} + +static bool +sdap_nested_member_is_ent(struct sdap_nested_group_ctx *group_ctx, + const char *dn, char **filter, bool is_user) +{ + struct sdap_domain *sditer = NULL; + bool ret = false; + struct sdap_search_base **search_bases; + + DLIST_FOR_EACH(sditer, group_ctx->opts->sdom) { + search_bases = is_user ? sditer->user_search_bases : \ + sditer->group_search_bases; + + ret = sss_ldap_dn_in_search_bases(group_ctx, dn, search_bases, + filter); + if (ret == true) { + break; + } + } + + return ret; +} + +static inline bool +sdap_nested_member_is_user(struct sdap_nested_group_ctx *group_ctx, + const char *dn, char **filter) +{ + return sdap_nested_member_is_ent(group_ctx, dn, filter, true); +} + +static inline bool +sdap_nested_member_is_group(struct sdap_nested_group_ctx *group_ctx, + const char *dn, char **filter) +{ + return sdap_nested_member_is_ent(group_ctx, dn, filter, false); +} + +static errno_t +sdap_nested_group_split_members(TALLOC_CTX *mem_ctx, + struct sdap_nested_group_ctx *group_ctx, + int threshold, + int nesting_level, + struct ldb_message_element *members, + struct sdap_nested_group_member **_missing, + int *_num_missing, + int *_num_groups) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct sdap_nested_group_member *missing = NULL; + enum sdap_nested_group_dn_type type; + char *dn = NULL; + char *user_filter = NULL; + char *group_filter = NULL; + int num_missing = 0; + int num_groups = 0; + hash_key_t key; + bool bret; + bool is_user; + bool is_group; + errno_t ret; + int i; + + if (members == NULL) { + *_missing = NULL; + *_num_missing = 0; + *_num_groups = 0; + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + missing = talloc_zero_array(tmp_ctx, struct sdap_nested_group_member, + members->num_values); + if (missing == NULL) { + ret = ENOMEM; + goto done; + } + + /* create list of missing members + * skip dn if: + * - is present in user or group hash table + * - is present in sysdb and not expired + * - it is a group and we have reached the maximal nesting level + * - it is not under user nor group search bases + * + * if dn is in sysdb but expired + * - we know what object type it is + * + * if dn is not in hash table or sysdb + * - try to determine type of object by search base that match dn + */ + for (i = 0; i < members->num_values; i++) { + dn = (char*)members->values[i].data; + type = SDAP_NESTED_GROUP_DN_UNKNOWN; + + /* check hash tables */ + key.type = HASH_KEY_STRING; + key.str = dn; + + bret = hash_has_key(group_ctx->users, &key); + if (bret) { + continue; + } + + bret = hash_has_key(group_ctx->groups, &key); + if (bret) { + continue; + } + + /* check sysdb */ + PROBE(SDAP_NESTED_GROUP_CHECK_CACHE_PRE); + ret = sdap_nested_group_check_cache(group_ctx->opts, group_ctx->domain, + dn, &type); + PROBE(SDAP_NESTED_GROUP_CHECK_CACHE_POST); + if (ret == EOK) { + /* found and valid */ + DEBUG(SSSDBG_TRACE_ALL, "[%s] found in cache, skipping\n", dn); + continue; + } else if (ret != EAGAIN && ret != ENOENT) { + /* error */ + goto done; + } + + /* try to determine type by dn */ + if (type == SDAP_NESTED_GROUP_DN_UNKNOWN) { + /* user */ + is_user = sdap_nested_member_is_user(group_ctx, dn, + &user_filter); + + is_group = sdap_nested_member_is_group(group_ctx, dn, + &group_filter); + + if (is_user && is_group) { + /* search bases overlap */ + DEBUG(SSSDBG_TRACE_ALL, "[%s] is unknown object\n", dn); + type = SDAP_NESTED_GROUP_DN_UNKNOWN; + } else if (is_user) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] is a user\n", dn); + type = SDAP_NESTED_GROUP_DN_USER; + } else if (is_group) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] is a group\n", dn); + type = SDAP_NESTED_GROUP_DN_GROUP; + } else { + /* dn is outside search bases */ + DEBUG(SSSDBG_TRACE_ALL, "[%s] is out of scope of configured " + "search bases, skipping\n", dn); + continue; + } + } + + /* check nesting level */ + if (type == SDAP_NESTED_GROUP_DN_GROUP) { + if (nesting_level >= group_ctx->max_nesting_level) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] is outside nesting limit " + "(level %d), skipping\n", dn, nesting_level); + talloc_zfree(user_filter); + talloc_zfree(group_filter); + continue; + } + } + + missing[num_missing].dn = talloc_strdup(missing, dn); + if (missing[num_missing].dn == NULL) { + ret = ENOMEM; + goto done; + } + + missing[num_missing].type = type; + missing[num_missing].user_filter = talloc_steal(missing, user_filter); + missing[num_missing].group_filter = talloc_steal(missing, group_filter); + + num_missing++; + if (threshold > 0 && num_missing > threshold) { + if (_num_missing) { + *_num_missing = num_missing; + } + + ret = ERR_DEREF_THRESHOLD; + goto done; + } + + if (type != SDAP_NESTED_GROUP_DN_USER) { + num_groups++; + } + } + + missing = talloc_realloc(mem_ctx, missing, + struct sdap_nested_group_member, num_missing); + /* talloc_realloc behaves as talloc_free if 3rd parameter (count) is 0, + * so it's OK to return NULL then + */ + if (missing == NULL && num_missing > 0) { + ret = ENOMEM; + goto done; + } + + if (_missing) { + *_missing = talloc_steal(mem_ctx, missing); + } + + if (_num_missing) { + *_num_missing = num_missing; + } + + if (_num_groups) { + *_num_groups = num_groups; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sdap_nested_group_add_ext_members(struct sdap_nested_group_ctx *group_ctx, + struct sysdb_attrs *group, + struct ldb_message_element *ext_members) +{ + errno_t ret; + const char *ext_member_attr; + const char *orig_dn; + + if (ext_members == NULL) { + return EOK; + } + + ret = sysdb_attrs_get_string(group, SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "A group with no originalDN!?!\n"); + return ret; + } + + for (size_t i = 0; i < ext_members->num_values; i++) { + ext_member_attr = (const char *) ext_members->values[i].data; + + ret = sdap_nested_group_external_add(group_ctx->missing_external, + ext_member_attr, + orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot add %s into external members [%d]: %s\n", + ext_member_attr, ret, sss_strerror(ret)); + return ret; + } + } + + return EOK; +} + +static struct ldb_message_element * +sdap_nested_group_ext_members(struct sdap_options *opts, + struct sysdb_attrs *group) +{ + errno_t ret; + struct ldb_message_element *ext_members = NULL; + + if (opts->ext_ctx == NULL) { + return NULL; + } + + ret = sysdb_attrs_get_el_ext(group, + opts->group_map[SDAP_AT_GROUP_EXT_MEMBER].sys_name, + false, &ext_members); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve external member list " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + return ext_members; +} + + +struct sdap_nested_group_state { + struct sdap_nested_group_ctx *group_ctx; +}; + +static void sdap_nested_group_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_nested_group_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sysdb_attrs *group) +{ + struct sdap_nested_group_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + int i; + + PROBE(SDAP_NESTED_GROUP_SEND); + + req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + /* create main nested group context */ + state->group_ctx = talloc_zero(state, struct sdap_nested_group_ctx); + if (state->group_ctx == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = sss_hash_create(state->group_ctx, 0, &state->group_ctx->users); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, strerror(ret)); + goto immediately; + } + + ret = sss_hash_create(state->group_ctx, 0, &state->group_ctx->groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, strerror(ret)); + goto immediately; + } + + ret = sss_hash_create(state->group_ctx, 0, + &state->group_ctx->missing_external); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, strerror(ret)); + goto immediately; + } + + state->group_ctx->try_deref = true; + state->group_ctx->deref_threshold = dp_opt_get_int(opts->basic, + SDAP_DEREF_THRESHOLD); + state->group_ctx->max_nesting_level = dp_opt_get_int(opts->basic, + SDAP_NESTING_LEVEL); + state->group_ctx->domain = sdom->dom; + state->group_ctx->opts = opts; + state->group_ctx->user_search_bases = sdom->user_search_bases; + state->group_ctx->group_search_bases = sdom->group_search_bases; + state->group_ctx->ignore_user_search_bases = sdom->ignore_user_search_bases; + state->group_ctx->sh = sh; + state->group_ctx->try_deref = sdap_has_deref_support(sh, opts); + + /* disable deref if threshold <= 0 */ + if (state->group_ctx->deref_threshold <= 0) { + state->group_ctx->try_deref = false; + } + + /* if any search base contains filter, disable dereference. */ + if (state->group_ctx->try_deref) { + for (i = 0; opts->sdom->user_search_bases[i] != NULL; i++) { + if (opts->sdom->user_search_bases[i]->filter != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "User search base contains filter, " + "dereference will be disabled\n"); + state->group_ctx->try_deref = false; + break; + } + } + } + + if (state->group_ctx->try_deref) { + for (i = 0; opts->sdom->group_search_bases[i] != NULL; i++) { + if (opts->sdom->group_search_bases[i]->filter != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Group search base contains filter, " + "dereference will be disabled\n"); + state->group_ctx->try_deref = false; + break; + } + } + } + + /* insert initial group into hash table */ + ret = sdap_nested_group_hash_group(state->group_ctx, group); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to insert group into hash table " + "[%d]: %s\n", ret, strerror(ret)); + goto immediately; + } + + /* resolve group */ + subreq = sdap_nested_group_process_send(state, ev, state->group_ctx, + 0, group); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_nested_group_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void sdap_nested_group_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_nested_group_process_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sdap_nested_group_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + unsigned long *_num_users, + struct sysdb_attrs ***_users, + unsigned long *_num_groups, + struct sysdb_attrs ***_groups, + hash_table_t **_missing_external) +{ + struct sdap_nested_group_state *state = NULL; + struct sysdb_attrs **users = NULL; + struct sysdb_attrs **groups = NULL; + unsigned long num_users; + unsigned long num_groups; + errno_t ret; + + state = tevent_req_data(req, struct sdap_nested_group_state); + + PROBE(SDAP_NESTED_GROUP_RECV); + TEVENT_REQ_RETURN_ON_ERROR(req); + + ret = sdap_nested_group_extract_hash_table(state, state->group_ctx->users, + &num_users, &users); + if (ret != EOK) { + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, "%lu users found in the hash table\n", + num_users); + + ret = sdap_nested_group_extract_hash_table(state, state->group_ctx->groups, + &num_groups, &groups); + if (ret != EOK) { + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, "%lu groups found in the hash table\n", + num_groups); + + if (_num_users != NULL) { + *_num_users = num_users; + } + + if (_users != NULL) { + *_users = talloc_steal(mem_ctx, users); + } + + if (_num_groups!= NULL) { + *_num_groups = num_groups; + } + + if (_groups != NULL) { + *_groups = talloc_steal(mem_ctx, groups); + } + + if (_missing_external) { + *_missing_external = talloc_steal(mem_ctx, + state->group_ctx->missing_external); + } + + return EOK; +} + +struct sdap_nested_group_process_state { + struct tevent_context *ev; + struct sdap_nested_group_ctx *group_ctx; + struct sdap_nested_group_member *missing; + int num_missing_total; + int num_missing_groups; + struct ldb_message_element *ext_members; + struct ldb_message_element *members; + int nesting_level; + char *group_dn; + bool deref; + bool deref_shortcut; +}; + +static void sdap_nested_group_process_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_process_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + int nesting_level, + struct sysdb_attrs *group) +{ + struct sdap_nested_group_process_state *state = NULL; + struct sdap_attr_map *group_map = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + const char *orig_dn = NULL; + errno_t ret; + int split_threshold; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_process_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->group_ctx = group_ctx; + state->nesting_level = nesting_level; + group_map = state->group_ctx->opts->group_map; + + /* get original dn */ + ret = sysdb_attrs_get_string(group, SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve original dn " + "[%d]: %s\n", ret, strerror(ret)); + goto immediately; + } + + state->group_dn = talloc_strdup(state, orig_dn); + if (state->group_dn == NULL) { + ret = ENOMEM; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "About to process group [%s]\n", orig_dn); + PROBE(SDAP_NESTED_GROUP_PROCESS_SEND, state->group_dn); + + /* get member list, both direct and external */ + state->ext_members = sdap_nested_group_ext_members(state->group_ctx->opts, + group); + + ret = sysdb_attrs_get_el_ext(group, group_map[SDAP_AT_GROUP_MEMBER].sys_name, + false, &state->members); + if (ret == ENOENT && state->ext_members == NULL) { + ret = EOK; /* no members, direct or external */ + goto immediately; + } else if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve member list " + "[%d]: %s\n", ret, strerror(ret)); + goto immediately; + } + + split_threshold = state->group_ctx->try_deref ? \ + state->group_ctx->deref_threshold : \ + -1; + + /* get members that need to be refreshed */ + PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_PRE); + ret = sdap_nested_group_split_members(state, state->group_ctx, + split_threshold, + state->nesting_level, + state->members, + &state->missing, + &state->num_missing_total, + &state->num_missing_groups); + PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_POST); + if (ret == ERR_DEREF_THRESHOLD) { + DEBUG(SSSDBG_TRACE_FUNC, + "More members were missing than the deref threshold\n"); + state->deref_shortcut = true; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to split member list " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + ret = sdap_nested_group_add_ext_members(state->group_ctx, + group, + state->ext_members); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to split external member list " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + if (state->num_missing_total == 0 + && hash_count(state->group_ctx->missing_external) == 0) { + ret = EOK; /* we're done */ + goto immediately; + } + + /* If there are only indirect members of the group, it's still safe to + * proceed and let the direct lookup code just fall through. + */ + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Looking up %d/%d members of group [%s]\n", + state->num_missing_total, + state->members ? state->members->num_values : 0, + orig_dn); + + /* process members */ + if (group_ctx->try_deref + && state->num_missing_total > group_ctx->deref_threshold) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Dereferencing members of group [%s]\n", + orig_dn); + state->deref = true; + subreq = sdap_nested_group_deref_send(state, ev, group_ctx, + state->members, orig_dn, + state->nesting_level); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, "Members of group [%s] will be " + "processed individually\n", orig_dn); + state->deref = false; + subreq = sdap_nested_group_single_send(state, ev, group_ctx, + state->missing, + state->num_missing_total, + state->num_missing_groups, + state->nesting_level); + } + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_nested_group_process_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void sdap_nested_group_process_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_process_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_process_state); + + if (state->deref) { + ret = sdap_nested_group_deref_recv(subreq); + talloc_zfree(subreq); + if (ret == ENOTSUP) { + /* dereference is not supported, try again without dereference */ + state->group_ctx->try_deref = false; + state->deref = false; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Members of group [%s] will be " + "processed individually\n", state->group_dn); + + if (state->deref_shortcut == true) { + /* If we previously short-cut dereference, we need to split the + * members again to get full list of missing member types + */ + PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_PRE); + ret = sdap_nested_group_split_members(state, state->group_ctx, + -1, + state->nesting_level, + state->members, + &state->missing, + &state->num_missing_total, + &state->num_missing_groups); + PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_POST); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to split member list " + "[%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + subreq = sdap_nested_group_single_send(state, + state->ev, + state->group_ctx, + state->missing, + state->num_missing_total, + state->num_missing_groups, + state->nesting_level); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_nested_group_process_done, + req); + + ret = EAGAIN; + } + } else { + ret = sdap_nested_group_single_recv(subreq); + talloc_zfree(subreq); + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static errno_t sdap_nested_group_process_recv(struct tevent_req *req) +{ +#ifdef HAVE_SYSTEMTAP + struct sdap_nested_group_process_state *state = NULL; + state = tevent_req_data(req, struct sdap_nested_group_process_state); + + PROBE(SDAP_NESTED_GROUP_PROCESS_RECV, state->group_dn); +#endif + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_nested_group_recurse_state { + struct tevent_context *ev; + struct sdap_nested_group_ctx *group_ctx; + struct sysdb_attrs **groups; + int num_groups; + int index; + int nesting_level; +}; + +static errno_t sdap_nested_group_recurse_step(struct tevent_req *req); +static void sdap_nested_group_recurse_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_recurse_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sysdb_attrs **nested_groups, + int num_groups, + int nesting_level) +{ + struct sdap_nested_group_recurse_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_recurse_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->group_ctx = group_ctx; + state->groups = nested_groups; + state->num_groups = num_groups; + state->index = 0; + state->nesting_level = nesting_level; + + /* process each group individually */ + ret = sdap_nested_group_recurse_step(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t sdap_nested_group_recurse_step(struct tevent_req *req) +{ + struct sdap_nested_group_recurse_state *state = NULL; + struct tevent_req *subreq = NULL; + + state = tevent_req_data(req, struct sdap_nested_group_recurse_state); + + if (state->index >= state->num_groups) { + /* we're done */ + return EOK; + } + + subreq = sdap_nested_group_process_send(state, state->ev, state->group_ctx, + state->nesting_level, + state->groups[state->index]); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_nested_group_recurse_done, req); + + state->index++; + + return EAGAIN; +} + +static void sdap_nested_group_recurse_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_nested_group_process_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = sdap_nested_group_recurse_step(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t sdap_nested_group_recurse_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_nested_group_single_state { + struct tevent_context *ev; + struct sdap_nested_group_ctx *group_ctx; + struct sdap_nested_group_member *members; + int nesting_level; + + struct sdap_nested_group_member *current_member; + int num_members; + int member_index; + + struct sysdb_attrs **nested_groups; + int num_groups; + bool ignore_unreadable_references; +}; + +static errno_t sdap_nested_group_single_step(struct tevent_req *req); +static void sdap_nested_group_single_step_done(struct tevent_req *subreq); +static void sdap_nested_group_single_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_single_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *members, + int num_members, + int num_groups_max, + int nesting_level) +{ + struct sdap_nested_group_single_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_single_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->group_ctx = group_ctx; + state->members = members; + state->nesting_level = nesting_level; + state->current_member = NULL; + state->num_members = num_members; + state->member_index = 0; + state->nested_groups = talloc_zero_array(state, struct sysdb_attrs *, + num_groups_max); + if (state->nested_groups == NULL) { + ret = ENOMEM; + goto immediately; + } + state->num_groups = 0; /* we will count exact number of the groups */ + state->ignore_unreadable_references = dp_opt_get_bool( + group_ctx->opts->basic, SDAP_IGNORE_UNREADABLE_REFERENCES); + + /* process each member individually */ + ret = sdap_nested_group_single_step(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t must_ignore(struct sdap_search_base **ignore_user_search_bases, + struct ldb_context *ldb_ctx, + const char *dn_str, + bool *_ignore) +{ + bool ignore; + struct ldb_dn *ldn; + struct sdap_search_base **base; + + if (ldb_ctx == NULL || dn_str == NULL) { + return EINVAL; + } + + if (ignore_user_search_bases == NULL) { + *_ignore = false; + return EOK; + } + + ldn = ldb_dn_new(NULL, ldb_ctx, dn_str); + if (ldn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to allocate memory for the DN\n"); + return ENOMEM; + } + + ignore = false; + for (base = ignore_user_search_bases; *base != NULL; base++) { + if ((*base)->ldb_basedn != NULL) { + if (ldb_dn_compare_base((*base)->ldb_basedn, ldn) == 0) { + ignore = true; + DEBUG(SSSDBG_TRACE_INTERNAL, "Ignoring entry [%s]\n", dn_str); + break; + } + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Not checking ignore user search base %s \n", + (*base)->basedn); + } + } + *_ignore = ignore; + + talloc_free(ldn); + return EOK; +} + +static errno_t sdap_nested_group_single_step(struct tevent_req *req) +{ + struct sdap_nested_group_single_state *state = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + bool ignore; + + state = tevent_req_data(req, struct sdap_nested_group_single_state); + + do { + if (state->member_index >= state->num_members) { + /* we're done */ + return EOK; + } + + state->current_member = &state->members[state->member_index]; + state->member_index++; + + ret = must_ignore(state->group_ctx->ignore_user_search_bases, + sysdb_ctx_get_ldb(state->group_ctx->domain->sysdb), + state->current_member->dn, &ignore); + if (ret != EOK) { + return ret; + } + } while (ignore); + + switch (state->current_member->type) { + case SDAP_NESTED_GROUP_DN_USER: + subreq = sdap_nested_group_lookup_user_send(state, state->ev, + state->group_ctx, + state->current_member); + break; + case SDAP_NESTED_GROUP_DN_GROUP: + subreq = sdap_nested_group_lookup_group_send(state, state->ev, + state->group_ctx, + state->current_member); + break; + case SDAP_NESTED_GROUP_DN_UNKNOWN: + subreq = sdap_nested_group_lookup_unknown_send(state, state->ev, + state->group_ctx, + state->current_member); + break; + } + + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_nested_group_single_step_done, req); + + return EAGAIN; +} + +static errno_t +sdap_nested_group_single_step_process(struct tevent_req *subreq) +{ + struct sdap_nested_group_single_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs *entry = NULL; + enum sdap_nested_group_dn_type type = SDAP_NESTED_GROUP_DN_UNKNOWN; + const char *orig_dn = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_single_state); + + /* set correct type if possible */ + if (state->current_member->type == SDAP_NESTED_GROUP_DN_UNKNOWN) { + ret = sdap_nested_group_lookup_unknown_recv(state, subreq, + &entry, &type); + if (ret != EOK) { + goto done; + } + + if (entry != NULL) { + state->current_member->type = type; + } + } + + switch (state->current_member->type) { + case SDAP_NESTED_GROUP_DN_USER: + if (entry == NULL) { + /* type was not unknown, receive data */ + ret = sdap_nested_group_lookup_user_recv(state, subreq, &entry); + if (ret != EOK) { + goto done; + } + + if (entry == NULL) { + /* user not found, continue */ + break; + } + } + + /* The original DN of the user object itself might differ from the one + * used in the member attribute, e.g. different case. To make sure if + * can be found in a hash table when iterating over group members the + * DN from the member attribute used for the search as saved as well. + */ + ret = sysdb_attrs_add_string(entry, + SYSDB_DN_FOR_MEMBER_HASH_TABLE, + state->current_member->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string failed.\n"); + goto done; + } + + /* save user in hash table */ + ret = sdap_nested_group_hash_user(state->group_ctx, entry); + if (ret == EEXIST) { + /* the user is already present, skip it */ + talloc_zfree(entry); + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save user in hash table " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + break; + case SDAP_NESTED_GROUP_DN_GROUP: + if (entry == NULL) { + /* type was not unknown, receive data */ + ret = sdap_nested_group_lookup_group_recv(state, subreq, &entry); + if (ret != EOK) { + goto done; + } + + if (entry == NULL) { + /* group not found, continue */ + break; + } + } else { + /* the type was unknown so we had to pull the group, + * but we don't want to process it if we have reached + * the nesting level */ + if (state->nesting_level >= state->group_ctx->max_nesting_level) { + ret = sysdb_attrs_get_string(entry, SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The entry has no originalDN\n"); + orig_dn = "invalid"; + } + + DEBUG(SSSDBG_TRACE_ALL, "[%s] is outside nesting limit " + "(level %d), skipping\n", orig_dn, state->nesting_level); + break; + } + } + + /* save group in hash table */ + ret = sdap_nested_group_hash_group(state->group_ctx, entry); + if (ret == EEXIST) { + /* the group is already present, skip it */ + talloc_zfree(entry); + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save group in hash table " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + /* remember the group for later processing */ + state->nested_groups[state->num_groups] = entry; + state->num_groups++; + + break; + case SDAP_NESTED_GROUP_DN_UNKNOWN: + if (state->ignore_unreadable_references) { + DEBUG(SSSDBG_TRACE_FUNC, "Ignoring unreadable reference [%s]\n", + state->current_member->dn); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unknown entry type [%s]!\n", + state->current_member->dn); + ret = EINVAL; + goto done; + } + break; + } + + ret = EOK; + +done: + return ret; +} + +static void sdap_nested_group_single_step_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_single_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_single_state); + + /* process direct members */ + ret = sdap_nested_group_single_step_process(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error processing direct membership " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + ret = sdap_nested_group_single_step(req); + if (ret == EOK) { + /* we have processed all direct members, + * now recurse and process nested groups */ + subreq = sdap_nested_group_recurse_send(state, state->ev, + state->group_ctx, + state->nested_groups, + state->num_groups, + state->nesting_level + 1); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_nested_group_single_done, req); + } else if (ret != EAGAIN) { + /* error */ + goto done; + } + + /* we're not done yet */ + ret = EAGAIN; + +done: + if (ret == EOK) { + /* tevent_req_error() cannot cope with EOK */ + DEBUG(SSSDBG_CRIT_FAILURE, "We should not get here with EOK\n"); + tevent_req_error(req, EINVAL); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static void sdap_nested_group_single_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + /* all nested groups are completed */ + ret = sdap_nested_group_recurse_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error processing nested groups " + "[%d]: %s.\n", ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + + return; +} + +static errno_t sdap_nested_group_single_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t sdap_nested_group_get_ipa_user(TALLOC_CTX *mem_ctx, + const char *user_dn, + struct sysdb_ctx *sysdb, + struct sysdb_attrs **_user) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs *user; + char *name; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = ipa_get_rdn(tmp_ctx, sysdb, user_dn, &name, "uid", + "cn", "users", "cn", "accounts"); + if (ret != EOK) { + goto done; + } + + user = sysdb_new_attrs(tmp_ctx); + if (user == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(user, SYSDB_NAME, name); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_add_string(user, SYSDB_ORIG_DN, user_dn); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_add_string(user, SYSDB_OBJECTCATEGORY, SYSDB_USER_CLASS); + if (ret != EOK) { + goto done; + } + + *_user = talloc_steal(mem_ctx, user); + +done: + talloc_free(tmp_ctx); + return ret; +} + +struct sdap_nested_group_lookup_user_state { + struct sysdb_attrs *user; +}; + +static void sdap_nested_group_lookup_user_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_lookup_user_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member) +{ + struct sdap_nested_group_lookup_user_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + const char **attrs = NULL; + const char *base_filter = NULL; + const char *filter = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_lookup_user_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + PROBE(SDAP_NESTED_GROUP_LOOKUP_USER_SEND); + + if (group_ctx->opts->schema_type == SDAP_SCHEMA_IPA_V1) { + /* if the schema is IPA, then just shortcut and guess the name */ + ret = sdap_nested_group_get_ipa_user(state, member->dn, + group_ctx->domain->sysdb, + &state->user); + if (ret == EOK) { + goto immediately; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Couldn't parse out user information " + "based on DN %s, falling back to an LDAP lookup\n", member->dn); + } + + /* only pull down username and originalDN */ + attrs = talloc_array(state, const char *, 3); + if (attrs == NULL) { + ret = ENOMEM; + goto immediately; + } + + attrs[0] = "objectClass"; + attrs[1] = group_ctx->opts->user_map[SDAP_AT_USER_NAME].name; + attrs[2] = NULL; + + /* create filter */ + base_filter = talloc_asprintf(state, "(objectclass=%s)", + group_ctx->opts->user_map[SDAP_OC_USER].name); + if (base_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* use search base filter if needed */ + filter = sdap_combine_filters(state, base_filter, member->user_filter); + if (filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* search */ + subreq = sdap_get_generic_send(state, ev, group_ctx->opts, group_ctx->sh, + member->dn, LDAP_SCOPE_BASE, filter, attrs, + group_ctx->opts->user_map, + group_ctx->opts->user_map_cnt, + dp_opt_get_int(group_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_nested_group_lookup_user_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void sdap_nested_group_lookup_user_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_lookup_user_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **user = NULL; + size_t count = 0; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_lookup_user_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &user); + talloc_zfree(subreq); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + goto done; + } + + if (count == 1) { + state->user = user[0]; + } else if (count == 0) { + /* group not found */ + state->user = NULL; + } else { + DEBUG(SSSDBG_OP_FAILURE, + "BASE search returned more than one records\n"); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sdap_nested_group_lookup_user_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_user) +{ + struct sdap_nested_group_lookup_user_state *state = NULL; + state = tevent_req_data(req, struct sdap_nested_group_lookup_user_state); + + PROBE(SDAP_NESTED_GROUP_LOOKUP_USER_RECV); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_user != NULL) { + *_user = talloc_steal(mem_ctx, state->user); + } + + return EOK; +} + +struct sdap_nested_group_lookup_group_state { + struct sysdb_attrs *group; +}; + +static void sdap_nested_group_lookup_group_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_lookup_group_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member) +{ + struct sdap_nested_group_lookup_group_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_attr_map *map = group_ctx->opts->group_map; + const char **attrs = NULL; + const char *base_filter = NULL; + const char *filter = NULL; + char *oc_list; + errno_t ret; + + PROBE(SDAP_NESTED_GROUP_LOOKUP_GROUP_SEND); + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_lookup_group_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ret = build_attrs_from_map(state, group_ctx->opts->group_map, + SDAP_OPTS_GROUP, NULL, &attrs, NULL); + if (ret != EOK) { + goto immediately; + } + + /* create filter */ + oc_list = sdap_make_oc_list(state, map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto immediately; + } + + base_filter = talloc_asprintf(attrs, "(&(%s)(%s=*))", oc_list, + map[SDAP_AT_GROUP_NAME].name); + if (base_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* use search base filter if needed */ + filter = sdap_combine_filters(state, base_filter, member->group_filter); + if (filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* search */ + subreq = sdap_get_generic_send(state, ev, group_ctx->opts, group_ctx->sh, + member->dn, LDAP_SCOPE_BASE, filter, attrs, + map, SDAP_OPTS_GROUP, + dp_opt_get_int(group_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_nested_group_lookup_group_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void sdap_nested_group_lookup_group_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_lookup_group_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **group = NULL; + size_t count = 0; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_lookup_group_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &group); + talloc_zfree(subreq); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + goto done; + } + + if (count == 1) { + state->group = group[0]; + } else if (count == 0) { + /* group not found */ + state->group = NULL; + } else { + DEBUG(SSSDBG_OP_FAILURE, + "BASE search returned more than one records\n"); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sdap_nested_group_lookup_group_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_group) +{ + struct sdap_nested_group_lookup_group_state *state = NULL; + state = tevent_req_data(req, struct sdap_nested_group_lookup_group_state); + + PROBE(SDAP_NESTED_GROUP_LOOKUP_GROUP_RECV); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_group != NULL) { + *_group = talloc_steal(mem_ctx, state->group); + } + + return EOK; +} + +struct sdap_nested_group_lookup_unknown_state { + struct tevent_context *ev; + struct sdap_nested_group_ctx *group_ctx; + struct sdap_nested_group_member *member; + enum sdap_nested_group_dn_type type; + struct sysdb_attrs *entry; +}; + +static void +sdap_nested_group_lookup_unknown_user_done(struct tevent_req *subreq); + +static void +sdap_nested_group_lookup_unknown_group_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_lookup_unknown_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member) +{ + struct sdap_nested_group_lookup_unknown_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_lookup_unknown_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + PROBE(SDAP_NESTED_GROUP_LOOKUP_UNKNOWN_SEND); + + state->ev = ev; + state->group_ctx = group_ctx; + state->member = member; + + /* try users first */ + subreq = sdap_nested_group_lookup_user_send(state, + state->ev, + state->group_ctx, + state->member); + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } else { + tevent_req_set_callback(subreq, + sdap_nested_group_lookup_unknown_user_done, + req); + } + + return req; +} + +static void +sdap_nested_group_lookup_unknown_user_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_lookup_unknown_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs *entry = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_lookup_unknown_state); + + ret = sdap_nested_group_lookup_user_recv(state, subreq, &entry); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + if (entry != NULL) { + /* found in users */ + state->entry = entry; + state->type = SDAP_NESTED_GROUP_DN_USER; + ret = EOK; + goto done; + } + + /* not found in users, try group */ + subreq = sdap_nested_group_lookup_group_send(state, + state->ev, + state->group_ctx, + state->member); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_nested_group_lookup_unknown_group_done, + req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static void +sdap_nested_group_lookup_unknown_group_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_lookup_unknown_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs *entry = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_lookup_unknown_state); + + ret = sdap_nested_group_lookup_group_recv(state, subreq, &entry); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + if (entry == NULL) { + /* not found, end request */ + state->entry = NULL; + state->type = SDAP_NESTED_GROUP_DN_UNKNOWN; + } else { + /* found in groups */ + state->entry = entry; + state->type = SDAP_NESTED_GROUP_DN_GROUP; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t +sdap_nested_group_lookup_unknown_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_entry, + enum sdap_nested_group_dn_type *_type) +{ + struct sdap_nested_group_lookup_unknown_state *state = NULL; + state = tevent_req_data(req, struct sdap_nested_group_lookup_unknown_state); + + PROBE(SDAP_NESTED_GROUP_LOOKUP_UNKNOWN_RECV); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_entry != NULL) { + *_entry = talloc_steal(mem_ctx, state->entry); + } + + if (_type != NULL) { + *_type = state->type; + } + + + return EOK; +} + +struct sdap_nested_group_deref_state { + struct tevent_context *ev; + struct sdap_nested_group_ctx *group_ctx; + struct ldb_message_element *members; + int nesting_level; + + struct sysdb_attrs **nested_groups; + int num_groups; +}; + +static void sdap_nested_group_deref_direct_done(struct tevent_req *subreq); +static void sdap_nested_group_deref_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_deref_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct ldb_message_element *members, + const char *group_dn, + int nesting_level) +{ + struct sdap_nested_group_deref_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_attr_map_info *maps = NULL; + static const int num_maps = 2; + struct sdap_options *opts = group_ctx->opts; + const char **attrs = NULL; + size_t num_attrs = 0; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_deref_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + PROBE(SDAP_NESTED_GROUP_DEREF_SEND); + + state->ev = ev; + state->group_ctx = group_ctx; + state->members = members; + state->nesting_level = nesting_level; + state->num_groups = 0; /* we will count exact number of the groups */ + + maps = talloc_array(state, struct sdap_attr_map_info, num_maps); + if (maps == NULL) { + ret = ENOMEM; + goto immediately; + } + + maps[0].map = opts->user_map; + maps[0].num_attrs = opts->user_map_cnt; + maps[1].map = opts->group_map; + maps[1].num_attrs = SDAP_OPTS_GROUP; + + /* pull down the whole group map, + * but only pull down username and originalDN for users */ + ret = build_attrs_from_map(state, opts->group_map, SDAP_OPTS_GROUP, + NULL, &attrs, &num_attrs); + if (ret != EOK) { + goto immediately; + } + + attrs = talloc_realloc(state, attrs, const char *, num_attrs + 2); + if (attrs == NULL) { + ret = ENOMEM; + goto immediately; + } + + attrs[num_attrs] = group_ctx->opts->user_map[SDAP_AT_USER_NAME].name; + attrs[num_attrs + 1] = NULL; + + /* send request */ + subreq = sdap_deref_search_send(state, ev, opts, group_ctx->sh, group_dn, + opts->group_map[SDAP_AT_GROUP_MEMBER].name, + attrs, num_maps, maps, + dp_opt_get_int(opts->basic, + SDAP_SEARCH_TIMEOUT)); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_nested_group_deref_direct_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static hash_table_t * +convert_ldb_element_to_set(const struct ldb_message_element *members) +{ + errno_t ret; + hash_table_t *set = NULL; + hash_key_t key; + hash_value_t value; + size_t j; + + ret = sss_hash_create(NULL, members->num_values, &set); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, strerror(ret)); + return NULL; + } + + key.type = HASH_KEY_CONST_STRING; + value.type = HASH_VALUE_UNDEF; + + for (j = 0; j < members->num_values; ++j) { + key.c_str = (const char*)members->values[j].data; + /* since hash table is used as a set, we don't care about value */ + ret = hash_enter(set, &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add '%s'\n", key.c_str); + hash_destroy(set); + return NULL; + } + } + + return set; +} + +static bool set_has_key(hash_table_t *set, const char *key) +{ + hash_key_t hkey; + + hkey.type = HASH_KEY_CONST_STRING; + hkey.c_str = key; + + return hash_has_key(set, &hkey); +} + +static errno_t +sdap_nested_group_deref_direct_process(struct tevent_req *subreq) +{ + struct sdap_nested_group_deref_state *state = NULL; + struct tevent_req *req = NULL; + struct sdap_options *opts = NULL; + struct sdap_deref_attrs **entries = NULL; + struct ldb_message_element *members = NULL; + hash_table_t *members_set = NULL; /* will be used as a `set` */ + const char *orig_dn = NULL; + size_t num_entries = 0; + size_t i; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_deref_state); + + opts = state->group_ctx->opts; + members = state->members; + members_set = convert_ldb_element_to_set(state->members); + if (members_set == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sdap_deref_search_recv(subreq, state, &num_entries, &entries); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Received %zu dereference results, " + "about to process them\n", num_entries); + + /* + * We don't have any knowledge about possible number of groups when + * dereferencing. We expect that every member is a group and we will + * allocate enough space to hold it. We will shrink the memory later. + */ + state->nested_groups = talloc_zero_array(state, struct sysdb_attrs *, + num_entries); + if (state->nested_groups == NULL) { + ret = ENOMEM; + goto done; + } + + PROBE(SDAP_NESTED_GROUP_DEREF_PROCESS_PRE); + for (i = 0; i < num_entries; i++) { + ret = sysdb_attrs_get_string(entries[i]->attrs, + SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "The entry has no originalDN\n"); + goto done; + } + + /* Ensure that all members returned from the deref request are included + * in the member processing. Sometimes we will get more results back + * from deref/asq than we got from the initial lookup, as is the case + * with Active Directory and its range retrieval mechanism. + */ + if (!set_has_key(members_set, orig_dn)) { + /* Append newly found member to member list. + * Changes in state->members will propagate into sysdb_attrs of + * the group. */ + state->members->values = talloc_realloc(members, members->values, + struct ldb_val, + members->num_values + 1); + if (members->values == NULL) { + ret = ENOMEM; + goto done; + } + + members->values[members->num_values].data = + (uint8_t *)talloc_strdup(members->values, orig_dn); + if (members->values[members->num_values].data == NULL) { + ret = ENOMEM; + goto done; + } + + members->values[members->num_values].length = strlen(orig_dn); + members->num_values++; + } + + if (entries[i]->map == opts->user_map) { + /* we found a user */ + + /* skip the user if it is not amongst configured search bases */ + if (!sdap_nested_member_is_user(state->group_ctx, orig_dn, NULL)) { + continue; + } + + /* save user in hash table */ + ret = sdap_nested_group_hash_user(state->group_ctx, + entries[i]->attrs); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to save user in hash table " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + } else if (entries[i]->map == opts->group_map) { + /* we found a group */ + + /* skip the group if we have reached the nesting limit */ + if (state->nesting_level >= state->group_ctx->max_nesting_level) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] is outside nesting limit " + "(level %d), skipping\n", orig_dn, state->nesting_level); + continue; + } + + /* skip the group if it is not amongst configured search bases */ + if (!sdap_nested_member_is_group(state->group_ctx, orig_dn, NULL)) { + continue; + } + + /* save group in hash table */ + ret = sdap_nested_group_hash_group(state->group_ctx, + entries[i]->attrs); + if (ret == EEXIST) { + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to save group in hash table " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + /* remember the group for later processing */ + state->nested_groups[state->num_groups] = entries[i]->attrs; + state->num_groups++; + + } else { + /* this should never happen, but if it does, do not loop forever */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Entry does not match any known map, skipping\n"); + continue; + } + } + PROBE(SDAP_NESTED_GROUP_DEREF_PROCESS_POST); + + /* adjust size of nested groups array */ + if (state->num_groups > 0) { + state->nested_groups = talloc_realloc(state, state->nested_groups, + struct sysdb_attrs *, + state->num_groups); + if (state->nested_groups == NULL) { + ret = ENOMEM; + goto done; + } + } else { + talloc_zfree(state->nested_groups); + } + + ret = EOK; + +done: + if (members_set != NULL) { + hash_destroy(members_set); + } + return ret; +} + +static void sdap_nested_group_deref_direct_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_deref_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_deref_state); + + /* process direct members */ + ret = sdap_nested_group_deref_direct_process(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error processing direct membership " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + /* we have processed all direct members, + * now recurse and process nested groups */ + subreq = sdap_nested_group_recurse_send(state, state->ev, + state->group_ctx, + state->nested_groups, + state->num_groups, + state->nesting_level + 1); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_nested_group_deref_done, req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + /* tevent_req_error() cannot cope with EOK */ + DEBUG(SSSDBG_CRIT_FAILURE, "We should not get here with EOK\n"); + tevent_req_error(req, EINVAL); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; + +} + +static void sdap_nested_group_deref_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + /* process nested groups */ + ret = sdap_nested_group_recurse_recv(subreq); + talloc_zfree(subreq); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t sdap_nested_group_deref_recv(struct tevent_req *req) +{ + PROBE(SDAP_NESTED_GROUP_DEREF_RECV); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_ext_member { + struct sdap_external_missing_member *missing_mem; + const char *ext_member_attr; + + enum sysdb_member_type member_type; + struct sss_domain_info *dom; + struct sysdb_attrs *attrs; +}; + +struct sdap_nested_group_lookup_external_state { + struct tevent_context *ev; + struct sdap_ext_member_ctx *ext_ctx; + struct sss_domain_info *group_dom; + hash_table_t *missing_external; + + hash_entry_t *entries; + unsigned long n_entries; + unsigned long eniter; + + struct sdap_ext_member *ext_members; + + ext_member_send_fn_t ext_member_resolve_send; + ext_member_recv_fn_t ext_member_resolve_recv; +}; + +static errno_t +sdap_nested_group_lookup_external_step(struct tevent_req *req); +static void +sdap_nested_group_lookup_external_done(struct tevent_req *subreq); +static errno_t +sdap_nested_group_lookup_external_link(struct tevent_req *req); +static errno_t +sdap_nested_group_lookup_external_link_member( + struct sdap_nested_group_lookup_external_state *state, + struct sdap_ext_member *member); +static errno_t +sdap_nested_group_memberof_dn_by_original_dn( + TALLOC_CTX *mem_ctx, + struct sss_domain_info *group_dom, + const char *original_dn, + const char ***_parents); + +struct tevent_req * +sdap_nested_group_lookup_external_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *group_dom, + struct sdap_ext_member_ctx *ext_ctx, + hash_table_t *missing_external) +{ + struct sdap_nested_group_lookup_external_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_lookup_external_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->group_dom = group_dom; + state->ext_ctx = ext_ctx; + state->missing_external = missing_external; + + if (state->ext_ctx->ext_member_resolve_send == NULL + || state->ext_ctx->ext_member_resolve_recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Wrong private context\n"); + ret = EINVAL; + goto immediately; + } + + ret = hash_entries(state->missing_external, + &state->n_entries, &state->entries); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "hash_entries returned %d\n", ret); + ret = EIO; + goto immediately; + } + state->eniter = 0; + + state->ext_members = talloc_zero_array(state, + struct sdap_ext_member, + state->n_entries); + if (state->ext_members == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = sdap_nested_group_lookup_external_step(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t +sdap_nested_group_lookup_external_step(struct tevent_req *req) +{ + struct tevent_req *subreq = NULL; + struct sdap_nested_group_lookup_external_state *state = NULL; + state = tevent_req_data(req, + struct sdap_nested_group_lookup_external_state); + + subreq = state->ext_ctx->ext_member_resolve_send(state, + state->ev, + state->entries[state->eniter].key.str, + state->ext_ctx->pvt); + if (subreq == NULL) { + return ENOMEM; + } + DEBUG(SSSDBG_TRACE_FUNC, "Refreshing member %lu/%lu\n", + state->eniter, state->n_entries); + tevent_req_set_callback(subreq, + sdap_nested_group_lookup_external_done, + req); + + return EAGAIN; +} + +static void +sdap_nested_group_lookup_external_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = NULL; + struct sdap_nested_group_lookup_external_state *state = NULL; + enum sysdb_member_type member_type; + struct sysdb_attrs *member; + struct sss_domain_info *member_dom; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, + struct sdap_nested_group_lookup_external_state); + + ret = state->ext_ctx->ext_member_resolve_recv(state, subreq, + &member_type, + &member_dom, + &member); + talloc_free(subreq); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Refreshed member %lu\n", state->eniter); + state->ext_members[state->eniter].missing_mem = \ + state->entries[state->eniter].value.ptr; + state->ext_members[state->eniter].dom = member_dom; + + state->ext_members[state->eniter].ext_member_attr = \ + talloc_steal(state->ext_members, + state->entries[state->eniter].key.str); + state->ext_members[state->eniter].member_type = member_type; + state->ext_members[state->eniter].attrs = \ + talloc_steal(state->ext_members, member); + } + + state->eniter++; + if (state->eniter >= state->n_entries) { + DEBUG(SSSDBG_TRACE_FUNC, "All external members processed\n"); + ret = sdap_nested_group_lookup_external_link(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + tevent_req_done(req); + return; + } + + ret = sdap_nested_group_lookup_external_step(req); + if (ret != EOK && ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + + return; +} + +static errno_t +sdap_nested_group_lookup_external_link(struct tevent_req *req) +{ + errno_t ret, tret; + bool in_transaction = false; + struct sdap_nested_group_lookup_external_state *state = NULL; + state = tevent_req_data(req, + struct sdap_nested_group_lookup_external_state); + + ret = sysdb_transaction_start(state->group_dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + + for (size_t i = 0; i < state->eniter; i++) { + if (state->ext_members[i].attrs == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "The member %s could not be resolved\n", + state->ext_members[i].ext_member_attr); + continue; + } + + ret = sdap_nested_group_lookup_external_link_member(state, + &state->ext_members[i]); + if (ret != EOK) { + goto fail; + } + } + + ret = sysdb_transaction_commit(state->group_dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + + return EOK; + +fail: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->group_dom->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + return EFAULT; +} + +static errno_t +sdap_nested_group_lookup_external_link_member( + struct sdap_nested_group_lookup_external_state *state, + struct sdap_ext_member *member) +{ + const char *name; + int ret; + const char **parents = NULL; + size_t i; + TALLOC_CTX *tmp_ctx; + const char *orig_dn; + + tmp_ctx = talloc_new(state); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_attrs_get_string(member->attrs, SYSDB_NAME, &name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "No name for a user\n"); + goto done; + } + + /* This only works because the groups were saved in a previous + * transaction */ + for (i=0; i < member->missing_mem->parent_dn_idx; i++) { + orig_dn = member->missing_mem->parent_group_dns[i]; + DEBUG(SSSDBG_TRACE_INTERNAL, + "Linking external members %s from domain %s to parents of %s\n", + name, member->dom->name, orig_dn); + ret = sdap_nested_group_memberof_dn_by_original_dn(tmp_ctx, + state->group_dom, + orig_dn, + &parents); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot find parents of %s\n", orig_dn); + continue; + } + + /* We don't have to remove the members here, since all members attributes + * are always written anew + */ + ret = sysdb_update_members_dn(member->dom, name, member->member_type, + parents, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot link %s@%s to its parents\n", + name, member->dom->name); + goto done; + } + + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sdap_nested_group_memberof_dn_by_original_dn( + TALLOC_CTX *mem_ctx, + struct sss_domain_info *group_dom, + const char *original_dn, + const char ***_parents) +{ + errno_t ret; + const char *attrs[] = { SYSDB_NAME, + SYSDB_MEMBEROF, + NULL }; + struct ldb_message **msgs = NULL; + size_t count; + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *memberof; + const char **parents; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_search_groups_by_orig_dn(tmp_ctx, group_dom, original_dn, + attrs, &count, &msgs); + if (ret != EOK) { + goto done; + } + + if (count != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "More than one entry found by originalDN?\n"); + goto done; + } + + memberof = ldb_msg_find_element(msgs[0], SYSDB_MEMBEROF); + if (memberof == NULL || memberof->num_values == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The external group is not a member of any groups\n"); + ret = ENOENT; + goto done; + } + + parents = talloc_zero_array(tmp_ctx, + const char *, + memberof->num_values + 1); + if (parents == NULL) { + ret = ENOMEM; + goto done; + } + + for (size_t i = 0; i < memberof->num_values; i++) { + parents[i] = talloc_strdup(parents, + (const char *) memberof->values[i].data); + if (parents[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + *_parents = talloc_steal(mem_ctx, parents); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +sdap_nested_group_lookup_external_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_netgroups.c b/src/providers/ldap/sdap_async_netgroups.c new file mode 100644 index 0000000..db486d3 --- /dev/null +++ b/src/providers/ldap/sdap_async_netgroups.c @@ -0,0 +1,778 @@ +/* + SSSD + + Async LDAP Helper routines for netgroups + + Authors: + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" + +bool is_dn(const char *str) +{ + int ret; + LDAPDN dn; + + ret = ldap_str2dn(str, &dn, LDAP_DN_FORMAT_LDAPV3); + ldap_dnfree(dn); + + return (ret == LDAP_SUCCESS ? true : false); +} + +static errno_t sdap_save_netgroup(TALLOC_CTX *memctx, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs *attrs, + char **_timestamp, + time_t now) +{ + struct ldb_message_element *el; + struct sysdb_attrs *netgroup_attrs; + const char *name = NULL; + int ret; + char *timestamp = NULL; + char **missing = NULL; + + ret = sdap_get_netgroup_primary_name(opts, attrs, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get netgroup name\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Processing netgroup %s\n", name); + + netgroup_attrs = sysdb_new_attrs(memctx); + if (!netgroup_attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sdap_attrs_add_string(attrs, SYSDB_ORIG_DN, + "original DN", + name, netgroup_attrs); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_get_el(attrs, + opts->netgroup_map[SDAP_AT_NETGROUP_MODSTAMP].sys_name, + &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Original mod-Timestamp is not available for [%s].\n", + name); + } else { + ret = sysdb_attrs_add_string(netgroup_attrs, + opts->netgroup_map[SDAP_AT_NETGROUP_MODSTAMP].sys_name, + (const char*)el->values[0].data); + if (ret) { + goto fail; + } + timestamp = talloc_strdup(memctx, (const char*)el->values[0].data); + if (!timestamp) { + ret = ENOMEM; + goto fail; + } + } + + ret = sdap_attrs_add_list(attrs, + opts->netgroup_map[SDAP_AT_NETGROUP_TRIPLE].sys_name, + "netgroup triple", + name, netgroup_attrs); + if (ret != EOK) { + goto fail; + } + + ret = sdap_attrs_add_list(attrs, + opts->netgroup_map[SDAP_AT_NETGROUP_MEMBER].sys_name, + "original members", + name, netgroup_attrs); + if (ret != EOK) { + goto fail; + } + + ret = sdap_attrs_add_list(attrs, SYSDB_NETGROUP_MEMBER, + "members", name, netgroup_attrs); + if (ret != EOK) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Storing info for netgroup %s\n", name); + + ret = sdap_save_all_names(name, attrs, dom, SYSDB_MEMBER_NETGROUP, + netgroup_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to save netgroup names\n"); + goto fail; + } + + /* Make sure that any attributes we requested from LDAP that we + * did not receive are also removed from the sysdb + */ + ret = list_missing_attrs(attrs, opts->netgroup_map, SDAP_OPTS_NETGROUP, + attrs, &missing); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to list missing attributes\n"); + goto fail; + } + + /* We store memberNisNetgroup from LDAP as originalMemberNisNetgroup in + * sysdb. It may contain simple name or DN. That's the reason why we always + * translate/generate simple name and store it in SYSDB_NETGROUP_MEMBER + * (memberNisNetgroup) in sysdb which is internally used for searching + * netgropus. + * We need to ensure if originalMemberNisNetgroup is missing, + * memberNisNetgroup is missing too. + */ + if (string_in_list(SYSDB_ORIG_NETGROUP_MEMBER, missing, false)) { + ret = add_string_to_list(attrs, SYSDB_NETGROUP_MEMBER, &missing); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add string into list\n"); + goto fail; + } + } + + ret = sysdb_add_netgroup(dom, name, NULL, netgroup_attrs, missing, + dom->netgroup_timeout, now); + if (ret) goto fail; + + if (_timestamp) { + *_timestamp = timestamp; + } + + return EOK; + +fail: + DEBUG(SSSDBG_OP_FAILURE, "Failed to save netgroup %s\n", name); + return ret; +} + +errno_t update_dn_list(struct dn_item *dn_list, const size_t count, + struct ldb_message **res, bool *all_resolved) +{ + struct dn_item *dn_item; + size_t c; + const char *dn; + const char *cn; + bool not_resolved = false; + + *all_resolved = false; + + DLIST_FOR_EACH(dn_item, dn_list) { + if (dn_item->cn != NULL) { + continue; + } + + for(c = 0; c < count; c++) { + dn = ldb_msg_find_attr_as_string(res[c], SYSDB_ORIG_DN, NULL); + if (dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing original DN.\n"); + return EINVAL; + } + if (strcmp(dn, dn_item->dn) == 0) { + DEBUG(SSSDBG_TRACE_ALL, + "Found matching entry for [%s].\n", dn_item->dn); + cn = ldb_msg_find_attr_as_string(res[c], SYSDB_NAME, NULL); + if (cn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing name.\n"); + return EINVAL; + } + dn_item->cn = talloc_strdup(dn_item, cn); + break; + } + } + + if (dn_item->cn == NULL) { + not_resolved = true; + } + } + + *all_resolved = !not_resolved; + + return EOK; +} + +struct netgr_translate_members_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + + struct sysdb_attrs **netgroups; + size_t count; + struct dn_item *dn_list; + struct dn_item *dn_item; + struct dn_item *dn_idx; +}; + +static errno_t netgr_translate_members_ldap_step(struct tevent_req *req); +static void netgr_translate_members_ldap_done(struct tevent_req *subreq); + +struct tevent_req *netgr_translate_members_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + const size_t count, + struct sysdb_attrs **netgroups) +{ + struct tevent_req *req; + struct netgr_translate_members_state *state; + size_t c; + size_t mc; + const char **member_list; + size_t sysdb_count; + int ret; + struct ldb_message **sysdb_res; + struct dn_item *dn_item; + char *dn_filter; + char *sysdb_filter; + struct ldb_dn *netgr_basedn; + bool all_resolved; + const char *cn_attr[] = { SYSDB_NAME, SYSDB_ORIG_DN, NULL }; + + req = tevent_req_create(memctx, &state, + struct netgr_translate_members_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->netgroups = netgroups; + state->count = count; + state->dn_list = NULL; + state->dn_item = NULL; + state->dn_idx = NULL; + + for (c = 0; c < count; c++) { + ret = sysdb_attrs_get_string_array(netgroups[c], + SYSDB_ORIG_NETGROUP_MEMBER, state, + &member_list); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, "Missing netgroup members.\n"); + continue; + } + + for (mc = 0; member_list[mc] != NULL; mc++) { + if (is_dn(member_list[mc])) { + dn_item = talloc_zero(state, struct dn_item); + if (dn_item == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto fail; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Adding [%s] to DN list.\n", member_list[mc]); + dn_item->netgroup = netgroups[c]; + dn_item->dn = member_list[mc]; + DLIST_ADD(state->dn_list, dn_item); + } else { + ret = sysdb_attrs_add_string(netgroups[c], SYSDB_NETGROUP_MEMBER, + member_list[mc]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto fail; + } + } + } + } + + if (state->dn_list == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No DNs found among netgroup members.\n"); + tevent_req_done(req); + tevent_req_post(req, ev); + return req; + } + + dn_filter = talloc_strdup(state, "(|"); + if (dn_filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto fail; + } + + DLIST_FOR_EACH(dn_item, state->dn_list) { + dn_filter = talloc_asprintf_append(dn_filter, "(%s=%s)", + SYSDB_ORIG_DN, dn_item->dn); + if (dn_filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n"); + ret = ENOMEM; + goto fail; + } + } + + dn_filter = talloc_asprintf_append(dn_filter, ")"); + if (dn_filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n"); + ret = ENOMEM; + goto fail; + } + + sysdb_filter = talloc_asprintf(state, "(&(%s)%s)", SYSDB_NC, dn_filter); + if (sysdb_filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto fail; + } + + netgr_basedn = sysdb_netgroup_base_dn(state, dom); + if (netgr_basedn == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_search_entry(state, sysdb, netgr_basedn, LDB_SCOPE_BASE, + sysdb_filter, cn_attr, &sysdb_count, &sysdb_res); + talloc_zfree(netgr_basedn); + talloc_zfree(sysdb_filter); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_entry failed.\n"); + goto fail; + } + + if (ret == EOK) { + ret = update_dn_list(state->dn_list, sysdb_count, sysdb_res, + &all_resolved); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "update_dn_list failed.\n"); + goto fail; + } + + if (all_resolved) { + DLIST_FOR_EACH(dn_item, state->dn_list) { + ret = sysdb_attrs_add_string(dn_item->netgroup, + SYSDB_NETGROUP_MEMBER, + dn_item->cn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto fail; + } + } + + tevent_req_done(req); + tevent_req_post(req, ev); + return req; + } + } + + state->dn_idx = state->dn_list; + ret = netgr_translate_members_ldap_step(req); + if (ret != EOK && ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, + "netgr_translate_members_ldap_step failed.\n"); + goto fail; + } + + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +/* netgr_translate_members_ldap_step() returns + * EOK: if everthing is translated, the caller can call tevent_req_done + * EAGAIN: if there are still members waiting to be translated, the caller + * should return to the mainloop + * Exyz: every other return code indicates an error and tevent_req_error + * should be called + */ +static errno_t netgr_translate_members_ldap_step(struct tevent_req *req) +{ + struct netgr_translate_members_state *state = tevent_req_data(req, + struct netgr_translate_members_state); + const char **cn_attr; + char *filter = NULL; + struct tevent_req *subreq; + int ret; + + DLIST_FOR_EACH(state->dn_item, state->dn_idx) { + if (state->dn_item->cn == NULL) { + break; + } + } + if (state->dn_item == NULL) { + DLIST_FOR_EACH(state->dn_item, state->dn_list) { + ret = sysdb_attrs_add_string(state->dn_item->netgroup, + SYSDB_NETGROUP_MEMBER, + state->dn_item->cn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_add_string failed.\n"); + tevent_req_error(req, ret); + return ret; + } + } + + return EOK; + } + + if (!sss_ldap_dn_in_search_bases(state, state->dn_item->dn, + state->opts->sdom->netgroup_search_bases, + &filter)) { + /* not in search base, skip it */ + state->dn_idx = state->dn_item->next; + DLIST_REMOVE(state->dn_list, state->dn_item); + return netgr_translate_members_ldap_step(req); + } + + cn_attr = talloc_array(state, const char *, 3); + if (cn_attr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + cn_attr[0] = state->opts->netgroup_map[SDAP_AT_NETGROUP_NAME].name; + cn_attr[1] = "objectclass"; + cn_attr[2] = NULL; + + DEBUG(SSSDBG_TRACE_ALL, "LDAP base search for [%s].\n", state->dn_item->dn); + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + state->dn_item->dn, LDAP_SCOPE_BASE, filter, + cn_attr, state->opts->netgroup_map, + SDAP_OPTS_NETGROUP, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_send failed.\n"); + return ENOMEM; + } + talloc_steal(subreq, cn_attr); + + tevent_req_set_callback(subreq, netgr_translate_members_ldap_done, req); + return EAGAIN; +} + +static void netgr_translate_members_ldap_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct netgr_translate_members_state *state = tevent_req_data(req, + struct netgr_translate_members_state); + int ret; + size_t count; + struct sysdb_attrs **netgroups; + const char *str; + + ret = sdap_get_generic_recv(subreq, state, &count, &netgroups); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic request failed.\n"); + goto fail; + } + + switch (count) { + case 0: + DEBUG(SSSDBG_FATAL_FAILURE, + "sdap_get_generic_recv found no entry for [%s].\n", + state->dn_item->dn); + break; + case 1: + ret = sysdb_attrs_get_string(netgroups[0], SYSDB_NAME, &str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_add_string failed.\n"); + break; + } + state->dn_item->cn = talloc_strdup(state->dn_item, str); + if (state->dn_item->cn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected number of results [%zu] for base search.\n", + count); + } + + if (state->dn_item->cn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to resolve netgroup name for DN [%s], using DN.\n", + state->dn_item->dn); + state->dn_item->cn = talloc_strdup(state->dn_item, state->dn_item->dn); + } + + state->dn_idx = state->dn_item->next; + ret = netgr_translate_members_ldap_step(req); + if (ret != EOK && ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, + "netgr_translate_members_ldap_step failed.\n"); + goto fail; + } + + if (ret == EOK) { + tevent_req_done(req); + } + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static errno_t netgroup_translate_ldap_members_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *count, + struct sysdb_attrs ***netgroups) +{ + struct netgr_translate_members_state *state = tevent_req_data(req, + struct netgr_translate_members_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *count = state->count; + *netgroups = talloc_steal(mem_ctx, state->netgroups); + + return EOK; +} + +/* ==Search-Netgroups-with-filter============================================ */ + +struct sdap_get_netgroups_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + + char *higher_timestamp; + struct sysdb_attrs **netgroups; + size_t count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t sdap_get_netgroups_next_base(struct tevent_req *req); +static void sdap_get_netgroups_process(struct tevent_req *subreq); +static void netgr_translate_members_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_netgroups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_get_netgroups_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_get_netgroups_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->sysdb = sysdb; + state->attrs = attrs; + state->higher_timestamp = NULL; + state->netgroups = NULL; + state->count = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Netgroup lookup request without a netgroup search base\n"); + ret = EINVAL; + goto done; + } + + + ret = sdap_get_netgroups_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + return req; +} + +static errno_t sdap_get_netgroups_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_netgroups_state *state; + + state = tevent_req_data(req, struct sdap_get_netgroups_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for netgroups with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->netgroup_map, SDAP_OPTS_NETGROUP, + state->timeout, + false); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_netgroups_process, req); + + return EOK; +} + +static void sdap_get_netgroups_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_netgroups_state *state = tevent_req_data(req, + struct sdap_get_netgroups_state); + int ret; + + ret = sdap_get_generic_recv(subreq, state, + &state->count, &state->netgroups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for netgroups, returned %zu results.\n", state->count); + + if (state->count == 0) { + /* No netgroups found in this search */ + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_netgroups_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ENOENT); + } + return; + } + + tevent_req_error(req, ENOENT); + return; + } + + subreq = netgr_translate_members_send(state, state->ev, state->opts, + state->sh, state->dom, state->sysdb, + state->count, state->netgroups); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, netgr_translate_members_done, req); + + return; + +} + +static void netgr_translate_members_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_netgroups_state *state = tevent_req_data(req, + struct sdap_get_netgroups_state); + int ret; + size_t c; + time_t now; + + ret = netgroup_translate_ldap_members_recv(subreq, state, &state->count, + &state->netgroups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + now = time(NULL); + for (c = 0; c < state->count; c++) { + ret = sdap_save_netgroup(state, + state->dom, + state->opts, + state->netgroups[c], + &state->higher_timestamp, + now); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store netgroups.\n"); + tevent_req_error(req, ret); + return; + } + } + + DEBUG(SSSDBG_TRACE_ALL, "Saving %zu Netgroups - Done\n", state->count); + + tevent_req_done(req); +} + +int sdap_get_netgroups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp, + size_t *reply_count, + struct sysdb_attrs ***reply) +{ + struct sdap_get_netgroups_state *state = tevent_req_data(req, + struct sdap_get_netgroups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (timestamp) { + *timestamp = talloc_steal(mem_ctx, state->higher_timestamp); + } + + if (reply_count) { + *reply_count = state->count; + } + + if (reply) { + *reply = talloc_steal(mem_ctx, state->netgroups); + } + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_private.h b/src/providers/ldap/sdap_async_private.h new file mode 100644 index 0000000..90ed365 --- /dev/null +++ b/src/providers/ldap/sdap_async_private.h @@ -0,0 +1,196 @@ +/* + SSSD + + Async LDAP Helper routines + + Copyright (C) Simo Sorce + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_ASYNC_PRIVATE_H_ +#define _SDAP_ASYNC_PRIVATE_H_ + +#include "config.h" +#include "util/sss_krb5.h" +#include "providers/ldap/sdap_async.h" + +struct dn_item { + const char *dn; + /* Parent netgroup containing this record */ + struct sysdb_attrs *netgroup; + char *cn; + struct dn_item *next; + struct dn_item *prev; +}; + +bool is_dn(const char *str); +errno_t update_dn_list(struct dn_item *dn_list, + const size_t count, + struct ldb_message **res, + bool *all_resolved); + +struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx); + +void sdap_ldap_result(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *pvt); + +int setup_ldap_connection_callbacks(struct sdap_handle *sh, + struct tevent_context *ev); +int remove_ldap_connection_callbacks(struct sdap_handle *sh); + +int get_fd_from_ldap(LDAP *ldap, int *fd); + +errno_t sdap_set_connected(struct sdap_handle *sh, struct tevent_context *ev); + +errno_t sdap_call_conn_cb(const char *uri,int fd, struct sdap_handle *sh); + +int sdap_op_get_msgid(struct sdap_op *op); + +int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_handle *sh, int msgid, const char *stat_info, + sdap_op_callback_t *callback, void *data, + int timeout, struct sdap_op **_op); + +struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh); +int sdap_get_rootdse_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sysdb_attrs **rootdse); + +errno_t deref_string_to_val(const char *str, int *val); + +/* Extract server IP from sdap_handle and return it as string or NULL in case + * of an error */ +const char *sdap_get_server_peer_str(struct sdap_handle *sh); + +/* Same as sdap_get_server_peer_str() but always returns a strings */ +const char *sdap_get_server_peer_str_safe(struct sdap_handle *sh); + +/* from sdap_child_helpers.c */ + +struct tevent_req *sdap_get_tgt_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *realm_str, + const char *princ_str, + const char *keytab_name, + int32_t lifetime, + int timeout); + +int sdap_get_tgt_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + int *result, + krb5_error_code *kerr, + char **ccname, + time_t *expire_time_out); + +int sdap_save_users(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **users, + int num_users, + struct sysdb_attrs *mapped_attrs, + char **_usn_value); + +int sdap_initgr_common_store(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + const char *name, + enum sysdb_member_type type, + char **sysdb_grouplist, + struct sysdb_attrs **ldap_groups, + int ldap_groups_count); + +errno_t get_sysdb_grouplist(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + char ***grouplist); + +errno_t get_sysdb_grouplist_dn(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + char ***grouplist); + +/* from sdap_async_nested_groups.c */ +struct tevent_req *sdap_nested_group_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sysdb_attrs *group); + +errno_t sdap_nested_group_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + unsigned long *_num_users, + struct sysdb_attrs ***_users, + unsigned long *_num_groups, + struct sysdb_attrs ***_groups, + hash_table_t **missing_external); + +struct tevent_req * +sdap_nested_group_lookup_external_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *group_dom, + struct sdap_ext_member_ctx *ext_ctx, + hash_table_t *missing_external); +errno_t +sdap_nested_group_lookup_external_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req); + +/* from sdap_async_initgroups.c */ +errno_t sdap_add_incomplete_groups(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + char **sysdb_groupnames, + struct sysdb_attrs **ldap_groups, + int ldap_groups_count); + +/* from sdap_ad_groups.c */ +errno_t sdap_check_ad_group_type(struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs *group_attrs, + const char *group_name, + bool *_need_filter); + +struct tevent_req *rfc2307bis_nested_groups_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct sdap_options *opts, struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, struct sdap_handle *sh, + struct sdap_search_base **search_bases, + struct sysdb_attrs **groups, size_t num_groups, + hash_table_t *group_hash, size_t nesting); +errno_t rfc2307bis_nested_groups_recv(struct tevent_req *req); + +errno_t sdap_nested_groups_store(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs **groups, + unsigned long count); + +struct tevent_req * +sdap_ad_get_domain_local_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_domain *local_sdom, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sysdb_attrs **groups, + size_t num_groups); +errno_t sdap_ad_get_domain_local_groups_recv(struct tevent_req *req); +#endif /* _SDAP_ASYNC_PRIVATE_H_ */ diff --git a/src/providers/ldap/sdap_async_resolver_enum.c b/src/providers/ldap/sdap_async_resolver_enum.c new file mode 100644 index 0000000..8c92260 --- /dev/null +++ b/src/providers/ldap/sdap_async_resolver_enum.c @@ -0,0 +1,318 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/ldap_resolver_enum.h" +#include "providers/ldap/sdap_async_resolver_enum.h" + +static errno_t sdap_dom_resolver_enum_retry(struct tevent_req *req, + struct sdap_id_op *op, + tevent_req_fn tcb); +static bool sdap_dom_resolver_enum_connected(struct tevent_req *subreq); +static void sdap_dom_resolver_enum_get_iphost(struct tevent_req *subreq); +static void sdap_dom_resolver_enum_iphost_done(struct tevent_req *subreq); +static void sdap_dom_resolver_enum_get_ipnetwork(struct tevent_req *subreq); +static void sdap_dom_resolver_enum_ipnetwork_done(struct tevent_req *subreq); + +struct sdap_dom_resolver_enum_state { + struct tevent_context *ev; + struct sdap_resolver_ctx *resolver_ctx; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *iphost_op; + struct sdap_id_op *ipnetwork_op; + + bool purge; +}; + +struct tevent_req * +sdap_dom_resolver_enum_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_resolver_ctx *resolver_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn) +{ + struct tevent_req *req; + struct sdap_dom_resolver_enum_state *state; + int t; + errno_t ret; + + req = tevent_req_create(memctx, &state, struct sdap_dom_resolver_enum_state); + if (req == NULL) return NULL; + + state->ev = ev; + state->resolver_ctx = resolver_ctx; + state->id_ctx = id_ctx; + state->sdom = sdom; + state->conn = conn; + state->resolver_ctx->last_enum = tevent_timeval_current(); + + t = dp_opt_get_int(resolver_ctx->id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if ((state->resolver_ctx->last_purge.tv_sec + t) < state->resolver_ctx->last_enum.tv_sec) { + state->purge = true; + } + + state->iphost_op = sdap_id_op_create(state, conn->conn_cache); + if (state->iphost_op == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_create failed for iphosts\n"); + ret = EIO; + goto fail; + } + + ret = sdap_dom_resolver_enum_retry(req, state->iphost_op, + sdap_dom_resolver_enum_get_iphost); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_dom_enum_retry failed\n"); + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +sdap_dom_resolver_enum_retry(struct tevent_req *req, + struct sdap_id_op *op, + tevent_req_fn tcb) +{ + struct sdap_dom_resolver_enum_state *state; + struct tevent_req *subreq; + errno_t ret; + + state = tevent_req_data(req, struct sdap_dom_resolver_enum_state); + subreq = sdap_id_op_connect_send(op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_id_op_connect_send failed: %d\n", ret); + return ret; + } + + tevent_req_set_callback(subreq, tcb, req); + return EOK; +} + +static bool sdap_dom_resolver_enum_connected(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, + "Backend is marked offline, retry later!\n"); + tevent_req_done(req); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Domain enumeration failed to connect to " \ + "LDAP server: (%d)[%s]\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } + return false; + } + + return true; +} + +static void sdap_dom_resolver_enum_get_iphost(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_resolver_enum_state *state; + + state = tevent_req_data(req, struct sdap_dom_resolver_enum_state); + + if (sdap_dom_resolver_enum_connected(subreq) == false) { + return; + } + + subreq = enum_iphosts_send(state, state->ev, + state->id_ctx, + state->iphost_op, + state->purge); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_dom_resolver_enum_iphost_done, req); +} + +static void sdap_dom_resolver_enum_iphost_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_resolver_enum_state *state; + errno_t ret; + int dp_error; + + state = tevent_req_data(req, struct sdap_dom_resolver_enum_state); + + ret = enum_iphosts_recv(subreq); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->iphost_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_dom_resolver_enum_retry(req, state->iphost_op, + sdap_dom_resolver_enum_get_iphost); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, "Backend is offline, retrying later\n"); + tevent_req_done(req); + return; + } else if (ret != EOK && ret != ENOENT) { + /* Non-recoverable error */ + DEBUG(SSSDBG_OP_FAILURE, + "IP hosts enumeration failed: %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->ipnetwork_op = sdap_id_op_create(state, state->conn->conn_cache); + if (state->ipnetwork_op == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_id_op_create failed for IP networks\n"); + tevent_req_error(req, EIO); + return; + } + + ret = sdap_dom_resolver_enum_retry(req, state->ipnetwork_op, + sdap_dom_resolver_enum_get_ipnetwork); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Continues to sdap_dom_resolver_enum_get_ipnetwork */ +} + +static void sdap_dom_resolver_enum_get_ipnetwork(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_resolver_enum_state *state; + + state = tevent_req_data(req, struct sdap_dom_resolver_enum_state); + + if (sdap_dom_resolver_enum_connected(subreq) == false) { + return; + } + + subreq = enum_ipnetworks_send(state, state->ev, + state->id_ctx, + state->ipnetwork_op, + state->purge); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_dom_resolver_enum_ipnetwork_done, req); +} + +static void sdap_dom_resolver_enum_ipnetwork_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_resolver_enum_state *state; + errno_t ret; + int dp_error; + + state = tevent_req_data(req, struct sdap_dom_resolver_enum_state); + + ret = enum_ipnetworks_recv(subreq); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->ipnetwork_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_dom_resolver_enum_retry(req, state->ipnetwork_op, + sdap_dom_resolver_enum_get_ipnetwork); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, "Backend is offline, retrying later\n"); + tevent_req_done(req); + return; + } else if (ret != EOK && ret != ENOENT) { + /* Non-recoverable error */ + DEBUG(SSSDBG_OP_FAILURE, + "IP networks enumeration failed: %d: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + /* Ok, we've completed an enumeration. Save this to the + * sysdb so we can postpone starting up the enumeration + * process on the next SSSD service restart (to avoid + * slowing down system boot-up + */ + ret = sysdb_set_enumerated(state->sdom->dom, SYSDB_HAS_ENUMERATED_RESOLVER, + true); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not mark domain as having enumerated.\n"); + /* This error is non-fatal, so continue */ + } + + if (state->purge) { + ret = ldap_resolver_cleanup(state->resolver_ctx); + if (ret != EOK) { + /* Not fatal, worst case we'll have stale entries that would be + * removed on a subsequent online lookup + */ + DEBUG(SSSDBG_MINOR_FAILURE, "Cleanup failed: [%d]: %s\n", + ret, sss_strerror(ret)); + } + + } + + tevent_req_done(req); +} + +errno_t sdap_dom_resolver_enum_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_resolver_enum.h b/src/providers/ldap/sdap_async_resolver_enum.h new file mode 100644 index 0000000..e096b74 --- /dev/null +++ b/src/providers/ldap/sdap_async_resolver_enum.h @@ -0,0 +1,36 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_ASYNC_RESOLVER_ENUM_H_ +#define _SDAP_ASYNC_RESOLVER_ENUM_H_ + +struct tevent_req * +sdap_dom_resolver_enum_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_resolver_ctx *resolver_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn); + +errno_t sdap_dom_resolver_enum_recv(struct tevent_req *req); + +#endif /* _SDAP_ASYNC_RESOLVER_ENUM_H_ */ diff --git a/src/providers/ldap/sdap_async_services.c b/src/providers/ldap/sdap_async_services.c new file mode 100644 index 0000000..5fa3bca --- /dev/null +++ b/src/providers/ldap/sdap_async_services.c @@ -0,0 +1,644 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "db/sysdb_services.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_get_services_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + bool enumeration; + + char *higher_usn; + struct sysdb_attrs **services; + size_t count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t +sdap_get_services_next_base(struct tevent_req *req); +static void +sdap_get_services_process(struct tevent_req *subreq); +static errno_t +sdap_save_services(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **services, + size_t num_services, + char **_usn_value); +static errno_t +sdap_save_service(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now); + +struct tevent_req * +sdap_get_services_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_get_services_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_get_services_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->sysdb = sysdb; + state->attrs = attrs; + state->higher_usn = NULL; + state->services = NULL; + state->count = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + state->enumeration = enumeration; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Services lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + ret = sdap_get_services_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + + return req; +} + +static errno_t +sdap_get_services_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_services_state *state; + + state = tevent_req_data(req, struct sdap_get_services_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for services with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->service_map, SDAP_OPTS_SERVICES, + state->timeout, + state->enumeration); /* If we're enumerating, we need paging */ + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_services_process, req); + + return EOK; +} + +static void +sdap_get_services_process(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_get_services_state *state = + tevent_req_data(req, struct sdap_get_services_state); + int ret; + size_t count, i; + struct sysdb_attrs **services; + bool next_base = false; + + ret = sdap_get_generic_recv(subreq, state, + &count, &services); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for services, returned %zu results.\n", + count); + + if (state->enumeration || count == 0) { + /* No services found in this search or enumerating */ + next_base = true; + } + + /* Add this batch of sevices to the list */ + if (count > 0) { + state->services = + talloc_realloc(state, + state->services, + struct sysdb_attrs *, + state->count + count + 1); + if (!state->services) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Copy the new services into the list + */ + for (i = 0; i < count; i++) { + state->services[state->count + i] = + talloc_steal(state->services, services[i]); + } + + state->count += count; + state->services[state->count] = NULL; + } + + if (next_base) { + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_services_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + } + + /* No more search bases + * Return ENOENT if no services were found + */ + if (state->count == 0) { + tevent_req_error(req, ENOENT); + return; + } + + ret = sdap_save_services(state, state->sysdb, + state->dom, state->opts, + state->services, state->count, + &state->higher_usn); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store services.\n"); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Saving %zu services - Done\n", state->count); + + tevent_req_done(req); +} + +static errno_t +sdap_save_services(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **services, + size_t num_services, + char **_usn_value) +{ + errno_t ret, sret; + time_t now; + size_t i; + bool in_transaction = false; + char *higher_usn = NULL; + char *usn_value; + TALLOC_CTX *tmp_ctx; + + if (num_services == 0) { + /* Nothing to do */ + return ENOENT; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + + in_transaction = true; + + now = time(NULL); + for (i = 0; i < num_services; i++) { + usn_value = NULL; + + ret = sdap_save_service(tmp_ctx, sysdb, opts, dom, + services[i], + &usn_value, now); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store service %zu. Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Service [%zu/%zu] processed!\n", i, num_services); + } + + if (usn_value) { + if (higher_usn) { + if ((strlen(usn_value) > strlen(higher_usn)) || + (strcmp(usn_value, higher_usn) > 0)) { + talloc_zfree(higher_usn); + higher_usn = usn_value; + } else { + talloc_zfree(usn_value); + } + } else { + higher_usn = usn_value; + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + if (_usn_value) { + *_usn_value = talloc_steal(mem_ctx, higher_usn); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to cancel transaction!\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sdap_save_service(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct sysdb_attrs *svc_attrs; + struct ldb_message_element *el; + char *usn_value = NULL; + const char *name = NULL; + const char **aliases; + const char **protocols; + const char **cased_protocols = NULL; + const char **store_protocols; + char **missing; + uint16_t port; + uint64_t cache_timeout; + + DEBUG(SSSDBG_TRACE_ALL, "Saving service\n"); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + svc_attrs = sysdb_new_attrs(tmp_ctx); + if (!svc_attrs) { + ret = ENOMEM; + goto done; + } + + /* Identify the primary name of this services */ + ret = sdap_get_primary_name(opts->service_map[SDAP_AT_SERVICE_NAME].name, + attrs, &name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not determine the primary name of the service\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Primary name: [%s]\n", name); + + + /* Handle any available aliases */ + ret = sysdb_attrs_get_aliases(tmp_ctx, attrs, name, + !dom->case_sensitive, + &aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify service aliases\n"); + goto done; + } + + /* Get the port number */ + ret = sysdb_attrs_get_uint16_t(attrs, SYSDB_SVC_PORT, &port); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify service port: [%s]\n", + strerror(ret)); + goto done; + } + + /* Get the protocols this service offers on that port */ + ret = sysdb_attrs_get_string_array(attrs, SYSDB_SVC_PROTO, + tmp_ctx, &protocols); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify service protocols: [%s]\n", + strerror(ret)); + goto done; + } + + if (dom->case_sensitive == false) { + /* Don't perform the extra mallocs if not necessary */ + ret = sss_get_cased_name_list(tmp_ctx, protocols, + dom->case_sensitive, &cased_protocols); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get case_sensitive protocols names: [%s]\n", + strerror(ret)); + goto done; + } + } + + store_protocols = dom->case_sensitive ? protocols : cased_protocols; + + /* Get the USN value, if available */ + ret = sysdb_attrs_get_el(attrs, + opts->service_map[SDAP_AT_SERVICE_USN].sys_name, &el); + if (ret && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to retrieve USN value: [%s]\n", + strerror(ret)); + goto done; + } + if (ret == ENOENT || el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Original USN value is not available for [%s].\n", + name); + } else { + ret = sysdb_attrs_add_string(svc_attrs, + opts->service_map[SDAP_AT_SERVICE_USN].sys_name, + (const char*)el->values[0].data); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add USN value: [%s]\n", + strerror(ret)); + goto done; + } + usn_value = talloc_strdup(tmp_ctx, (const char*)el->values[0].data); + if (!usn_value) { + ret = ENOMEM; + goto done; + } + } + + /* Make sure to remove any extra attributes from the sysdb + * that have been removed from LDAP + */ + ret = list_missing_attrs(svc_attrs, opts->service_map, SDAP_OPTS_SERVICES, + attrs, &missing); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify removed attributes: [%s]\n", + strerror(ret)); + goto done; + } + + cache_timeout = dom->service_timeout; + + ret = sysdb_store_service(dom, name, port, aliases, store_protocols, + svc_attrs, missing, cache_timeout, now); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store service in the sysdb: [%s]\n", + strerror(ret)); + goto done; + } + + *_usn_value = talloc_steal(mem_ctx, usn_value); + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +sdap_get_services_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value) +{ + struct sdap_get_services_state *state = + tevent_req_data(req, struct sdap_get_services_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (usn_value) { + *usn_value = talloc_steal(mem_ctx, state->higher_usn); + } + + return EOK; +} + + +/* Enumeration routines */ + +struct enum_services_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_op *op; + struct sss_domain_info *domain; + struct sysdb_ctx *sysdb; + + char *filter; + const char **attrs; +}; + +static void +enum_services_op_done(struct tevent_req *subreq); + +struct tevent_req * +enum_services_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct enum_services_state *state; + + req = tevent_req_create(memctx, &state, struct enum_services_state); + if (!req) return NULL; + + state->ev = ev; + state->id_ctx = id_ctx; + state->domain = id_ctx->be->domain; + state->sysdb = id_ctx->be->domain->sysdb; + state->op = op; + + if (id_ctx->srv_opts && id_ctx->srv_opts->max_service_value && !purge) { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*)(%s=*)(%s>=%s)(!(%s=%s)))", + id_ctx->opts->service_map[SDAP_OC_SERVICE].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_NAME].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_PORT].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_PROTOCOL].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_USN].name, + id_ctx->srv_opts->max_service_value, + id_ctx->opts->service_map[SDAP_AT_SERVICE_USN].name, + id_ctx->srv_opts->max_service_value); + } else { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*)(%s=*))", + id_ctx->opts->service_map[SDAP_OC_SERVICE].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_NAME].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_PORT].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_PROTOCOL].name); + } + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, id_ctx->opts->service_map, + SDAP_OPTS_SERVICES, NULL, + &state->attrs, NULL); + if (ret != EOK) goto fail; + + subreq = sdap_get_services_send(state, state->ev, + state->domain, state->sysdb, + state->id_ctx->opts, + state->id_ctx->opts->sdom->service_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + true); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_services_op_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +enum_services_op_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct enum_services_state *state = + tevent_req_data(req, struct enum_services_state); + char *usn_value; + char *endptr = NULL; + unsigned usn_number; + int ret; + + ret = sdap_get_services_recv(state, subreq, &usn_value); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (usn_value) { + talloc_zfree(state->id_ctx->srv_opts->max_service_value); + state->id_ctx->srv_opts->max_service_value = + talloc_steal(state->id_ctx, usn_value); + errno = 0; + usn_number = strtoul(usn_value, &endptr, 10); + if (!errno && endptr && (*endptr == '\0') && (endptr != usn_value) + && (usn_number > state->id_ctx->srv_opts->last_usn)) { + state->id_ctx->srv_opts->last_usn = usn_number; + } + } + + DEBUG(SSSDBG_FUNC_DATA, "Services higher USN value: [%s]\n", + state->id_ctx->srv_opts->max_service_value); + + tevent_req_done(req); +} + +errno_t +enum_services_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_sudo.c b/src/providers/ldap/sdap_async_sudo.c new file mode 100644 index 0000000..28b65b6 --- /dev/null +++ b/src/providers/ldap/sdap_async_sudo.c @@ -0,0 +1,698 @@ +/* + SSSD + + Async LDAP Helper routines for sudo + + Authors: + Pavel Březina + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "providers/backend.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_ops.h" +#include "providers/ldap/sdap_sudo.h" +#include "providers/ldap/sdap_sudo_shared.h" +#include "db/sysdb_sudo.h" + +struct sdap_sudo_load_sudoers_state { + struct sysdb_attrs **rules; + size_t num_rules; +}; + +static void sdap_sudo_load_sudoers_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_sudo_load_sudoers_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *ldap_filter) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_sudo_load_sudoers_state *state; + struct sdap_search_base **sb; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_sudo_load_sudoers_state); + if (!req) { + return NULL; + } + + state->rules = NULL; + state->num_rules = 0; + + sb = opts->sdom->sudo_search_bases; + if (sb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "SUDOERS lookup request without a search base\n"); + ret = EINVAL; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch sudo rules\n"); + + subreq = sdap_search_bases_send(state, ev, opts, sh, sb, + opts->sudorule_map, true, 0, + ldap_filter, NULL, NULL); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_sudo_load_sudoers_done, req); + + ret = EOK; + +immediately: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_sudo_load_sudoers_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_sudo_load_sudoers_state *state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_load_sudoers_state); + + ret = sdap_search_bases_recv(subreq, state, &state->num_rules, + &state->rules); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_FUNC_DATA, "Received %zu sudo rules\n", + state->num_rules); + + tevent_req_done(req); + + return; +} + +static int sdap_sudo_load_sudoers_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *num_rules, + struct sysdb_attrs ***rules) +{ + struct sdap_sudo_load_sudoers_state *state; + + state = tevent_req_data(req, struct sdap_sudo_load_sudoers_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *num_rules = state->num_rules; + *rules = talloc_steal(mem_ctx, state->rules); + + return EOK; +} + +static char *sdap_sudo_build_host_filter(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + char **hostnames, + char **ip_addr, + bool netgroups, + bool regexp) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *filter = NULL; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return NULL; + } + + filter = talloc_strdup(tmp_ctx, "(|"); + if (filter == NULL) { + goto done; + } + + /* sudoHost is not specified and it is a cn=defaults rule */ + filter = talloc_asprintf_append_buffer(filter, "(&(!(%s=*))(%s=defaults))", + map[SDAP_AT_SUDO_HOST].name, + map[SDAP_AT_SUDO_NAME].name); + if (filter == NULL) { + goto done; + } + + /* ALL */ + filter = talloc_asprintf_append_buffer(filter, "(%s=ALL)", + map[SDAP_AT_SUDO_HOST].name); + if (filter == NULL) { + goto done; + } + + /* hostnames */ + if (hostnames != NULL) { + for (i = 0; hostnames[i] != NULL; i++) { + filter = talloc_asprintf_append_buffer(filter, "(%s=%s)", + map[SDAP_AT_SUDO_HOST].name, + hostnames[i]); + if (filter == NULL) { + goto done; + } + } + } + + /* ip addresses and networks */ + if (ip_addr != NULL) { + for (i = 0; ip_addr[i] != NULL; i++) { + filter = talloc_asprintf_append_buffer(filter, "(%s=%s)", + map[SDAP_AT_SUDO_HOST].name, + ip_addr[i]); + if (filter == NULL) { + goto done; + } + } + } + + /* sudoHost contains netgroup - will be filtered more by sudo */ + if (netgroups) { + filter = talloc_asprintf_append_buffer(filter, SDAP_SUDO_FILTER_NETGROUP, + map[SDAP_AT_SUDO_HOST].name, + "*"); + if (filter == NULL) { + goto done; + } + } + + /* sudoHost contains regexp - will be filtered more by sudo */ + /* from sudo match.c : + * #define has_meta(s) (strpbrk(s, "\\?*[]") != NULL) + */ + if (regexp) { + filter = talloc_asprintf_append_buffer(filter, + "(|(%s=*\\\\*)(%s=*?*)(%s=*\\2A*)" + "(%s=*[*]*))", + map[SDAP_AT_SUDO_HOST].name, + map[SDAP_AT_SUDO_HOST].name, + map[SDAP_AT_SUDO_HOST].name, + map[SDAP_AT_SUDO_HOST].name); + if (filter == NULL) { + goto done; + } + } + + filter = talloc_strdup_append_buffer(filter, ")"); + if (filter == NULL) { + goto done; + } + + talloc_steal(mem_ctx, filter); + +done: + talloc_free(tmp_ctx); + + return filter; +} + +static char *sdap_sudo_get_filter(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + struct sdap_sudo_ctx *sudo_ctx, + const char *rule_filter) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *host_filter = NULL; + char *filter = NULL; + + if (!sudo_ctx->use_host_filter) { + return talloc_strdup(mem_ctx, rule_filter); + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return NULL; + } + + host_filter = sdap_sudo_build_host_filter(tmp_ctx, map, + sudo_ctx->hostnames, + sudo_ctx->ip_addr, + sudo_ctx->include_netgroups, + sudo_ctx->include_regexp); + if (host_filter == NULL) { + goto done; + } + + filter = sdap_combine_filters(tmp_ctx, rule_filter, host_filter); + if (filter == NULL) { + goto done; + } + + talloc_steal(mem_ctx, filter); + +done: + talloc_free(tmp_ctx); + return filter; +} + +struct sdap_sudo_refresh_state { + struct sdap_sudo_ctx *sudo_ctx; + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_id_op *sdap_op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *search_filter; + const char *delete_filter; + bool update_usn; + + int dp_error; + size_t num_rules; +}; + +static errno_t sdap_sudo_refresh_retry(struct tevent_req *req); +static void sdap_sudo_refresh_connect_done(struct tevent_req *subreq); +static void sdap_sudo_refresh_hostinfo_done(struct tevent_req *subreq); +static errno_t sdap_sudo_refresh_sudoers(struct tevent_req *req); +static void sdap_sudo_refresh_done(struct tevent_req *subreq); + +struct tevent_req *sdap_sudo_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx, + const char *search_filter, + const char *delete_filter, + bool update_usn) +{ + struct tevent_req *req; + struct sdap_sudo_refresh_state *state; + struct sdap_id_ctx *id_ctx = sudo_ctx->id_ctx; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_refresh_state); + if (!req) { + return NULL; + } + + /* if we don't have a search filter, this request is meaningless */ + if (search_filter == NULL) { + ret = EINVAL; + goto immediately; + } + + state->sudo_ctx = sudo_ctx; + state->ev = id_ctx->be->ev; + state->opts = id_ctx->opts; + state->domain = id_ctx->be->domain; + state->sysdb = id_ctx->be->domain->sysdb; + state->dp_error = DP_ERR_FATAL; + state->update_usn = update_usn; + + state->sdap_op = sdap_id_op_create(state, id_ctx->conn->conn_cache); + if (!state->sdap_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + state->search_filter = talloc_strdup(state, search_filter); + if (state->search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->delete_filter = talloc_strdup(state, delete_filter); + if (delete_filter != NULL && state->delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = sdap_sudo_refresh_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, id_ctx->be->ev); + + return req; +} + +static errno_t sdap_sudo_refresh_retry(struct tevent_req *req) +{ + struct sdap_sudo_refresh_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: " + "%d(%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, sdap_sudo_refresh_connect_done, req); + + return EAGAIN; +} + +static void sdap_sudo_refresh_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_sudo_refresh_state *state; + int dp_error; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "SUDO LDAP connection failed " + "[%d]: %s\n", ret, strerror(ret)); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "SUDO LDAP connection successful\n"); + + /* Renew host information if needed. */ + if (state->sudo_ctx->run_hostinfo) { + subreq = sdap_sudo_get_hostinfo_send(state, state->opts, + state->sudo_ctx->id_ctx->be); + if (subreq == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_sudo_refresh_hostinfo_done, req); + state->sudo_ctx->run_hostinfo = false; + return; + } + + ret = sdap_sudo_refresh_sudoers(req); + if (ret != EAGAIN) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + } +} + +static void sdap_sudo_refresh_hostinfo_done(struct tevent_req *subreq) +{ + struct sdap_sudo_ctx *sudo_ctx; + struct sdap_sudo_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + sudo_ctx = state->sudo_ctx; + + ret = sdap_sudo_get_hostinfo_recv(sudo_ctx, subreq, &sudo_ctx->hostnames, + &sudo_ctx->ip_addr); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve host information, " + "host filter will be disabled [%d]: %s\n", + ret, sss_strerror(ret)); + sudo_ctx->use_host_filter = false; + } else { + sudo_ctx->use_host_filter = true; + } + + ret = sdap_sudo_refresh_sudoers(req); + if (ret != EAGAIN) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + } +} + +static errno_t sdap_sudo_refresh_sudoers(struct tevent_req *req) +{ + struct sdap_sudo_refresh_state *state; + struct tevent_req *subreq; + char *filter; + + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + /* We are connected. Host information may have changed during transition + * from offline to online state. At this point we can combine search + * and host filter. */ + filter = sdap_sudo_get_filter(state, state->opts->sudorule_map, + state->sudo_ctx, state->search_filter); + if (filter == NULL) { + return ENOMEM; + } + + subreq = sdap_sudo_load_sudoers_send(state, state->ev, + state->opts, + sdap_id_op_handle(state->sdap_op), + filter); + if (subreq == NULL) { + talloc_free(filter); + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_sudo_refresh_done, req); + + return EAGAIN; +} + +static errno_t sdap_sudo_qualify_names(struct sss_domain_info *dom, + struct sysdb_attrs **rules, + size_t rules_count) +{ + errno_t ret; + bool qualify; + struct ldb_message_element *el; + char *domain; + char *name; + const char *orig_name; + struct ldb_message_element unique_el; + + for (size_t i = 0; i < rules_count; i++) { + ret = sysdb_attrs_get_el_ext(rules[i], SYSDB_SUDO_CACHE_AT_USER, + false, &el); + if (ret != EOK) { + continue; + } + + unique_el.values = talloc_zero_array(rules, struct ldb_val, el->num_values); + if (unique_el.values == NULL) { + return ENOMEM; + } + unique_el.num_values = 0; + + for (size_t ii = 0; ii < el->num_values; ii++) { + orig_name = (const char *) el->values[ii].data; + + qualify = is_user_or_group_name(orig_name); + if (qualify) { + struct ldb_val fqval; + struct ldb_val *dup; + + ret = sss_parse_name(rules, dom->names, orig_name, + &domain, &name); + if (ret != EOK) { + continue; + } + + if (domain == NULL) { + domain = talloc_strdup(rules, dom->name); + if (domain == NULL) { + talloc_zfree(name); + return ENOMEM; + } + } + + fqval.data = (uint8_t * ) sss_create_internal_fqname(rules, + name, + domain); + talloc_zfree(domain); + talloc_zfree(name); + if (fqval.data == NULL) { + return ENOMEM; + } + fqval.length = strlen((const char *) fqval.data); + + /* Prevent saving duplicates in case the sudo rule contains + * e.g. foo and foo@domain + */ + dup = ldb_msg_find_val(&unique_el, &fqval); + if (dup != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Discarding duplicate value %s\n", (const char *) fqval.data); + talloc_free(fqval.data); + continue; + } + unique_el.values[unique_el.num_values].data = talloc_steal(unique_el.values, fqval.data); + unique_el.values[unique_el.num_values].length = fqval.length; + unique_el.num_values++; + } else { + unique_el.values[unique_el.num_values] = ldb_val_dup(unique_el.values, + &el->values[ii]); + unique_el.num_values++; + } + } + + talloc_zfree(el->values); + el->values = unique_el.values; + el->num_values = unique_el.num_values; + } + + return EOK; +} + +static void sdap_sudo_refresh_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_sudo_refresh_state *state; + struct sysdb_attrs **rules = NULL; + size_t rules_count = 0; + char *usn = NULL; + int dp_error; + int ret; + errno_t sret; + bool in_transaction = false; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + ret = sdap_sudo_load_sudoers_recv(subreq, state, &rules_count, &rules); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_sudo_refresh_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Received %zu rules\n", rules_count); + + /* Save users and groups fully qualified */ + ret = sdap_sudo_qualify_names(state->domain, rules, rules_count); + if (ret != EOK) { + goto done; + } + + /* start transaction */ + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + /* purge cache */ + ret = sysdb_sudo_purge(state->domain, state->delete_filter, + rules, rules_count); + if (ret != EOK) { + goto done; + } + + /* store rules */ + ret = sysdb_sudo_store(state->domain, rules, rules_count); + if (ret != EOK) { + goto done; + } + + /* commit transaction */ + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + DEBUG(SSSDBG_TRACE_FUNC, "Sudoers is successfully stored in cache\n"); + + if (state->update_usn) { + /* remember new usn */ + ret = sysdb_get_highest_usn(state, rules, rules_count, &usn); + if (ret == EOK) { + sdap_sudo_set_usn(state->sudo_ctx->id_ctx->srv_opts, usn); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to get highest USN [%d]: %s\n", + ret, sss_strerror(ret)); + } + } + + ret = EOK; + state->num_rules = rules_count; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(state->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + + state->dp_error = dp_error; + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +int sdap_sudo_refresh_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + int *dp_error, + size_t *num_rules) +{ + struct sdap_sudo_refresh_state *state; + + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + if (num_rules != NULL) { + *num_rules = state->num_rules; + } + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_sudo_hostinfo.c b/src/providers/ldap/sdap_async_sudo_hostinfo.c new file mode 100644 index 0000000..a3c3e10 --- /dev/null +++ b/src/providers/ldap/sdap_async_sudo_hostinfo.c @@ -0,0 +1,516 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_id_op.h" +#include "providers/ldap/sdap_sudo.h" +#include "resolv/async_resolv.h" + +static int sdap_sudo_get_ip_addresses(TALLOC_CTX *mem_ctx, char ***_ip_addr); + +struct sdap_sudo_get_hostinfo_state { + char **hostnames; + char **ip_addr; +}; + +struct sdap_sudo_get_hostnames_state { + struct tevent_context *ev; + struct resolv_ctx *resolv_ctx; + enum host_database *host_db; + enum restrict_family family_order; + char **hostnames; +}; + +static void sdap_sudo_get_hostinfo_done(struct tevent_req *req); + +static struct tevent_req *sdap_sudo_get_hostnames_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx); + +static void sdap_sudo_get_hostnames_done(struct tevent_req *subreq); + +static int sdap_sudo_get_hostnames_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char ***hostnames); + + +struct tevent_req * sdap_sudo_get_hostinfo_send(TALLOC_CTX *mem_ctx, + struct sdap_options *opts, + struct be_ctx *be_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_sudo_get_hostinfo_state *state = NULL; + char *conf_hostnames = NULL; + char *conf_ip_addr = NULL; + int ret = EOK; + + /* create request */ + req = tevent_req_create(mem_ctx, &state, + struct sdap_sudo_get_hostinfo_state); + if (req == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->hostnames = NULL; + state->ip_addr = NULL; + + /* load info from configuration */ + conf_hostnames = dp_opt_get_string(opts->basic, SDAP_SUDO_HOSTNAMES); + conf_ip_addr = dp_opt_get_string(opts->basic, SDAP_SUDO_IP); + + if (conf_hostnames != NULL) { + ret = split_on_separator(state, conf_hostnames, ' ', true, true, + &state->hostnames, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to parse hostnames [%d]: %s\n", ret, strerror(ret)); + goto done; + } else { + DEBUG(SSSDBG_CONF_SETTINGS, + "Hostnames set to: %s\n", conf_hostnames); + } + } + + if (conf_ip_addr != NULL) { + ret = split_on_separator(state, conf_ip_addr, ' ', true, true, + &state->ip_addr, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to parse IP addresses [%d]: %s\n", + ret, strerror(ret)); + goto done; + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "IP addresses set to: %s\n", + conf_ip_addr); + } + } + + /* if IP addresses are not specified, configure it automatically */ + if (state->ip_addr == NULL) { + ret = sdap_sudo_get_ip_addresses(state, &state->ip_addr); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to detect IP addresses [%d]: %s\n", + ret, strerror(ret)); + } + } + + /* if hostnames are not specified, configure it automatically */ + if (state->hostnames == NULL) { + subreq = sdap_sudo_get_hostnames_send(state, be_ctx); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_sudo_get_hostinfo_done, req); + ret = EAGAIN; + } + +done: + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, be_ctx->ev); + } + + return req; +} + +static void sdap_sudo_get_hostinfo_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_sudo_get_hostinfo_state *state = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_get_hostinfo_state); + + ret = sdap_sudo_get_hostnames_recv(state, subreq, &state->hostnames); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve hostnames [%d]: %s\n", + ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_sudo_get_hostinfo_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char ***hostnames, char ***ip_addr) +{ + struct sdap_sudo_get_hostinfo_state *state = NULL; + state = tevent_req_data(req, struct sdap_sudo_get_hostinfo_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *hostnames = talloc_steal(mem_ctx, state->hostnames); + *ip_addr = talloc_steal(mem_ctx, state->ip_addr); + + return EOK; +} + +static int sdap_sudo_get_ip_addresses(TALLOC_CTX *mem_ctx, + char ***_ip_addr_list) +{ + TALLOC_CTX *tmp_ctx = NULL; + char **ip_addr_list = NULL; + struct ifaddrs *ifaces = NULL; + struct ifaddrs *iface = NULL; + struct sockaddr_in ip4_addr; + struct sockaddr_in ip4_network; + struct sockaddr_in6 ip6_addr; + struct sockaddr_in6 ip6_network; + char ip_addr[INET6_ADDRSTRLEN + 1]; + char network_addr[INET6_ADDRSTRLEN + 1]; + in_addr_t ip4_netmask = 0; + uint32_t ip6_netmask = 0; + unsigned int netmask = 0; + void *sinx_addr = NULL; + void *sinx_network = NULL; + int addr_count = 0; + int ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + errno = 0; + ret = getifaddrs(&ifaces); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Could not read interfaces [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + for (iface = ifaces; iface != NULL; iface = iface->ifa_next) { + /* Some interfaces don't have an ifa_addr */ + if (!iface->ifa_addr) continue; + + netmask = 0; + switch (iface->ifa_addr->sa_family) { + case AF_INET: + memcpy(&ip4_addr, iface->ifa_addr, sizeof(struct sockaddr_in)); + memcpy(&ip4_network, iface->ifa_netmask, sizeof(struct sockaddr_in)); + + if (!check_ipv4_addr(&ip4_addr.sin_addr, + SSS_NO_LOOPBACK|SSS_NO_MULTICAST + |SSS_NO_BROADCAST)) { + continue; + } + + /* get network mask length */ + ip4_netmask = ntohl(ip4_network.sin_addr.s_addr); + while (ip4_netmask) { + netmask++; + ip4_netmask <<= 1; + } + + /* get network address */ + ip4_network.sin_addr.s_addr = ip4_addr.sin_addr.s_addr + & ip4_network.sin_addr.s_addr; + + sinx_addr = &ip4_addr.sin_addr; + sinx_network = &ip4_network.sin_addr; + break; + case AF_INET6: + memcpy(&ip6_addr, iface->ifa_addr, sizeof(struct sockaddr_in6)); + memcpy(&ip6_network, iface->ifa_netmask, sizeof(struct sockaddr_in6)); + + if (!check_ipv6_addr(&ip6_addr.sin6_addr, + SSS_NO_LOOPBACK|SSS_NO_MULTICAST)) { + continue; + } + + /* get network mask length */ + for (i = 0; i < 4; i++) { + ip6_netmask = ntohl(((uint32_t*)(&ip6_network.sin6_addr))[i]); + while (ip6_netmask) { + netmask++; + ip6_netmask <<= 1; + } + } + + /* get network address */ + for (i = 0; i < 4; i++) { + ((uint32_t*)(&ip6_network.sin6_addr))[i] = + ((uint32_t*)(&ip6_addr.sin6_addr))[i] + & ((uint32_t*)(&ip6_network.sin6_addr))[i]; + } + + sinx_addr = &ip6_addr.sin6_addr; + sinx_network = &ip6_network.sin6_addr; + break; + default: + /* skip other families */ + continue; + } + + /* ip address */ + errno = 0; + if (inet_ntop(iface->ifa_addr->sa_family, sinx_addr, + ip_addr, INET6_ADDRSTRLEN) == NULL) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "inet_ntop() failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + /* network */ + errno = 0; + if (inet_ntop(iface->ifa_addr->sa_family, sinx_network, + network_addr, INET6_ADDRSTRLEN) == NULL) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "inet_ntop() failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + addr_count += 2; + ip_addr_list = talloc_realloc(tmp_ctx, ip_addr_list, char*, + addr_count + 1); + if (ip_addr_list == NULL) { + ret = ENOMEM; + goto done; + } + + ip_addr_list[addr_count - 2] = talloc_strdup(ip_addr_list, ip_addr); + if (ip_addr_list[addr_count - 2] == NULL) { + ret = ENOMEM; + goto done; + } + + ip_addr_list[addr_count - 1] = talloc_asprintf(ip_addr_list, "%s/%d", + network_addr, netmask); + if (ip_addr_list[addr_count - 1] == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Found IP address: %s in network %s/%d\n", + ip_addr, network_addr, netmask); + } + + if (ip_addr_list) { + ip_addr_list[addr_count] = NULL; + } + *_ip_addr_list = talloc_steal(mem_ctx, ip_addr_list); + +done: + freeifaddrs(ifaces); + talloc_free(tmp_ctx); + + return ret; +} + +/* + * SUDO allows only one hostname that is returned from gethostname() + * (and set to "localhost" if the returned value is empty) + * and then - if allowed - resolves its fqdn using gethostbyname() or + * getaddrinfo() if available. + */ +static struct tevent_req *sdap_sudo_get_hostnames_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_sudo_get_hostnames_state *state = NULL; + char *dot = NULL; + char hostname[HOST_NAME_MAX + 1]; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_sudo_get_hostnames_state); + if (req == NULL) { + return NULL; + } + + state->ev = be_ctx->ev; + state->hostnames = NULL; + + /* hostname, fqdn and NULL */ + state->hostnames = talloc_zero_array(state, char*, 3); + if (state->hostnames == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + /* get hostname */ + + errno = 0; + ret = gethostname(hostname, sizeof(hostname)); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve machine hostname " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + hostname[HOST_NAME_MAX] = '\0'; + + state->hostnames[0] = talloc_strdup(state->hostnames, hostname); + if (state->hostnames[0] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + + dot = strchr(hostname, '.'); + if (dot != NULL) { + /* already a fqdn, determine hostname and finish */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Found fqdn: %s\n", hostname); + + *dot = '\0'; + DEBUG(SSSDBG_TRACE_INTERNAL, "Found hostname: %s\n", hostname); + + state->hostnames[1] = talloc_strdup(state->hostnames, hostname); + if (state->hostnames[1] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = EOK; + goto done; + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, "Found hostname: %s\n", hostname); + } + + state->resolv_ctx = be_ctx->be_res->resolv; + state->host_db = default_host_dbs; + + /* get fqdn */ + subreq = resolv_gethostbyname_send(state, state->ev, state->resolv_ctx, + hostname, be_ctx->be_res->family_order, + state->host_db); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_sudo_get_hostnames_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, be_ctx->ev); + } + + return req; +} + +static void sdap_sudo_get_hostnames_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_sudo_get_hostnames_state *state = NULL; + struct resolv_hostent *rhostent = NULL; + int resolv_status; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_get_hostnames_state); + + ret = resolv_gethostbyname_recv(subreq, state, &resolv_status, NULL, + &rhostent); + talloc_zfree(subreq); + if (ret == ENOENT) { + /* Empty result, just quit */ + DEBUG(SSSDBG_TRACE_INTERNAL, "No hostent found\n"); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not resolve fqdn for this machine, error [%d]: %s, " + "resolver returned: [%d]: %s\n", ret, strerror(ret), + resolv_status, resolv_strerror(resolv_status)); + tevent_req_error(req, ret); + return; + } + + /* EOK */ + + DEBUG(SSSDBG_TRACE_INTERNAL, "Found fqdn: %s\n", rhostent->name); + + if (state->hostnames == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "state->hostnames is NULL\n"); + ret = EINVAL; + goto done; + } + + state->hostnames[1] = talloc_strdup(state->hostnames, rhostent->name); + if (state->hostnames[1] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static int sdap_sudo_get_hostnames_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char ***hostnames) +{ + struct sdap_sudo_get_hostnames_state *state = NULL; + + state = tevent_req_data(req, struct sdap_sudo_get_hostnames_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *hostnames = talloc_steal(mem_ctx, state->hostnames); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_users.c b/src/providers/ldap/sdap_async_users.c new file mode 100644 index 0000000..728295d --- /dev/null +++ b/src/providers/ldap/sdap_async_users.c @@ -0,0 +1,1209 @@ +/* + SSSD + + Async LDAP Helper routines - retrieving users + + Copyright (C) Simo Sorce - 2009 + Copyright (C) 2010, Ralf Haferkamp , Novell Inc. + Copyright (C) Jan Zeleny - 2011 + + 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 3 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; 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, see . +*/ + +#include + +#include "util/util.h" +#include "util/probes.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_users.h" + +#define REALM_SEPARATOR '@' + +static void make_realm_upper_case(const char *upn) +{ + char *c; + + c = strchr(upn, REALM_SEPARATOR); + if (c == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No realm delimiter found in upn [%s].\n", upn); + return; + } + + while(*(++c) != '\0') { + c[0] = toupper(*c); + } + + return; +} + +/* ==Save-User-Entry====================================================== */ + +static errno_t +sdap_get_idmap_primary_gid(struct sdap_options *opts, + struct sysdb_attrs *attrs, + char *sid_str, + char *dom_sid_str, + gid_t *_gid) +{ + errno_t ret; + TALLOC_CTX *tmpctx = NULL; + gid_t gid, primary_gid; + char *group_sid_str; + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_uint32_t(attrs, + opts->user_map[SDAP_AT_USER_PRIMARY_GROUP].sys_name, + &primary_gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "no primary group ID provided\n"); + ret = EINVAL; + goto done; + } + + /* The primary group ID is just the RID part of the objectSID + * of the group. Generate the GID by adding this to the domain + * SID value. + */ + + /* First, get the domain SID if we didn't do so above */ + if (!dom_sid_str) { + ret = sdap_idmap_get_dom_sid_from_object(tmpctx, sid_str, + &dom_sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not parse domain SID from [%s]\n", sid_str); + goto done; + } + } + + /* Add the RID to the end */ + group_sid_str = talloc_asprintf(tmpctx, "%s-%lu", dom_sid_str, + (unsigned long) primary_gid); + if (!group_sid_str) { + ret = ENOMEM; + goto done; + } + + /* Convert the SID into a UNIX group ID */ + ret = sdap_idmap_sid_to_unix(opts->idmap_ctx, group_sid_str, &gid); + if (ret != EOK) goto done; + + ret = EOK; + *_gid = gid; +done: + talloc_free(tmpctx); + return ret; +} + +static errno_t sdap_set_non_posix_flag(struct sysdb_attrs *attrs, + const char *pkey) +{ + errno_t ret; + + ret = sysdb_attrs_add_uint32(attrs, pkey, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add a zero ID to a non-POSIX object!\n"); + return ret; + } + + ret = sysdb_attrs_add_bool(attrs, SYSDB_POSIX, false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Error: Failed to mark objects as non-POSIX!\n"); + return ret; + } + + return EOK; +} + +static int sdap_user_set_mpg(struct sysdb_attrs *user_attrs, + gid_t *_gid) +{ + errno_t ret; + + if (_gid == NULL) { + return EINVAL; + } + + if (*_gid == 0) { + /* The original entry had no GID number. This is OK, we just won't add + * the SYSDB_PRIMARY_GROUP_GIDNUM attribute + */ + return EOK; + } + + ret = sysdb_attrs_add_uint32(user_attrs, + SYSDB_PRIMARY_GROUP_GIDNUM, + (uint32_t) *_gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_uint32 failed.\n"); + return ret; + } + + /* We won't really store gidNumber=0, but the zero value tells + * the sysdb layer that no GID is set, which sysdb requires for + * MPG-enabled domains + */ + *_gid = 0; + return EOK; +} + +/* FIXME: support storing additional attributes */ +int sdap_save_user(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + struct sysdb_attrs *mapped_attrs, + char **_usn_value, + time_t now, + bool set_non_posix) +{ + struct ldb_message_element *el; + int ret; + const char *user_name = NULL; + const char *fullname = NULL; + const char *pwd; + const char *gecos; + const char *homedir; + const char *shell; + const char *orig_dn = NULL; + uid_t uid = 0; + gid_t gid = 0; + struct sysdb_attrs *user_attrs; + char *upn = NULL; + size_t i; + int cache_timeout; + char *usn_value = NULL; + char **missing = NULL; + TALLOC_CTX *tmpctx = NULL; + bool use_id_mapping; + char *sid_str; + char *dom_sid_str = NULL; + struct sss_domain_info *subdomain; + size_t c; + char *p1; + char *p2; + bool is_posix = true; + + DEBUG(SSSDBG_TRACE_FUNC, "Save user\n"); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + ret = ENOMEM; + goto done; + } + + user_attrs = sysdb_new_attrs(tmpctx); + if (user_attrs == NULL) { + ret = ENOMEM; + goto done; + } + + /* Always store SID string if available */ + ret = sdap_attrs_get_sid_str(tmpctx, opts->idmap_ctx, attrs, + opts->user_map[SDAP_AT_USER_OBJECTSID].sys_name, + &sid_str); + if (ret == EOK) { + ret = sysdb_attrs_add_string(user_attrs, SYSDB_SID_STR, sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not add SID string: [%s]\n", + sss_strerror(ret)); + goto done; + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "objectSID: not available for user\n"); + sid_str = NULL; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not identify objectSID: [%s]\n", + sss_strerror(ret)); + sid_str = NULL; + } + + /* Always store UUID if available */ + ret = sysdb_handle_original_uuid(opts->user_map[SDAP_AT_USER_UUID].def_name, + attrs, + opts->user_map[SDAP_AT_USER_UUID].sys_name, + user_attrs, SYSDB_UUID); + if (ret != EOK) { + DEBUG((ret == ENOENT) ? SSSDBG_TRACE_ALL : SSSDBG_MINOR_FAILURE, + "Failed to retrieve UUID [%d][%s].\n", ret, sss_strerror(ret)); + } + + /* If this object has a SID available, we will determine the correct + * domain by its SID. */ + if (sid_str != NULL) { + subdomain = find_domain_by_sid(get_domains_head(dom), sid_str); + if (subdomain) { + dom = subdomain; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "SID %s does not belong to any known " + "domain\n", sid_str); + } + } + + ret = sdap_get_user_primary_name(memctx, opts, attrs, dom, &user_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get user name\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Processing user %s\n", user_name); + + if (opts->schema_type == SDAP_SCHEMA_AD) { + ret = sysdb_attrs_get_string(attrs, + opts->user_map[SDAP_AT_USER_FULLNAME].sys_name, &fullname); + if (ret == EOK) { + ret = sysdb_attrs_add_string(user_attrs, SYSDB_FULLNAME, fullname); + if (ret != EOK) { + goto done; + } + } else if (ret != ENOENT) { + goto done; + } + } + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_PWD].sys_name, &el); + if (ret) goto done; + if (el->num_values == 0) pwd = NULL; + else pwd = (const char *)el->values[0].data; + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_GECOS].sys_name, &el); + if (ret) goto done; + if (el->num_values == 0) gecos = NULL; + else gecos = (const char *)el->values[0].data; + + if (!gecos) { + /* Fall back to the user's full name */ + ret = sysdb_attrs_get_el( + attrs, + opts->user_map[SDAP_AT_USER_FULLNAME].sys_name, &el); + if (ret) goto done; + if (el->num_values > 0) gecos = (const char *)el->values[0].data; + } + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_HOME].sys_name, &el); + if (ret) goto done; + if (el->num_values == 0) homedir = NULL; + else homedir = (const char *)el->values[0].data; + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_SHELL].sys_name, &el); + if (ret) goto done; + if (el->num_values == 0) shell = NULL; + else shell = (const char *)el->values[0].data; + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(opts->idmap_ctx, + dom->name, + sid_str); + + /* Retrieve or map the UID as appropriate */ + if (use_id_mapping) { + + if (sid_str == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "SID not available, cannot map a " \ + "unix ID to user [%s].\n", user_name); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Mapping user [%s] objectSID [%s] to unix ID\n", user_name, sid_str); + + /* Convert the SID into a UNIX user ID */ + ret = sdap_idmap_sid_to_unix(opts->idmap_ctx, sid_str, &uid); + if (ret == ENOTSUP) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping built-in object.\n"); + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + + /* Store the UID in the ldap_attrs so it doesn't get + * treated as a missing attribute from LDAP and removed. + */ + ret = sdap_replace_id(attrs, SYSDB_UIDNUM, uid); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set the id-mapped UID\n"); + goto done; + } + } else { + ret = sysdb_attrs_get_uint32_t(attrs, + opts->user_map[SDAP_AT_USER_UID].sys_name, + &uid); + if (ret == ENOENT && (dom->type == DOM_TYPE_APPLICATION || set_non_posix)) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Marking object as non-POSIX and setting ID=0!\n"); + ret = sdap_set_non_posix_flag(user_attrs, + opts->user_map[SDAP_AT_USER_UID].sys_name); + if (ret != EOK) { + goto done; + } + is_posix = false; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot retrieve UID for [%s] in domain [%s].\n", + user_name, dom->name); + ret = ERR_NO_POSIX; + goto done; + } + } + + /* check that the uid is valid for this domain if the user is a POSIX one */ + if (is_posix == true && OUT_OF_ID_RANGE(uid, dom->id_min, dom->id_max)) { + DEBUG(SSSDBG_OP_FAILURE, + "User [%s] filtered out! (uid out of range)\n", + user_name); + ret = EINVAL; + goto done; + } + + if (use_id_mapping) { + ret = sdap_get_idmap_primary_gid(opts, attrs, sid_str, dom_sid_str, + &gid); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot get the GID for [%s] in domain [%s].\n", + user_name, dom->name); + goto done; + } + + if (sss_domain_is_mpg(dom) == true) { + /* For subdomain users, only create the private group as + * the subdomain is an MPG domain. + * But we have to save the GID of the original primary group + * because otherwise this information might be lost because + * typically (UNIX and AD) the user is not listed in his primary + * group as a member. + */ + ret = sdap_user_set_mpg(user_attrs, &gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_user_set_mpg failed [%d]: %s\n", ret, + sss_strerror(ret)); + goto done; + } + } + + /* Store the GID in the ldap_attrs so it doesn't get + * treated as a missing attribute from LDAP and removed. + */ + ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, gid); + if (ret != EOK) goto done; + } else if (sss_domain_is_mpg(dom)) { + /* Likewise, if a domain is set to contain 'magic private groups', do + * not process the real GID, but save it in the cache as originalGID + * (if available) + */ + ret = sysdb_attrs_get_uint32_t(attrs, + opts->user_map[SDAP_AT_USER_GID].sys_name, + &gid); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_LIBS, + "Missing GID, won't save the %s attribute\n", + SYSDB_PRIMARY_GROUP_GIDNUM); + + /* Store the UID as GID (since we're in a MPG domain so that it doesn't + * get treated as a missing attribute and removed + */ + ret = sdap_replace_id(attrs, SYSDB_GIDNUM, uid); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set the id-mapped UID\n"); + goto done; + } + gid = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot retrieve GID, won't save the %s attribute\n", + SYSDB_PRIMARY_GROUP_GIDNUM); + gid = 0; + } + + ret = sdap_user_set_mpg(user_attrs, &gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_user_set_mpg failed [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } else { + ret = sysdb_attrs_get_uint32_t(attrs, + opts->user_map[SDAP_AT_USER_GID].sys_name, + &gid); + if (ret == ENOENT && (dom->type == DOM_TYPE_APPLICATION || set_non_posix)) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Marking object as non-POSIX and setting ID=0!\n"); + ret = sdap_set_non_posix_flag(attrs, + opts->user_map[SDAP_AT_USER_GID].sys_name); + if (ret != EOK) { + goto done; + } + is_posix = false; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot retrieve GID for [%s] in domain [%s].\n", + user_name, dom->name); + ret = ERR_NO_POSIX; + goto done; + } + } + + /* check that the gid is valid for this domain */ + if (is_posix == true && IS_SUBDOMAIN(dom) == false + && sss_domain_is_mpg(dom) == false + && OUT_OF_ID_RANGE(gid, dom->id_min, dom->id_max)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User [%s] filtered out! (primary gid out of range)\n", + user_name); + ret = EINVAL; + goto done; + } + + ret = sysdb_attrs_get_el(attrs, SYSDB_ORIG_DN, &el); + if (ret) { + goto done; + } + if (!el || el->num_values == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "originalDN is not available for [%s].\n", user_name); + } else { + orig_dn = (const char *) el->values[0].data; + DEBUG(SSSDBG_TRACE_INTERNAL, "Adding originalDN [%s] to attributes " + "of [%s].\n", orig_dn, user_name); + + ret = sysdb_attrs_add_string(user_attrs, SYSDB_ORIG_DN, orig_dn); + if (ret) { + goto done; + } + } + + ret = sysdb_attrs_get_el(attrs, SYSDB_MEMBEROF, &el); + if (ret) { + goto done; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Original memberOf is not available for [%s].\n", user_name); + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Adding original memberOf attributes to [%s].\n", user_name); + for (i = 0; i < el->num_values; i++) { + ret = sysdb_attrs_add_string(user_attrs, SYSDB_ORIG_MEMBEROF, + (const char *) el->values[i].data); + if (ret) { + goto done; + } + } + } + + ret = sdap_attrs_add_string(attrs, + opts->user_map[SDAP_AT_USER_MODSTAMP].sys_name, + "original mod-Timestamp", + user_name, user_attrs); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_USN].sys_name, &el); + if (ret) { + goto done; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Original USN value is not available for [%s].\n", user_name); + } else { + ret = sysdb_attrs_add_string(user_attrs, + opts->user_map[SDAP_AT_USER_USN].sys_name, + (const char*)el->values[0].data); + if (ret) { + goto done; + } + usn_value = talloc_strdup(tmpctx, (const char*)el->values[0].data); + if (!usn_value) { + ret = ENOMEM; + goto done; + } + } + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_PRINC].sys_name, &el); + if (ret) { + goto done; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "User principal is not available for [%s].\n", user_name); + } else { + for (c = 0; c < el->num_values; c++) { + upn = talloc_strdup(tmpctx, (const char*) el->values[c].data); + if (!upn) { + ret = ENOMEM; + goto done; + } + + /* Check for IPA Kerberos enterprise principal strings + * 'user\@my.realm@IPA.REALM' and use 'user@my.realm' */ + if ( (p1 = strchr(upn,'\\')) != NULL + && *(p1 + 1) == '@' + && (p2 = strchr(p1 + 2, '@')) != NULL) { + *p1 = '\0'; + *p2 = '\0'; + upn = talloc_asprintf(tmpctx, "%s%s", upn, p1 + 1); + if (upn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (dp_opt_get_bool(opts->basic, SDAP_FORCE_UPPER_CASE_REALM)) { + make_realm_upper_case(upn); + } + DEBUG(SSSDBG_TRACE_FUNC, + "Adding user principal [%s] to attributes of [%s].\n", + upn, user_name); + ret = sysdb_attrs_add_string(user_attrs, SYSDB_UPN, upn); + if (ret) { + goto done; + } + } + } + + for (i = SDAP_FIRST_EXTRA_USER_AT; i < opts->user_map_cnt; i++) { + ret = sdap_attrs_add_list(attrs, opts->user_map[i].sys_name, + NULL, user_name, user_attrs); + if (ret) { + goto done; + } + } + + cache_timeout = dom->user_timeout; + + ret = sdap_save_all_names(user_name, attrs, dom, + SYSDB_MEMBER_USER, user_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to save user names\n"); + goto done; + } + + /* Make sure that any attributes we requested from LDAP that we + * did not receive are also removed from the sysdb + */ + ret = list_missing_attrs(user_attrs, opts->user_map, opts->user_map_cnt, + attrs, &missing); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Storing info for user %s\n", user_name); + + ret = sysdb_store_user(dom, user_name, pwd, uid, gid, + gecos, homedir, shell, orig_dn, + user_attrs, missing, cache_timeout, now); + if (ret) goto done; + + if (mapped_attrs != NULL) { + ret = sysdb_set_user_attr(dom, user_name, mapped_attrs, SYSDB_MOD_ADD); + if (ret) return ret; + } + + if (_usn_value) { + *_usn_value = talloc_steal(memctx, usn_value); + } + + talloc_steal(memctx, user_attrs); + ret = EOK; + +done: + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to save user [%s]\n", + user_name ? user_name : "Unknown"); + } + talloc_free(tmpctx); + return ret; +} + + +/* ==Generic-Function-to-save-multiple-users============================= */ + +int sdap_save_users(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **users, + int num_users, + struct sysdb_attrs *mapped_attrs, + char **_usn_value) +{ + TALLOC_CTX *tmpctx; + char *higher_usn = NULL; + char *usn_value; + int ret; + errno_t sret; + int i; + time_t now; + bool in_transaction = false; + + if (num_users == 0) { + /* Nothing to do if there are no users */ + return EOK; + } + + tmpctx = talloc_new(memctx); + if (!tmpctx) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + if (mapped_attrs != NULL) { + ret = sysdb_remove_mapped_data(dom, mapped_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_remove_mapped_data failed, " + "some cached entries might contain invalid mapping data.\n"); + } + } + + now = time(NULL); + for (i = 0; i < num_users; i++) { + usn_value = NULL; + + ret = sdap_save_user(tmpctx, opts, dom, users[i], mapped_attrs, + &usn_value, now, false); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store user %d. Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_ALL, "User %d processed!\n", i); + } + + if (usn_value) { + if (higher_usn) { + if ((strlen(usn_value) > strlen(higher_usn)) || + (strcmp(usn_value, higher_usn) > 0)) { + talloc_zfree(higher_usn); + higher_usn = usn_value; + } else { + talloc_zfree(usn_value); + } + } else { + higher_usn = usn_value; + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + if (_usn_value) { + *_usn_value = talloc_steal(memctx, higher_usn); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_zfree(tmpctx); + return ret; +} + + +/* ==Search-Users-with-filter============================================= */ + +struct sdap_search_user_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + + const char **attrs; + const char *base_filter; + const char *filter; + int timeout; + enum sdap_entry_lookup_type lookup_type; + + char *higher_usn; + struct sysdb_attrs **users; + size_t count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t sdap_search_user_next_base(struct tevent_req *req); +static void sdap_search_user_copy_batch(struct sdap_search_user_state *state, + struct sysdb_attrs **users, + size_t count); +static void sdap_search_user_process(struct tevent_req *subreq); + +struct tevent_req *sdap_search_user_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_search_user_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_search_user_state); + if (req == NULL) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->attrs = attrs; + state->higher_usn = NULL; + state->users = NULL; + state->count = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + state->lookup_type = lookup_type; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + ret = sdap_search_user_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + + return req; +} + +static errno_t sdap_search_user_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_search_user_state *state; + bool need_paging = false; + int sizelimit = 0; + + state = tevent_req_data(req, struct sdap_search_user_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (state->filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for users with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + switch (state->lookup_type) { + case SDAP_LOOKUP_SINGLE: + break; + /* Only requests that can return multiple entries should require + * the paging control + */ + case SDAP_LOOKUP_WILDCARD: + sizelimit = dp_opt_get_int(state->opts->basic, SDAP_WILDCARD_LIMIT); + need_paging = true; + break; + case SDAP_LOOKUP_ENUMERATE: + need_paging = true; + break; + } + + subreq = sdap_get_and_parse_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->user_map, state->opts->user_map_cnt, + 0, NULL, NULL, sizelimit, state->timeout, + need_paging); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_search_user_process, req); + + return EOK; +} + +static void sdap_search_user_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_search_user_state *state = tevent_req_data(req, + struct sdap_search_user_state); + int ret; + size_t count; + struct sysdb_attrs **users; + bool next_base = false; + + ret = sdap_get_and_parse_generic_recv(subreq, state, + &count, &users); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for users, returned %zu results.\n", count); + + if (state->lookup_type == SDAP_LOOKUP_WILDCARD || \ + state->lookup_type == SDAP_LOOKUP_ENUMERATE || \ + count == 0) { + /* No users found in this search or looking up multiple entries */ + next_base = true; + } + + /* Add this batch of users to the list */ + if (count > 0) { + state->users = + talloc_realloc(state, + state->users, + struct sysdb_attrs *, + state->count + count + 1); + if (!state->users) { + tevent_req_error(req, ENOMEM); + return; + } + + sdap_search_user_copy_batch(state, users, count); + } + + if (next_base) { + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_search_user_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Retrieved total %zu users\n", state->count); + + /* No more search bases + * Return ENOENT if no users were found + */ + if (state->count == 0) { + tevent_req_error(req, ENOENT); + return; + } + + tevent_req_done(req); +} + +static void sdap_search_user_copy_batch(struct sdap_search_user_state *state, + struct sysdb_attrs **users, + size_t count) +{ + size_t copied; + bool filter; + + /* Always copy all objects for wildcard lookups. */ + filter = state->lookup_type == SDAP_LOOKUP_SINGLE ? true : false; + + copied = sdap_steal_objects_in_dom(state->opts, + state->users, + state->count, + state->dom, + users, count, filter); + + state->count += copied; + state->users[state->count] = NULL; +} + +int sdap_search_user_recv(TALLOC_CTX *memctx, struct tevent_req *req, + char **higher_usn, struct sysdb_attrs ***users, + size_t *count) +{ + struct sdap_search_user_state *state = tevent_req_data(req, + struct sdap_search_user_state); + + if (higher_usn) { + *higher_usn = talloc_steal(memctx, state->higher_usn); + } + + if (users) { + *users = talloc_steal(memctx, state->users); + } + + if (count) { + *count = state->count; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Search-And-Save-Users-with-filter============================================= */ +struct sdap_get_users_state { + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + const char *filter; + + char *higher_usn; + struct sysdb_attrs **users; + struct sysdb_attrs *mapped_attrs; + size_t count; +}; + +static void sdap_get_users_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type, + struct sysdb_attrs *mapped_attrs) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_get_users_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_get_users_state); + if (!req) return NULL; + + state->sysdb = sysdb; + state->opts = opts; + state->dom = dom; + + state->filter = filter; + PROBE(SDAP_SEARCH_USER_SEND, state->filter); + + if (mapped_attrs == NULL) { + state->mapped_attrs = NULL; + } else { + state->mapped_attrs = sysdb_new_attrs(state); + if (state->mapped_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_copy(mapped_attrs, state->mapped_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_copy failed.\n"); + goto done; + } + } + + subreq = sdap_search_user_send(state, ev, dom, opts, search_bases, + sh, attrs, filter, timeout, lookup_type); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, sdap_get_users_done, req); + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_get_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_users_state *state = tevent_req_data(req, + struct sdap_get_users_state); + int ret; + + ret = sdap_search_user_recv(state, subreq, &state->higher_usn, + &state->users, &state->count); + if (ret) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to retrieve users [%d][%s].\n", + ret, sss_strerror(ret)); + } + tevent_req_error(req, ret); + return; + } + + PROBE(SDAP_SEARCH_USER_SAVE_BEGIN, state->filter); + + ret = sdap_save_users(state, state->sysdb, + state->dom, state->opts, + state->users, state->count, + state->mapped_attrs, + &state->higher_usn); + PROBE(SDAP_SEARCH_USER_SAVE_END, state->filter); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store users [%d][%s].\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_ALL, "Saving %zu Users - Done\n", state->count); + + tevent_req_done(req); +} + +int sdap_get_users_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **usn_value) +{ + struct sdap_get_users_state *state = tevent_req_data(req, + struct sdap_get_users_state); + + PROBE(SDAP_SEARCH_USER_RECV, state->filter); + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (usn_value) { + *usn_value = talloc_steal(mem_ctx, state->higher_usn); + } + + return EOK; +} + +/* ==Fetch-Fallback-local-user============================================ */ + +errno_t sdap_fallback_local_user(TALLOC_CTX *memctx, + const char *name, uid_t uid, + struct sysdb_attrs ***reply) +{ + struct sysdb_attrs **ua; + struct sysdb_attrs *user; + struct passwd *pwd; + int ret; + + if (name) { + pwd = getpwnam(name); + } else { + pwd = getpwuid(uid); + } + + if (!pwd) { + return errno ? errno : ENOENT; + } + + ua = talloc_array(memctx, struct sysdb_attrs *, 2); + if (!ua) { + ret = ENOMEM; + goto done; + } + ua[1] = NULL; + + user = sysdb_new_attrs(ua); + if (!user) { + ret = ENOMEM; + goto done; + } + ua[0] = user; + + ret = sysdb_attrs_add_string(user, SYSDB_NAME, pwd->pw_name); + if (ret != EOK) { + goto done; + } + + if (pwd->pw_passwd) { + ret = sysdb_attrs_add_string(user, SYSDB_PWD, pwd->pw_passwd); + if (ret != EOK) { + goto done; + } + } + + ret = sysdb_attrs_add_long(user, SYSDB_UIDNUM, (long)pwd->pw_uid); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_add_long(user, SYSDB_GIDNUM, (long)pwd->pw_gid); + if (ret != EOK) { + goto done; + } + + if (pwd->pw_gecos && *pwd->pw_gecos) { + ret = sysdb_attrs_add_string(user, SYSDB_GECOS, pwd->pw_gecos); + if (ret != EOK) { + goto done; + } + } + + if (pwd->pw_dir && *pwd->pw_dir) { + ret = sysdb_attrs_add_string(user, SYSDB_HOMEDIR, pwd->pw_dir); + if (ret != EOK) { + goto done; + } + } + + if (pwd->pw_shell && *pwd->pw_shell) { + ret = sysdb_attrs_add_string(user, SYSDB_SHELL, pwd->pw_shell); + if (ret != EOK) { + goto done; + } + } + +done: + if (ret != EOK) { + talloc_free(ua); + } else { + *reply = ua; + } + + return ret; +} diff --git a/src/providers/ldap/sdap_autofs.c b/src/providers/ldap/sdap_autofs.c new file mode 100644 index 0000000..b951790 --- /dev/null +++ b/src/providers/ldap/sdap_autofs.c @@ -0,0 +1,491 @@ +/* + SSSD + + LDAP handler for autofs + + Authors: + Jakub Hrozek + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_autofs.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async.h" +#include "providers/backend.h" +#include "providers/data_provider.h" +#include "db/sysdb_autofs.h" +#include "util/util.h" + +static void +sdap_autofs_invalidate_maps(struct sdap_id_ctx *id_ctx, + const char *mapname) +{ + const char *master_map; + errno_t ret; + + master_map = dp_opt_get_string(id_ctx->opts->basic, + SDAP_AUTOFS_MAP_MASTER_NAME); + if (strcmp(master_map, mapname) == 0) { + DEBUG(SSSDBG_FUNC_DATA, "Refresh of automount master map triggered: " + "%s\n", mapname); + + ret = sysdb_invalidate_autofs_maps(id_ctx->be->domain); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not invalidate autofs maps, " + "backend might return stale entries\n"); + } + } +} + +struct sdap_autofs_enumerate_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_id_op *op; + const char *map_name; + + int dp_error; +}; + +static errno_t +sdap_autofs_enumerate_retry(struct tevent_req *req); +static void +sdap_autofs_enumerate_connect_done(struct tevent_req *subreq); +static void +sdap_autofs_enumerate_done(struct tevent_req *req); + +static struct tevent_req * +sdap_autofs_enumerate_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + const char *map_name) +{ + struct tevent_req *req; + struct sdap_autofs_enumerate_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_autofs_enumerate_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->dp_error = DP_ERR_FATAL; + state->map_name = map_name; + + state->op = sdap_id_op_create(state, state->ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + ret = sdap_autofs_enumerate_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +sdap_autofs_enumerate_retry(struct tevent_req *req) +{ + struct sdap_autofs_enumerate_state *state = + tevent_req_data(req, struct sdap_autofs_enumerate_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, sdap_autofs_enumerate_connect_done, req); + return EOK; +} + +static void +sdap_autofs_enumerate_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_autofs_enumerate_state *state = + tevent_req_data(req, struct sdap_autofs_enumerate_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_autofs_setautomntent_send(state, state->ev, + state->ctx->be->domain, + state->ctx->be->domain->sysdb, + sdap_id_op_handle(state->op), + state->op, + state->ctx->opts, + state->map_name); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_autofs_setautomntent_send failed\n"); + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_autofs_enumerate_done, req); + +} + +static void +sdap_autofs_enumerate_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_autofs_enumerate_state *state = + tevent_req_data(req, struct sdap_autofs_enumerate_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_autofs_setautomntent_recv(subreq); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_autofs_enumerate_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT) { + ret = sysdb_delete_autofsmap(state->ctx->be->domain, state->map_name); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete autofs map %s [%d]: %s\n", + state->map_name, ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +static errno_t +sdap_autofs_enumerate_recv(struct tevent_req *req, int *dp_error_out) +{ + struct sdap_autofs_enumerate_state *state = + tevent_req_data(req, struct sdap_autofs_enumerate_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_autofs_enumerate_handler_state { + int dummy; +}; + +static void sdap_autofs_enumerate_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_autofs_enumerate_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params) +{ + struct sdap_autofs_enumerate_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_autofs_enumerate_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + DEBUG(SSSDBG_FUNC_DATA, "Requested refresh for: %s\n", data->mapname); + + sdap_autofs_invalidate_maps(id_ctx, data->mapname); + + subreq = sdap_autofs_enumerate_send(mem_ctx, params->ev, + id_ctx, data->mapname); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request for %s.\n", + data->mapname); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_autofs_enumerate_handler_done, req); + + ret = EAGAIN; + +immediately: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, params->ev); + } + + return req; +} + +static void sdap_autofs_enumerate_handler_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_autofs_enumerate_recv(subreq, &dp_error); + talloc_zfree(subreq); + ret = dp_error_to_ret(ret, dp_error); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +sdap_autofs_enumerate_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_autofs_get_map_handler_state { + int dummy; +}; + +static void sdap_autofs_get_map_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_autofs_get_map_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params) +{ + struct sdap_autofs_get_map_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_autofs_get_map_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + DEBUG(SSSDBG_FUNC_DATA, "Requested refresh for: %s\n", data->mapname); + + sdap_autofs_invalidate_maps(id_ctx, data->mapname); + + subreq = sdap_autofs_get_map_send(mem_ctx, id_ctx, data->mapname); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request for %s.\n", + data->mapname); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_map_handler_done, req); + + ret = EAGAIN; + +immediately: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, params->ev); + } + + return req; +} + +static void sdap_autofs_get_map_handler_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_autofs_get_map_recv(subreq, &dp_error); + talloc_zfree(subreq); + ret = dp_error_to_ret(ret, dp_error); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +sdap_autofs_get_map_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_autofs_get_entry_handler_state { + int dummy; +}; + +static void sdap_autofs_get_entry_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_autofs_get_entry_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params) +{ + struct sdap_autofs_get_entry_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_autofs_get_entry_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + DEBUG(SSSDBG_FUNC_DATA, "Requested refresh for: %s:%s\n", + data->mapname, data->entryname); + + subreq = sdap_autofs_get_entry_send(mem_ctx, id_ctx, + data->mapname, data->entryname); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request for %s:%s.\n", + data->mapname, data->entryname); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_entry_handler_done, req); + + ret = EAGAIN; + +immediately: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, params->ev); + } + + return req; +} + +static void sdap_autofs_get_entry_handler_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_autofs_get_entry_recv(subreq, &dp_error); + talloc_zfree(subreq); + ret = dp_error_to_ret(ret, dp_error); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +sdap_autofs_get_entry_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t sdap_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing autofs LDAP back end\n"); + + ret = ldap_get_autofs_options(id_ctx, sysdb_ctx_get_ldb(be_ctx->domain->sysdb), + be_ctx->cdb, be_ctx->conf_path, id_ctx->opts); + if (ret != EOK) { + return ret; + } + + dp_set_method(dp_methods, DPM_AUTOFS_ENUMERATE, + sdap_autofs_enumerate_handler_send, sdap_autofs_enumerate_handler_recv, id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + dp_set_method(dp_methods, DPM_AUTOFS_GET_MAP, + sdap_autofs_get_map_handler_send, sdap_autofs_get_map_handler_recv, id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + dp_set_method(dp_methods, DPM_AUTOFS_GET_ENTRY, + sdap_autofs_get_entry_handler_send, sdap_autofs_get_entry_handler_recv, id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + return EOK; +} diff --git a/src/providers/ldap/sdap_autofs.h b/src/providers/ldap/sdap_autofs.h new file mode 100644 index 0000000..2103937 --- /dev/null +++ b/src/providers/ldap/sdap_autofs.h @@ -0,0 +1,62 @@ +/* + SSSD + + LDAP handler for autofs + + Authors: + Jakub Hrozek + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_AUTOFS_H_ +#define _SDAP_AUTOFS_H_ + +errno_t sdap_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_method *dp_methods); + +struct tevent_req * +sdap_autofs_setautomntent_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_handle *sh, + struct sdap_id_op *op, + struct sdap_options *opts, + const char *mapname); + +errno_t +sdap_autofs_setautomntent_recv(struct tevent_req *req); + +struct tevent_req *sdap_autofs_get_map_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + const char *mapname); + +errno_t sdap_autofs_get_map_recv(struct tevent_req *req, + int *dp_error); + +struct tevent_req *sdap_autofs_get_entry_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + const char *mapname, + const char *entryname); + +errno_t sdap_autofs_get_entry_recv(struct tevent_req *req, + int *dp_error); + +#endif /* _SDAP_AUTOFS_H_ */ + diff --git a/src/providers/ldap/sdap_certmap.c b/src/providers/ldap/sdap_certmap.c new file mode 100644 index 0000000..e32895c --- /dev/null +++ b/src/providers/ldap/sdap_certmap.c @@ -0,0 +1,150 @@ + +/* + SSSD + + Authors: + Sumit Bose + + Copyright (C) 2017 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "lib/certmap/sss_certmap.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_certmap_ctx { + struct sss_certmap_ctx *certmap_ctx; +}; + +struct priv_sss_debug { + int level; +}; + +static void ext_debug(void *private, const char *file, long line, + const char *function, const char *format, ...) +{ + va_list ap; + struct priv_sss_debug *data = private; + int level = SSSDBG_OP_FAILURE; + + if (data != NULL) { + level = data->level; + } + + va_start(ap, format); + sss_vdebug_fn(file, line, function, level, APPEND_LINE_FEED, + format, ap); + va_end(ap); +} + +struct sss_certmap_ctx *sdap_get_sss_certmap(struct sdap_certmap_ctx *ctx) +{ + return ctx == NULL ? NULL : ctx->certmap_ctx; +} + +errno_t sdap_setup_certmap(struct sdap_certmap_ctx *sdap_certmap_ctx, + struct certmap_info **certmap_list) +{ + int ret; + struct sss_certmap_ctx *sss_certmap_ctx = NULL; + size_t c; + + if (sdap_certmap_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sdap_certmap_ctx.\n"); + return EINVAL; + } + + if (certmap_list == NULL || *certmap_list == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No certmap data, nothing to do.\n"); + ret = EOK; + goto done; + } + + ret = sss_certmap_init(sdap_certmap_ctx, ext_debug, NULL, &sss_certmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + goto done; + } + + for (c = 0; certmap_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Trying to add rule [%s][%d][%s][%s].\n", + certmap_list[c]->name, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule); + + ret = sss_certmap_add_rule(sss_certmap_ctx, certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule, + certmap_list[c]->domains); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_certmap_add_rule failed for rule [%s] " + "with error [%d][%s], skipping. " + "Please check for typos and if rule syntax is supported.\n", + certmap_list[c]->name, ret, sss_strerror(ret)); + continue; + } + } + + ret = EOK; + +done: + if (ret == EOK) { + sss_certmap_free_ctx(sdap_certmap_ctx->certmap_ctx); + sdap_certmap_ctx->certmap_ctx = sss_certmap_ctx; + } else { + sss_certmap_free_ctx(sss_certmap_ctx); + } + + return ret; +} + +errno_t sdap_init_certmap(TALLOC_CTX *mem_ctx, struct sdap_id_ctx *id_ctx) +{ + int ret; + bool hint; + struct certmap_info **certmap_list = NULL; + + if (id_ctx->opts->sdap_certmap_ctx == NULL) { + id_ctx->opts->sdap_certmap_ctx = talloc_zero(mem_ctx, + struct sdap_certmap_ctx); + if (id_ctx->opts->sdap_certmap_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + } + + ret = sysdb_get_certmap(mem_ctx, id_ctx->be->domain->sysdb, + &certmap_list, &hint); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_certmap failed.\n"); + goto done; + } + + ret = sdap_setup_certmap(id_ctx->opts->sdap_certmap_ctx, certmap_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_setup_certmap failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(certmap_list); + + return ret; +} diff --git a/src/providers/ldap/sdap_child_helpers.c b/src/providers/ldap/sdap_child_helpers.c new file mode 100644 index 0000000..3d42f4e --- /dev/null +++ b/src/providers/ldap/sdap_child_helpers.c @@ -0,0 +1,515 @@ +/* + SSSD + + LDAP Backend Module -- child helpers + + Authors: + Jakub Hrozek + + Copyright (C) 2009 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include +#include + +#include "util/util.h" +#include "util/sss_krb5.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_private.h" +#include "util/child_common.h" + +#ifndef SSSD_LIBEXEC_PATH +#error "SSSD_LIBEXEC_PATH not defined" +#else +#define LDAP_CHILD SSSD_LIBEXEC_PATH"/ldap_child" +#endif + +#ifndef LDAP_CHILD_USER +#define LDAP_CHILD_USER "nobody" +#endif + +struct sdap_child { + /* child info */ + pid_t pid; + struct child_io_fds *io; +}; + +static void sdap_close_fd(int *fd) +{ + int ret; + + if (*fd == -1) { + DEBUG(SSSDBG_TRACE_FUNC, "fd already closed\n"); + return; + } + + ret = close(*fd); + if (ret) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, "Closing fd %d, return error %d (%s)\n", + *fd, ret, strerror(ret)); + } + + *fd = -1; +} + +static void child_callback(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + if (WEXITSTATUS(child_status) == CHILD_TIMEOUT_EXIT_CODE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "LDAP child was terminated due to timeout\n"); + + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + tevent_req_error(req, ETIMEDOUT); + } +} + +static errno_t sdap_fork_child(struct tevent_context *ev, + struct sdap_child *child, struct tevent_req *req) +{ + int pipefd_to_child[2] = PIPE_INIT; + int pipefd_from_child[2] = PIPE_INIT; + pid_t pid; + errno_t ret; + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe(from) failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe(to) failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + pid = fork(); + + if (pid == 0) { /* child */ + exec_child(child, + pipefd_to_child, pipefd_from_child, + LDAP_CHILD, LDAP_CHILD_LOG_FILE); + + /* We should never get here */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec LDAP child\n"); + } else if (pid > 0) { /* parent */ + child->pid = pid; + child->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + child->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(child->io->read_from_child_fd); + sss_fd_nonblocking(child->io->write_to_child_fd); + + ret = child_handler_setup(ev, pid, child_callback, req, NULL); + if (ret != EOK) { + goto fail; + } + + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fork failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + return EOK; + +fail: + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + return ret; +} + +static errno_t create_tgt_req_send_buffer(TALLOC_CTX *mem_ctx, + const char *realm_str, + const char *princ_str, + const char *keytab_name, + int32_t lifetime, + struct io_buffer **io_buf) +{ + struct io_buffer *buf; + size_t rp; + + buf = talloc(mem_ctx, struct io_buffer); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + buf->size = 6 * sizeof(uint32_t); + if (realm_str) { + buf->size += strlen(realm_str); + } + if (princ_str) { + buf->size += strlen(princ_str); + } + if (keytab_name) { + buf->size += strlen(keytab_name); + } + + DEBUG(SSSDBG_TRACE_FUNC, "buffer size: %zu\n", buf->size); + + buf->data = talloc_size(buf, buf->size); + if (buf->data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + talloc_free(buf); + return ENOMEM; + } + + rp = 0; + + /* realm */ + if (realm_str) { + SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(realm_str), &rp); + safealign_memcpy(&buf->data[rp], realm_str, strlen(realm_str), &rp); + } else { + SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp); + } + + /* principal */ + if (princ_str) { + SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(princ_str), &rp); + safealign_memcpy(&buf->data[rp], princ_str, strlen(princ_str), &rp); + } else { + SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp); + } + + /* keytab */ + if (keytab_name) { + SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(keytab_name), &rp); + safealign_memcpy(&buf->data[rp], keytab_name, strlen(keytab_name), &rp); + } else { + SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp); + } + + /* lifetime */ + SAFEALIGN_SET_UINT32(&buf->data[rp], lifetime, &rp); + + /* UID and GID to drop privileges to, if needed. The ldap_child process runs as + * setuid if the back end runs unprivileged as it needs to access the keytab + */ + SAFEALIGN_SET_UINT32(&buf->data[rp], geteuid(), &rp); + SAFEALIGN_SET_UINT32(&buf->data[rp], getegid(), &rp); + + *io_buf = buf; + return EOK; +} + +static int parse_child_response(TALLOC_CTX *mem_ctx, + uint8_t *buf, ssize_t size, + int *result, krb5_error_code *kerr, + char **ccache, time_t *expire_time_out) +{ + size_t p = 0; + uint32_t len; + uint32_t res; + char *ccn; + time_t expire_time; + krb5_error_code krberr; + + /* operation result code */ + SAFEALIGN_COPY_UINT32_CHECK(&res, buf + p, size, &p); + + /* krb5 error code */ + safealign_memcpy(&krberr, buf+p, sizeof(krberr), &p); + + /* ccache name size */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + + if (len > size - p) return EINVAL; + + ccn = talloc_size(mem_ctx, sizeof(char) * (len + 1)); + if (ccn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + safealign_memcpy(ccn, buf+p, sizeof(char) * len, &p); + ccn[len] = '\0'; + + if (p + sizeof(time_t) > size) { + talloc_free(ccn); + return EINVAL; + } + safealign_memcpy(&expire_time, buf+p, sizeof(time_t), &p); + + *result = res; + *ccache = ccn; + *expire_time_out = expire_time; + *kerr = krberr; + return EOK; +} + +/* ==The-public-async-interface============================================*/ + +struct sdap_get_tgt_state { + struct tevent_context *ev; + struct sdap_child *child; + ssize_t len; + uint8_t *buf; + + struct tevent_timer *kill_te; +}; + +static errno_t set_tgt_child_timeout(struct tevent_req *req, + struct tevent_context *ev, + int timeout); +static void sdap_get_tgt_step(struct tevent_req *subreq); +static void sdap_get_tgt_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_tgt_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *realm_str, + const char *princ_str, + const char *keytab_name, + int32_t lifetime, + int timeout) +{ + struct tevent_req *req, *subreq; + struct sdap_get_tgt_state *state; + struct io_buffer *buf; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_get_tgt_state); + if (!req) { + return NULL; + } + + state->ev = ev; + + state->child = talloc_zero(state, struct sdap_child); + if (!state->child) { + ret = ENOMEM; + goto fail; + } + + state->child->io = talloc(state, struct child_io_fds); + if (state->child->io == NULL) { + ret = ENOMEM; + goto fail; + } + state->child->io->read_from_child_fd = -1; + state->child->io->write_to_child_fd = -1; + talloc_set_destructor((TALLOC_CTX *) state->child->io, child_io_destructor); + + /* prepare the data to pass to child */ + ret = create_tgt_req_send_buffer(state, + realm_str, princ_str, keytab_name, lifetime, + &buf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "create_tgt_req_send_buffer failed.\n"); + goto fail; + } + + ret = sdap_fork_child(state->ev, state->child, req); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_fork_child failed.\n"); + goto fail; + } + + ret = set_tgt_child_timeout(req, ev, timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "set_tgt_child_timeout failed.\n"); + goto fail; + } + + subreq = write_pipe_send(state, ev, buf->data, buf->size, + state->child->io->write_to_child_fd); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sdap_get_tgt_step, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sdap_get_tgt_step(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_tgt_state *state = tevent_req_data(req, + struct sdap_get_tgt_state); + int ret; + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + sdap_close_fd(&state->child->io->write_to_child_fd); + + subreq = read_pipe_send(state, state->ev, + state->child->io->read_from_child_fd); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_tgt_done, req); +} + +static void sdap_get_tgt_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_tgt_state *state = tevent_req_data(req, + struct sdap_get_tgt_state); + int ret; + + ret = read_pipe_recv(subreq, state, &state->buf, &state->len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + sdap_close_fd(&state->child->io->read_from_child_fd); + + if (state->kill_te == NULL) { + tevent_req_done(req); + return; + } + + /* wait for child callback to terminate the request */ +} + +int sdap_get_tgt_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + int *result, + krb5_error_code *kerr, + char **ccname, + time_t *expire_time_out) +{ + struct sdap_get_tgt_state *state = tevent_req_data(req, + struct sdap_get_tgt_state); + char *ccn; + time_t expire_time; + int res; + int ret; + krb5_error_code krberr; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + ret = parse_child_response(mem_ctx, state->buf, state->len, + &res, &krberr, &ccn, &expire_time); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse child response: [%d][%s]\n", ret, strerror(ret)); + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Child responded: %d [%s], expired on [%"SPRItime"]\n", + res, ccn, expire_time); + *result = res; + *kerr = krberr; + *ccname = ccn; + *expire_time_out = expire_time; + return EOK; +} + +static void get_tgt_sigkill_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct sdap_get_tgt_state *state = tevent_req_data(req, + struct sdap_get_tgt_state); + int ret; + + DEBUG(SSSDBG_TRACE_ALL, + "timeout for sending SIGKILL to TGT child [%d] reached.\n", + state->child->pid); + + ret = kill(state->child->pid, SIGKILL); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "kill failed [%d][%s].\n", errno, strerror(errno)); + } + + tevent_req_error(req, ETIMEDOUT); +} + +static void get_tgt_timeout_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct sdap_get_tgt_state *state = tevent_req_data(req, + struct sdap_get_tgt_state); + int ret; + + DEBUG(SSSDBG_TRACE_ALL, + "timeout for sending SIGTERM to TGT child [%d] reached.\n", + state->child->pid); + + ret = kill(state->child->pid, SIGTERM); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Sending SIGTERM failed [%d][%s].\n", ret, strerror(ret)); + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Setting %d seconds timeout for sending SIGKILL to TGT child\n", + SIGTERM_TO_SIGKILL_TIME); + + tv = tevent_timeval_current_ofs(SIGTERM_TO_SIGKILL_TIME, 0); + + state->kill_te = tevent_add_timer(ev, req, tv, get_tgt_sigkill_handler, req); + if (state->kill_te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + tevent_req_error(req, ECANCELED); + } +} + +static errno_t set_tgt_child_timeout(struct tevent_req *req, + struct tevent_context *ev, + int timeout) +{ + struct tevent_timer *te; + struct timeval tv; + + DEBUG(SSSDBG_TRACE_FUNC, + "Setting %d seconds timeout for TGT child\n", timeout); + + tv = tevent_timeval_current_ofs(timeout, 0); + + te = tevent_add_timer(ev, req, tv, get_tgt_timeout_handler, req); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + return ENOMEM; + } + + return EOK; +} diff --git a/src/providers/ldap/sdap_domain.c b/src/providers/ldap/sdap_domain.c new file mode 100644 index 0000000..95f2ef9 --- /dev/null +++ b/src/providers/ldap/sdap_domain.c @@ -0,0 +1,232 @@ +/* + Authors: + Simo Sorce + + Copyright (C) 2008-2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/ldap_common.h" + +int +sdap_domain_destructor(void *mem) +{ + struct sdap_domain *dom = + talloc_get_type(mem, struct sdap_domain); + DLIST_REMOVE(*(dom->head), dom); + return 0; +} + +struct sdap_domain * +sdap_domain_get(struct sdap_options *opts, + struct sss_domain_info *dom) +{ + struct sdap_domain *sditer = NULL; + + DLIST_FOR_EACH(sditer, opts->sdom) { + if (sditer->dom == dom) { + break; + } + } + + return sditer; +} + +struct sdap_domain * +sdap_domain_get_by_name(struct sdap_options *opts, + const char *dom_name) +{ + struct sdap_domain *sditer = NULL; + + if (dom_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing domain name.\n"); + return NULL; + } + + DLIST_FOR_EACH(sditer, opts->sdom) { + if (sditer->dom->name != NULL + && strcasecmp(sditer->dom->name, dom_name) == 0) { + break; + } + } + + return sditer; +} + +struct sdap_domain * +sdap_domain_get_by_dn(struct sdap_options *opts, + const char *dn) +{ + struct sdap_domain *sditer = NULL; + struct sdap_domain *sdmatch = NULL; + TALLOC_CTX *tmp_ctx = NULL; + int match_len; + int best_match_len = 0; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + DLIST_FOR_EACH(sditer, opts->sdom) { + if (sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, sditer->search_bases, + NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->user_search_bases, NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->group_search_bases, NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->netgroup_search_bases, NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->sudo_search_bases, NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->service_search_bases, NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->autofs_search_bases, NULL, &match_len)) { + if (best_match_len < match_len) { + /*this is a longer match*/ + best_match_len = match_len; + sdmatch = sditer; + } + } + } + talloc_free(tmp_ctx); + return sdmatch; +} + +errno_t +sdap_domain_add(struct sdap_options *opts, + struct sss_domain_info *dom, + struct sdap_domain **_sdom) +{ + struct sdap_domain *sdom; + errno_t ret; + + sdom = talloc_zero(opts, struct sdap_domain); + if (sdom == NULL) { + return ENOMEM; + } + sdom->dom = dom; + sdom->head = &opts->sdom; + + /* Convert the domain name into search base */ + ret = domain_to_basedn(sdom, sdom->dom->name, &sdom->basedn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot convert domain name [%s] to base DN [%d]: %s\n", + dom->name, ret, strerror(ret)); + goto done; + } + + talloc_set_destructor((TALLOC_CTX *)sdom, sdap_domain_destructor); + DLIST_ADD_END(opts->sdom, sdom, struct sdap_domain *); + + if (_sdom) *_sdom = sdom; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(sdom); + } + + return ret; +} + +errno_t +sdap_domain_subdom_add(struct sdap_id_ctx *sdap_id_ctx, + struct sdap_domain *sdom_list, + struct sss_domain_info *parent) +{ + struct sss_domain_info *dom; + struct sdap_domain *sdom, *sditer; + errno_t ret; + + for (dom = get_next_domain(parent, SSS_GND_DESCEND|SSS_GND_INCLUDE_DISABLED); + dom && IS_SUBDOMAIN(dom); /* if we get back to a parent, stop */ + dom = get_next_domain(dom, SSS_GND_INCLUDE_DISABLED)) { + + /* Always create sdap domain object for the forest root, even if it is + * disabled so that we can connect later to discover trusted domains + * in the forest. */ + if (sss_domain_get_state(dom) == DOM_DISABLED + && !sss_domain_is_forest_root(dom)) { + continue; + } + + DLIST_FOR_EACH(sditer, sdom_list) { + if (sditer->dom == dom) { + break; + } + } + + if (sditer == NULL) { + /* New sdap domain */ + DEBUG(SSSDBG_TRACE_FUNC, "subdomain %s is a new one, will " + "create a new sdap domain object\n", dom->name); + + ret = sdap_domain_add(sdap_id_ctx->opts, dom, &sdom); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot add new sdap domain for domain %s [%d]: %s\n", + parent->name, ret, strerror(ret)); + return ret; + } + } else if (sditer->search_bases != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "subdomain %s has already initialized search bases\n", + dom->name); + continue; + } else { + sdom = sditer; + } + + /* Update search bases */ + talloc_zfree(sdom->search_bases); + sdom->search_bases = talloc_array(sdom, struct sdap_search_base *, 2); + if (sdom->search_bases == NULL) { + return ENOMEM; + } + sdom->search_bases[1] = NULL; + + ret = sdap_create_search_base(sdom, sysdb_ctx_get_ldb(dom->sysdb), + sdom->basedn, LDAP_SCOPE_SUBTREE, + NULL, &sdom->search_bases[0]); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot create new sdap search base\n"); + return ret; + } + + sdom->user_search_bases = sdom->search_bases; + sdom->group_search_bases = sdom->search_bases; + sdom->netgroup_search_bases = sdom->search_bases; + sdom->sudo_search_bases = sdom->search_bases; + sdom->service_search_bases = sdom->search_bases; + sdom->autofs_search_bases = sdom->search_bases; + } + + return EOK; +} + +void +sdap_domain_remove(struct sdap_options *opts, + struct sss_domain_info *dom) +{ + struct sdap_domain *sdom; + + sdom = sdap_domain_get(opts, dom); + if (sdom == NULL) return; + + DLIST_REMOVE(*(sdom->head), sdom); +} diff --git a/src/providers/ldap/sdap_dyndns.c b/src/providers/ldap/sdap_dyndns.c new file mode 100644 index 0000000..3535fb4 --- /dev/null +++ b/src/providers/ldap/sdap_dyndns.c @@ -0,0 +1,713 @@ +/* + SSSD + + sdap_dyndns.c: LDAP specific dynamic DNS update + + Authors: + Jakub Hrozek + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "resolv/async_resolv.h" +#include "providers/backend.h" +#include "providers/be_dyndns.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/sdap_dyndns.h" +#include "providers/ldap/sdap_id_op.h" +#include "providers/ldap/ldap_common.h" + +static struct tevent_req * +sdap_dyndns_get_addrs_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_ctx, + const char *iface); +static errno_t +sdap_dyndns_get_addrs_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sss_iface_addr **_addresses); + +struct sdap_dyndns_update_state { + struct tevent_context *ev; + struct be_resolv_ctx *be_res; + struct dp_option *opts; + + const char *hostname; + const char *realm; + const char *servername; + int ttl; + + struct sss_iface_addr *addresses; + struct sss_iface_addr *dns_addrlist; + uint8_t remove_af; + + bool update_per_family; + bool update_ptr; + bool check_diff; + enum be_nsupdate_auth auth_type; + enum be_nsupdate_auth auth_ptr_type; + bool fallback_mode; + char *update_msg; +}; + +static void sdap_dyndns_update_addrs_done(struct tevent_req *subreq); +static void sdap_dyndns_dns_addrs_done(struct tevent_req *subreq); +static errno_t sdap_dyndns_addrs_diff(struct sdap_dyndns_update_state *state, + bool *_do_update); +static errno_t sdap_dyndns_update_step(struct tevent_req *req); +static errno_t sdap_dyndns_update_ptr_step(struct tevent_req *req); +static void sdap_dyndns_update_done(struct tevent_req *subreq); +static void sdap_dyndns_update_ptr_done(struct tevent_req *subreq); + +static bool should_retry(int nsupdate_ret, int child_status) +{ + if ((WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) + || nsupdate_ret == ERR_DYNDNS_TIMEOUT) { + return true; + } + + return false; +} + +struct tevent_req * +sdap_dyndns_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct dp_option *opts, + struct sdap_id_ctx *sdap_ctx, + enum be_nsupdate_auth auth_type, + enum be_nsupdate_auth auth_ptr_type, + const char *ifname, + const char *hostname, + const char *realm, + const int ttl, + bool check_diff) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_dyndns_update_state *state; + const char *conf_servername; + + req = tevent_req_create(mem_ctx, &state, struct sdap_dyndns_update_state); + if (req == NULL) { + return NULL; + } + state->check_diff = check_diff; + state->update_per_family = dp_opt_get_bool(opts, DP_OPT_DYNDNS_UPDATE_PER_FAMILY); + state->update_ptr = dp_opt_get_bool(opts, DP_OPT_DYNDNS_UPDATE_PTR); + state->hostname = hostname; + state->realm = realm; + state->servername = NULL; + state->fallback_mode = false; + state->ttl = ttl; + state->be_res = be_ctx->be_res; + state->ev = ev; + state->opts = opts; + state->auth_type = auth_type; + state->auth_ptr_type = auth_ptr_type; + + /* fallback servername is overridden by user option */ + conf_servername = dp_opt_get_string(opts, DP_OPT_DYNDNS_SERVER); + if (conf_servername != NULL) { + state->servername = conf_servername; + } + + if (ifname) { + /* Unless one family is restricted, just replace all + * address families during the update + */ + switch (state->be_res->family_order) { + case IPV4_ONLY: + state->remove_af |= DYNDNS_REMOVE_A; + break; + case IPV6_ONLY: + state->remove_af |= DYNDNS_REMOVE_AAAA; + break; + case IPV4_FIRST: + case IPV6_FIRST: + state->remove_af |= (DYNDNS_REMOVE_A | + DYNDNS_REMOVE_AAAA); + break; + } + } else { + /* If the interface isn't specified, we ONLY want to have the address + * that's connected to the LDAP server stored, so we need to check + * (and later remove) both address families. + */ + state->remove_af = (DYNDNS_REMOVE_A | DYNDNS_REMOVE_AAAA); + } + + subreq = sdap_dyndns_get_addrs_send(state, state->ev, sdap_ctx, ifname); + if (!subreq) { + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + tevent_req_set_callback(subreq, sdap_dyndns_update_addrs_done, req); + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void +sdap_dyndns_update_addrs_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_dyndns_update_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + ret = sdap_dyndns_get_addrs_recv(subreq, state, &state->addresses); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Can't get addresses for DNS update\n"); + tevent_req_error(req, ret); + return; + } + + if (state->check_diff || state->update_ptr) { + /* Check if we need the update at all. In case we are updating the PTR + * records as well, we need to know the old addresses to be able to + * reliably delete the PTR records */ + subreq = nsupdate_get_addrs_send(state, state->ev, + state->be_res, state->hostname); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Can't initiate address check\n"); + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, sdap_dyndns_dns_addrs_done, req); + return; + } + + /* Perform update */ + ret = sdap_dyndns_update_step(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + /* Execution will resume in sdap_dyndns_update_done */ +} + +static void +sdap_dyndns_dns_addrs_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_dyndns_update_state *state; + errno_t ret; + bool do_update; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + ret = nsupdate_get_addrs_recv(subreq, state, &state->dns_addrlist, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not receive list of current addresses [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->check_diff) { + ret = sdap_dyndns_addrs_diff(state, &do_update); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not check the diff between DNS " + "and current addresses [%d]: %s\n", ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (do_update == false) { + DEBUG(SSSDBG_TRACE_FUNC, + "No DNS update needed, addresses did not change\n"); + tevent_req_done(req); + return; + } + DEBUG(SSSDBG_TRACE_FUNC, + "Detected IP addresses change, will perform an update\n"); + } + + /* Either we needed the addresses for updating PTR records only or + * the addresses have changed (or both) */ + ret = sdap_dyndns_update_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not start the update [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + } + return; +} + +static errno_t +sdap_dyndns_addrs_diff(struct sdap_dyndns_update_state *state, bool *_do_update) +{ + errno_t ret; + int i; + char **str_dnslist = NULL, **str_local_list = NULL; + char **dns_only = NULL, **local_only = NULL; + bool do_update = false; + + ret = sss_iface_addr_list_as_str_list(state, + state->dns_addrlist, &str_dnslist); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Converting DNS IP addresses to strings failed: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = sss_iface_addr_list_as_str_list(state, + state->addresses, &str_local_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Converting local IP addresses to strings failed: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + /* Compare the lists */ + ret = diff_string_lists(state, str_dnslist, str_local_list, + &dns_only, &local_only, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "diff_string_lists failed: [%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + if (dns_only) { + for (i=0; dns_only[i]; i++) { + DEBUG(SSSDBG_TRACE_LIBS, + "Address in DNS only: %s\n", dns_only[i]); + do_update = true; + } + } + + if (local_only) { + for (i=0; local_only[i]; i++) { + DEBUG(SSSDBG_TRACE_LIBS, + "Address on localhost only: %s\n", local_only[i]); + do_update = true; + } + } + + *_do_update = do_update; + return EOK; +} + +static errno_t +sdap_dyndns_update_step(struct tevent_req *req) +{ + errno_t ret; + struct sdap_dyndns_update_state *state; + const char *servername; + const char *realm; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + servername = NULL; + realm = NULL; + if (state->fallback_mode) { + servername = state->servername; + realm = state->realm; + } + + ret = be_nsupdate_create_fwd_msg(state, realm, servername, + state->hostname, + state->ttl, state->remove_af, + state->addresses, + state->update_per_family, + &state->update_msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Can't get addresses for DNS update\n"); + return ret; + } + + /* Fork a child process to perform the DNS update */ + subreq = be_nsupdate_send(state, state->ev, state->auth_type, + state->update_msg, + dp_opt_get_bool(state->opts, + DP_OPT_DYNDNS_FORCE_TCP)); + if (subreq == NULL) { + return EIO; + } + + tevent_req_set_callback(subreq, sdap_dyndns_update_done, req); + return EOK; +} + +static void +sdap_dyndns_update_done(struct tevent_req *subreq) +{ + errno_t ret; + int child_status; + struct tevent_req *req; + struct sdap_dyndns_update_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + ret = be_nsupdate_recv(subreq, &child_status); + talloc_zfree(subreq); + if (ret != EOK) { + /* If the update didn't succeed, we can retry using the server name */ + if (state->fallback_mode == false + && should_retry(ret, child_status)) { + state->fallback_mode = true; + DEBUG(SSSDBG_MINOR_FAILURE, + "nsupdate failed, retrying.\n"); + ret = sdap_dyndns_update_step(req); + if (ret == EOK) { + return; + } + } + } + + if (state->update_ptr == false) { + DEBUG(SSSDBG_TRACE_FUNC, "No PTR update requested, done\n"); + tevent_req_done(req); + return; + } + + talloc_free(state->update_msg); + + ret = sdap_dyndns_update_ptr_step(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + /* Execution will resume in sdap_dyndns_update_ptr_done */ +} + +static errno_t +sdap_dyndns_update_ptr_step(struct tevent_req *req) +{ + errno_t ret; + struct sdap_dyndns_update_state *state; + const char *servername; + const char *realm; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + servername = NULL; + realm = NULL; + if (state->fallback_mode == true) { + servername = state->servername; + realm = state->realm; + } + + ret = be_nsupdate_create_ptr_msg(state, realm, servername, + state->hostname, + state->ttl, state->remove_af, + state->addresses, + state->update_per_family, + &state->update_msg); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Can't get addresses for DNS update\n"); + return ret; + } + + /* Fork a child process to perform the DNS update */ + subreq = be_nsupdate_send(state, state->ev, state->auth_ptr_type, + state->update_msg, + dp_opt_get_bool(state->opts, + DP_OPT_DYNDNS_FORCE_TCP)); + if (subreq == NULL) { + return EIO; + } + + tevent_req_set_callback(subreq, sdap_dyndns_update_ptr_done, req); + return EOK; +} + +static void +sdap_dyndns_update_ptr_done(struct tevent_req *subreq) +{ + errno_t ret; + int child_status; + struct tevent_req *req; + struct sdap_dyndns_update_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + ret = be_nsupdate_recv(subreq, &child_status); + talloc_zfree(subreq); + if (ret != EOK) { + /* If the update didn't succeed, we can retry using the server name */ + if (state->fallback_mode == false + && should_retry(ret, child_status)) { + state->fallback_mode = true; + DEBUG(SSSDBG_MINOR_FAILURE, "nsupdate failed, retrying\n"); + ret = sdap_dyndns_update_ptr_step(req); + if (ret == EOK) { + return; + } + } + + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +sdap_dyndns_update_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +/* A request to get addresses to update with */ +struct sdap_dyndns_get_addrs_state { + struct sdap_id_op* sdap_op; + struct sss_iface_addr *addresses; +}; + +static void sdap_dyndns_get_addrs_done(struct tevent_req *subreq); +static errno_t sdap_dyndns_add_ldap_conn(struct sdap_dyndns_get_addrs_state *state, + struct sdap_handle *sh); + +static errno_t get_ifaces_addrs(TALLOC_CTX *mem_ctx, + const char *iface, + struct sss_iface_addr **_result) +{ + struct sss_iface_addr *result_addrs = NULL; + struct sss_iface_addr *intf_addrs; + TALLOC_CTX *tmp_ctx; + char **list_of_intfs; + int num_of_intfs; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = split_on_separator(tmp_ctx, iface, ',', true, true, &list_of_intfs, + &num_of_intfs); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Parsing names of interfaces failed - %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + for (i = 0; i < num_of_intfs; i++) { + ret = sss_iface_addr_list_get(tmp_ctx, list_of_intfs[i], &intf_addrs); + if (ret == EOK) { + if (result_addrs != NULL) { + /* If there is already an existing list, head of this existing + * list will be considered as parent talloc context for the + * new list. + */ + talloc_steal(result_addrs, intf_addrs); + } + sss_iface_addr_concatenate(&result_addrs, intf_addrs); + } else if (ret == ENOENT) { + /* non-critical failure */ + DEBUG(SSSDBG_TRACE_FUNC, + "Cannot get interface %s or there are no addresses " + "bind to it.\n", list_of_intfs[i]); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get list of addresses from interface %s - %d:[%s]\n", + list_of_intfs[i], ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + *_result = talloc_steal(mem_ctx, result_addrs); + +done: + talloc_free(tmp_ctx); + return ret; +} + +static struct tevent_req * +sdap_dyndns_get_addrs_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_ctx, + const char *iface) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_dyndns_get_addrs_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_dyndns_get_addrs_state); + if (req == NULL) { + return NULL; + } + + if (iface) { + ret = get_ifaces_addrs(state, iface, &state->addresses); + if (ret != EOK || state->addresses == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "get_ifaces_addrs() failed: %d:[%s]\n", + ret, sss_strerror(ret)); + } + /* We're done. Just fake an async request completion */ + goto done; + } + + /* Detect DYNDNS address from LDAP connection */ + state->sdap_op = sdap_id_op_create(state, sdap_ctx->conn->conn_cache); + if (!state->sdap_op) { + ret = ENOMEM; + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + goto done; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (!subreq) { + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + tevent_req_set_callback(subreq, sdap_dyndns_get_addrs_done, req); + + ret = EAGAIN; +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + /* EAGAIN - resolution in progress */ + return req; +} + +static void +sdap_dyndns_get_addrs_done(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error; + struct tevent_req *req; + struct sdap_dyndns_get_addrs_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_dyndns_get_addrs_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, "No LDAP server is available, " + "dynamic DNS update is skipped in offline mode.\n"); + ret = ERR_DYNDNS_OFFLINE; + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to LDAP server: [%d](%s)\n", + ret, sss_strerror(ret)); + } + tevent_req_error(req, ret); + return; + } + + ret = sdap_dyndns_add_ldap_conn(state, sdap_id_op_handle(state->sdap_op)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Can't get addresses from LDAP connection\n"); + tevent_req_error(req, ret); + return; + } + + /* Got the address! Done! */ + tevent_req_done(req); +} + +static errno_t +sdap_dyndns_add_ldap_conn(struct sdap_dyndns_get_addrs_state *state, + struct sdap_handle *sh) +{ + int ret; + int fd; + struct sockaddr_storage ss = {0}; + socklen_t ss_len = sizeof(ss); + + if (sh == NULL) { + return EINVAL; + } + + /* Get the file descriptor for the primary LDAP connection */ + ret = get_fd_from_ldap(sh->ldap, &fd); + if (ret != EOK) { + return ret; + } + + errno = 0; + ret = getsockname(fd, (struct sockaddr *) &ss, &ss_len); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to get socket name\n"); + return ret; + } + + if (ss.ss_family != AF_INET && ss.ss_family != AF_INET6) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Connection to LDAP is neither IPv4 nor IPv6\n"); + return EIO; + } + + ret = sss_get_dualstack_addresses(state, (struct sockaddr *) &ss, + &state->addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sss_get_dualstack_addresses failed: %d:[%s]\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +static errno_t +sdap_dyndns_get_addrs_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sss_iface_addr **_addresses) +{ + struct sdap_dyndns_get_addrs_state *state; + + state = tevent_req_data(req, struct sdap_dyndns_get_addrs_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_addresses = talloc_steal(mem_ctx, state->addresses); + return EOK; +} diff --git a/src/providers/ldap/sdap_dyndns.h b/src/providers/ldap/sdap_dyndns.h new file mode 100644 index 0000000..5fb3667 --- /dev/null +++ b/src/providers/ldap/sdap_dyndns.h @@ -0,0 +1,49 @@ +/* + SSSD + + sdap_dyndns.h: LDAP specific dynamic DNS update + + Authors: + Jakub Hrozek + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef SDAP_DYNDNS_H_ +#define SDAP_DYNDNS_H_ + +#include "util/util.h" +#include "providers/backend.h" +#include "providers/be_dyndns.h" +#include "providers/ldap/ldap_common.h" + +struct tevent_req * +sdap_dyndns_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct dp_option *opts, + struct sdap_id_ctx *sdap_ctx, + enum be_nsupdate_auth auth_type, + enum be_nsupdate_auth auth_ptr_type, + const char *ifname, + const char *hostname, + const char *realm, + const int ttl, + bool check_diff); + +errno_t sdap_dyndns_update_recv(struct tevent_req *req); + +#endif /* SDAP_DYNDNS_H_ */ diff --git a/src/providers/ldap/sdap_fd_events.c b/src/providers/ldap/sdap_fd_events.c new file mode 100644 index 0000000..42b2efe --- /dev/null +++ b/src/providers/ldap/sdap_fd_events.c @@ -0,0 +1,357 @@ +/* + SSSD + + Helper routines for file descriptor events + + Authors: + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "util/sss_sockets.h" +#include "util/sss_chain_id.h" +#include "providers/ldap/sdap_async_private.h" + +struct sdap_fd_events { +#ifdef HAVE_LDAP_CONNCB + struct ldap_conncb *conncb; +#else + struct tevent_fd *fde; +#endif +}; + +int get_fd_from_ldap(LDAP *ldap, int *fd) +{ + int ret; + + ret = ldap_get_option(ldap, LDAP_OPT_DESC, fd); + if (ret != LDAP_OPT_SUCCESS || *fd < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to get fd from ldap!!\n"); + *fd = -1; + return EIO; + } + + return EOK; +} + +int remove_ldap_connection_callbacks(struct sdap_handle *sh) +{ + /* sdap_fd_events might be NULL here if the back end was marked offline + * before a connection was established. + */ + if (sh->sdap_fd_events) { +#ifdef HAVE_LDAP_CONNCB + talloc_zfree(sh->sdap_fd_events->conncb); +#else + talloc_zfree(sh->sdap_fd_events->fde); +#endif + } + return EOK; +} + +#ifdef HAVE_LDAP_CONNCB + +static int remove_connection_callback(TALLOC_CTX *mem_ctx) +{ + int lret; + struct ldap_conncb *conncb = talloc_get_type(mem_ctx, struct ldap_conncb); + + struct ldap_cb_data *cb_data = talloc_get_type(conncb->lc_arg, + struct ldap_cb_data); + + lret = ldap_get_option(cb_data->sh->ldap, LDAP_OPT_CONNECT_CB, conncb); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to remove connection callback.\n"); + } else { + DEBUG(SSSDBG_TRACE_ALL, "Successfully removed connection callback.\n"); + } + return EOK; +} + +static int sdap_ldap_connect_callback_add(LDAP *ld, Sockbuf *sb, + LDAPURLDesc *srv, + struct sockaddr *addr, + struct ldap_conncb *ctx) +{ + int ret; + ber_socket_t ber_fd; + uint64_t old_chain_id; + struct timeval *tv = NULL; + struct fd_event_item *fd_event_item; + struct ldap_cb_data *cb_data = talloc_get_type(ctx->lc_arg, + struct ldap_cb_data); + + if (cb_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_ldap_connect_callback_add called without " + "callback data.\n"); + return EINVAL; + } + + ret = ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &ber_fd); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_sockbuf_ctrl failed.\n"); + return EINVAL; + } + + /* (ld == NULL) means call flow is sdap_sys_connect_done() -> + * sdap_call_conn_cb() and this is "regular" socket that was already setup + * in sssd_async_socket_init_send(). + * Otherwise this is socket open by libldap during referral chasing and it + * requires setting up. + */ + if (ld != NULL) { + ret = ldap_get_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &tv); + if ((ret == LDAP_OPT_SUCCESS) && (tv != NULL)) { + ret = set_fd_common_opts(ber_fd, tv->tv_sec); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "set_fd_common_opts() failed\n"); + } + free(tv); + tv = NULL; + } else if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldap_get_option(LDAP_OPT_NETWORK_TIMEOUT) failed\n"); + } + } + + char *uri = ldap_url_desc2str(srv); + DEBUG(SSSDBG_TRACE_ALL, "New connection to [%s] with fd [%d]\n", + uri, ber_fd); + free(uri); + + fd_event_item = talloc_zero(cb_data, struct fd_event_item); + if (fd_event_item == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + /* This is a global event which is shared between multiple requests. However + * it is usually created from an input request chain therefore we need to set + * the chain id to zero explicitly. */ + old_chain_id = sss_chain_id_set(0); + fd_event_item->fde = tevent_add_fd(cb_data->ev, fd_event_item, ber_fd, + TEVENT_FD_READ, sdap_ldap_result, + cb_data->sh); + sss_chain_id_set(old_chain_id); + if (fd_event_item->fde == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_fd failed.\n"); + talloc_free(fd_event_item); + return ENOMEM; + } + fd_event_item->fd = ber_fd; + + DLIST_ADD(cb_data->fd_list, fd_event_item); + + return LDAP_SUCCESS; +} + +static void sdap_ldap_connect_callback_del(LDAP *ld, Sockbuf *sb, + struct ldap_conncb *ctx) +{ + int ret; + ber_socket_t ber_fd; + struct fd_event_item *fd_event_item; + struct ldap_cb_data *cb_data = talloc_get_type(ctx->lc_arg, + struct ldap_cb_data); + + if (sb == NULL || cb_data == NULL) { + return; + } + + ret = ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &ber_fd); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_sockbuf_ctrl failed.\n"); + return; + } + DEBUG(SSSDBG_TRACE_ALL, "Closing LDAP connection with fd [%d].\n", ber_fd); + + DLIST_FOR_EACH(fd_event_item, cb_data->fd_list) { + if (fd_event_item->fd == ber_fd) { + break; + } + } + if (fd_event_item == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No event for fd [%d] found.\n", ber_fd); + return; + } + + DLIST_REMOVE(cb_data->fd_list, fd_event_item); + talloc_zfree(fd_event_item); + + return; +} + +#else + +static int sdap_install_ldap_callbacks(struct sdap_handle *sh, + struct tevent_context *ev) +{ + uint64_t old_chain_id; + int fd; + int ret; + + if (sh->sdap_fd_events) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_install_ldap_callbacks is called with already " + "initialized sdap_fd_events.\n"); + return EINVAL; + } + + sh->sdap_fd_events = talloc_zero(sh, struct sdap_fd_events); + if (!sh->sdap_fd_events) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + ret = get_fd_from_ldap(sh->ldap, &fd); + if (ret) return ret; + + /* This is a global event which is shared between multiple requests. However + * it is usually created from an input request chain therefore we need to set + * the chain id to zero explicitly. */ + old_chain_id = sss_chain_id_set(0); + sh->sdap_fd_events->fde = tevent_add_fd(ev, sh->sdap_fd_events, fd, + TEVENT_FD_READ, sdap_ldap_result, + sh); + sss_chain_id_set(old_chain_id); + if (!sh->sdap_fd_events->fde) { + talloc_zfree(sh->sdap_fd_events); + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Trace: sh[%p], connected[%d], ops[%p], fde[%p], ldap[%p]\n", + sh, (int)sh->connected, sh->ops, sh->sdap_fd_events->fde, + sh->ldap); + + return EOK; +} + +#endif + + +errno_t setup_ldap_connection_callbacks(struct sdap_handle *sh, + struct tevent_context *ev) +{ +#ifdef HAVE_LDAP_CONNCB + int ret; + struct ldap_cb_data *cb_data; + + sh->sdap_fd_events = talloc_zero(sh, struct sdap_fd_events); + if (sh->sdap_fd_events == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto fail; + } + + sh->sdap_fd_events->conncb = talloc_zero(sh->sdap_fd_events, + struct ldap_conncb); + if (sh->sdap_fd_events->conncb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto fail; + } + + cb_data = talloc_zero(sh->sdap_fd_events->conncb, struct ldap_cb_data); + if (cb_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto fail; + } + cb_data->sh = sh; + cb_data->ev = ev; + + sh->sdap_fd_events->conncb->lc_add = sdap_ldap_connect_callback_add; + sh->sdap_fd_events->conncb->lc_del = sdap_ldap_connect_callback_del; + sh->sdap_fd_events->conncb->lc_arg = cb_data; + + ret = ldap_set_option(sh->ldap, LDAP_OPT_CONNECT_CB, + sh->sdap_fd_events->conncb); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set connection callback\n"); + ret = EFAULT; + goto fail; + } + + talloc_set_destructor((TALLOC_CTX *) sh->sdap_fd_events->conncb, + remove_connection_callback); + + return EOK; + +fail: + talloc_zfree(sh->sdap_fd_events); + return ret; +#else + DEBUG(SSSDBG_TRACE_ALL, "LDAP connection callbacks are not supported.\n"); + return EOK; +#endif +} + +errno_t sdap_set_connected(struct sdap_handle *sh, struct tevent_context *ev) +{ + int ret = EOK; + + sh->connected = true; + +#ifndef HAVE_LDAP_CONNCB + ret = sdap_install_ldap_callbacks(sh, ev); +#endif + + return ret; +} + +errno_t sdap_call_conn_cb(const char *uri,int fd, struct sdap_handle *sh) +{ +#ifdef HAVE_LDAP_CONNCB + int ret; + Sockbuf *sb; + LDAPURLDesc *lud; + + sb = ber_sockbuf_alloc(); + if (sb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_sockbuf_alloc failed.\n"); + return ENOMEM; + } + + ret = ber_sockbuf_ctrl(sb, LBER_SB_OPT_SET_FD, &fd); + if (ret != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_sockbuf_ctrl failed.\n"); + return EFAULT; + } + + ret = ldap_url_parse(uri, &lud); + if (ret != 0) { + ber_sockbuf_free(sb); + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_url_parse failed to validate [%s] on fd [%d].\n", + uri, fd); + return EFAULT; + } + + ret = sdap_ldap_connect_callback_add(NULL, sb, lud, NULL, + sh->sdap_fd_events->conncb); + + ldap_free_urldesc(lud); + ber_sockbuf_free(sb); + return ret; +#else + DEBUG(SSSDBG_TRACE_ALL, "LDAP connection callbacks are not supported.\n"); + return EOK; +#endif +} diff --git a/src/providers/ldap/sdap_hostid.c b/src/providers/ldap/sdap_hostid.c new file mode 100644 index 0000000..ae8caad --- /dev/null +++ b/src/providers/ldap/sdap_hostid.c @@ -0,0 +1,324 @@ +/* + Authors: + Jan Cholasta + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "db/sysdb_ssh.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_hostid.h" + +struct hosts_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_op *op; + struct sss_domain_info *domain; + const char *name; + const char *alias; + + size_t count; + struct sysdb_attrs **hosts; + int dp_error; +}; + +static errno_t +hosts_get_retry(struct tevent_req *req); +static void +hosts_get_connect_done(struct tevent_req *subreq); +static void +hosts_get_done(struct tevent_req *subreq); + +struct tevent_req * +hosts_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + const char *name, + const char *alias) +{ + struct tevent_req *req; + struct hosts_get_state *state; + errno_t ret; + + req = tevent_req_create(memctx, &state, struct hosts_get_state); + if (!req) return NULL; + + state->ev = ev; + state->id_ctx = id_ctx; + state->dp_error = DP_ERR_FATAL; + + state->op = sdap_id_op_create(state, id_ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = id_ctx->be->domain; + state->name = name; + state->alias = alias; + + ret = hosts_get_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +hosts_get_retry(struct tevent_req *req) +{ + struct hosts_get_state *state = tevent_req_data(req, + struct hosts_get_state); + struct tevent_req *subreq; + errno_t ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, hosts_get_connect_done, req); + return EOK; +} + +static void +hosts_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hosts_get_state *state = tevent_req_data(req, + struct hosts_get_state); + int dp_error = DP_ERR_FATAL; + errno_t ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_host_info_send(state, state->ev, + sdap_id_op_handle(state->op), + state->id_ctx->opts, state->name, + state->id_ctx->opts->host_map, + state->id_ctx->opts->sdom->host_search_bases); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, hosts_get_done, req); +} + +static void +hosts_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hosts_get_state *state = tevent_req_data(req, + struct hosts_get_state); + int dp_error = DP_ERR_FATAL; + errno_t ret; + struct sysdb_attrs *attrs; + time_t now = time(NULL); + + ret = sdap_host_info_recv(subreq, state, + &state->count, &state->hosts); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = hosts_get_retry(req); + if (ret != EOK) { + goto done; + } + return; + } + + if (ret != EOK && ret != ENOENT) { + goto done; + } + + if (state->count == 0) { + DEBUG(SSSDBG_FUNC_DATA, + "No host with name [%s] found.\n", state->name); + + ret = sysdb_delete_ssh_host(state->domain, state->name); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + ret = EINVAL; + goto done; + } + + if (state->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Found more than one host with name [%s].\n", state->name); + ret = EINVAL; + goto done; + } + + attrs = sysdb_new_attrs(state); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + /* we are interested only in the host keys */ + ret = sysdb_attrs_copy_values(state->hosts[0], attrs, SYSDB_SSH_PUBKEY); + if (ret != EOK) { + goto done; + } + + ret = sysdb_store_ssh_host(state->domain, state->name, state->alias, + state->domain->ssh_host_timeout, now, attrs); + if (ret != EOK) { + goto done; + } + + dp_error = DP_ERR_OK; + +done: + state->dp_error = dp_error; + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t +hosts_get_recv(struct tevent_req *req, + int *dp_error_out) +{ + struct hosts_get_state *state = tevent_req_data(req, + struct hosts_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_hostid_handler_state { + struct dp_reply_std reply; +}; + +static void sdap_hostid_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_hostid_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_hostid_data *data, + struct dp_req_params *params) +{ + struct sdap_hostid_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_hostid_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + subreq = hosts_get_send(state, params->ev, id_ctx, + data->name, data->alias); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request\n"); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_hostid_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_hostid_handler_done(struct tevent_req *subreq) +{ + struct sdap_hostid_handler_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_hostid_handler_state); + + ret = hosts_get_recv(subreq, &dp_error); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +errno_t +sdap_hostid_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_hostid_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_hostid_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} + +errno_t sdap_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + (void)be_ctx; + + dp_set_method(dp_methods, DPM_HOSTID_HANDLER, + sdap_hostid_handler_send, sdap_hostid_handler_recv, id_ctx, + struct sdap_id_ctx, struct dp_hostid_data, struct dp_reply_std); + + return EOK; +} diff --git a/src/providers/ldap/sdap_hostid.h b/src/providers/ldap/sdap_hostid.h new file mode 100644 index 0000000..6234f9f --- /dev/null +++ b/src/providers/ldap/sdap_hostid.h @@ -0,0 +1,40 @@ +/* + Authors: + Jan Cholasta + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_HOSTID_H_ +#define _SDAP_HOSTID_H_ + +errno_t sdap_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_method *dp_methods); + +struct tevent_req * +sdap_hostid_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_hostid_data *data, + struct dp_req_params *params); + +errno_t +sdap_hostid_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +#endif /* _SDAP_HOSTID_H_ */ diff --git a/src/providers/ldap/sdap_id_op.c b/src/providers/ldap/sdap_id_op.c new file mode 100644 index 0000000..857a635 --- /dev/null +++ b/src/providers/ldap/sdap_id_op.c @@ -0,0 +1,1054 @@ +/* + SSSD + + LDAP ID backend operation retry logic and connection cache + + Authors: + Eugene Indenbom + + Copyright (C) 2008-2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_id_op.h" +#include "util/sss_chain_id.h" + +/* LDAP async connection cache */ +struct sdap_id_conn_cache { + struct sdap_id_conn_ctx *id_conn; + + /* list of all open connections */ + struct sdap_id_conn_data *connections; + /* cached (current) connection */ + struct sdap_id_conn_data *cached_connection; +}; + +/* LDAP async operation tracker: + * - keeps track of connection usage + * - keeps track of operation retries */ +struct sdap_id_op { + /* ID backend context */ + struct sdap_id_conn_cache *conn_cache; + /* double linked list pointers */ + struct sdap_id_op *prev, *next; + /* current connection */ + struct sdap_id_conn_data *conn_data; + /* number of reconnects for this operation */ + int reconnect_retry_count; + /* connection request + * It is required as we need to know which requests to notify + * when shared connection request to sdap_handle completes. + * This member is cleared when sdap_id_op_connect_state + * associated with request is destroyed */ + struct tevent_req *connect_req; + + /* chain id of the request that created this op */ + uint64_t chain_id; +}; + +/* LDAP connection cache connection attempt/established connection data */ +struct sdap_id_conn_data { + /* LDAP connection cache */ + struct sdap_id_conn_cache *conn_cache; + /* double linked list pointers */ + struct sdap_id_conn_data *prev, *next; + /* sdap handle */ + struct sdap_handle *sh; + /* connection request */ + struct tevent_req *connect_req; + /* timer for connection expiration */ + struct tevent_timer *expire_timer; + /* timer for idle connection expiration */ + struct tevent_timer *idle_timer; + /* number of running connection notifies */ + int notify_lock; + /* list of operations using connect */ + struct sdap_id_op *ops; + /* A flag which is signalizing that this + * connection will be disconnected and should + * not be used any more */ + bool disconnecting; +}; + +static void sdap_id_conn_cache_be_offline_cb(void *pvt); +static void sdap_id_conn_cache_fo_reconnect_cb(void *pvt); + +static void sdap_id_release_conn_data(struct sdap_id_conn_data *conn_data); +static int sdap_id_conn_data_destroy(struct sdap_id_conn_data *conn_data); +static bool sdap_is_connection_expired(struct sdap_id_conn_data *conn_data, int timeout); +static bool sdap_can_reuse_connection(struct sdap_id_conn_data *conn_data); +static void sdap_id_conn_data_expire_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt); +static int sdap_id_conn_data_set_expire_timer(struct sdap_id_conn_data *conn_data); +static void sdap_id_conn_data_idle_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt); +static int sdap_id_conn_data_start_idle_timer(struct sdap_id_conn_data *conn_data); +static void sdap_id_conn_data_not_idle(struct sdap_id_conn_data *conn_data); +static void sdap_id_conn_data_idle(struct sdap_id_conn_data *conn_data); + +static void sdap_id_op_hook_conn_data(struct sdap_id_op *op, struct sdap_id_conn_data *conn_data); +static int sdap_id_op_destroy(void *pvt); +static bool sdap_id_op_can_reconnect(struct sdap_id_op *op); + +static void sdap_id_op_connect_req_complete(struct sdap_id_op *op, int dp_error, int ret); +static int sdap_id_op_connect_state_destroy(void *pvt); +static int sdap_id_op_connect_step(struct tevent_req *req); +static void sdap_id_op_connect_done(struct tevent_req *subreq); + +/* Create a connection cache */ +int sdap_id_conn_cache_create(TALLOC_CTX *memctx, + struct sdap_id_conn_ctx *id_conn, + struct sdap_id_conn_cache** conn_cache_out) +{ + int ret; + struct sdap_id_conn_cache *conn_cache = talloc_zero(memctx, struct sdap_id_conn_cache); + if (!conn_cache) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_zero(struct sdap_id_conn_cache) failed.\n"); + ret = ENOMEM; + goto fail; + } + + conn_cache->id_conn = id_conn; + + ret = be_add_offline_cb(conn_cache, id_conn->id_ctx->be, + sdap_id_conn_cache_be_offline_cb, conn_cache, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_add_offline_cb failed.\n"); + goto fail; + } + + ret = be_add_reconnect_cb(conn_cache, id_conn->id_ctx->be, + sdap_id_conn_cache_fo_reconnect_cb, conn_cache, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_add_reconnect_cb failed.\n"); + goto fail; + } + + *conn_cache_out = conn_cache; + return EOK; + +fail: + talloc_zfree(conn_cache); + return ret; +} + +/* Callback on BE going offline */ +static void sdap_id_conn_cache_be_offline_cb(void *pvt) +{ + struct sdap_id_conn_cache *conn_cache = talloc_get_type(pvt, struct sdap_id_conn_cache); + struct sdap_id_conn_data *cached_connection = conn_cache->cached_connection; + + /* Release any cached connection on going offline */ + if (cached_connection != NULL) { + conn_cache->cached_connection = NULL; + sdap_id_release_conn_data(cached_connection); + } +} + +/* Callback for attempt to reconnect to primary server */ +static void sdap_id_conn_cache_fo_reconnect_cb(void *pvt) +{ + struct sdap_id_conn_cache *conn_cache = talloc_get_type(pvt, struct sdap_id_conn_cache); + struct sdap_id_conn_data *cached_connection = conn_cache->cached_connection; + + /* Release any cached connection on going offline */ + if (cached_connection != NULL) { + cached_connection->disconnecting = true; + } +} + +/* Release sdap_id_conn_data and destroy it if no longer needed */ +static void sdap_id_release_conn_data(struct sdap_id_conn_data *conn_data) +{ + ber_socket_t fd = -1; + Sockbuf *sb; + int ret; + struct sdap_id_conn_cache *conn_cache; + if (!conn_data || conn_data->ops || conn_data->notify_lock) { + /* connection is in use */ + return; + } + + conn_cache = conn_data->conn_cache; + if (conn_data == conn_cache->cached_connection) { + return; + } + + if (conn_data->sh && conn_data->sh->ldap) { + ret = ldap_get_option(conn_data->sh->ldap, LDAP_OPT_SOCKBUF, &sb); + if (ret == LDAP_OPT_SUCCESS) { + if (ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &fd) != 1) { + fd = -1; + } + } + } + + DEBUG(SSSDBG_TRACE_ALL, "Releasing unused connection with fd [%d]\n", fd); + + DLIST_REMOVE(conn_cache->connections, conn_data); + talloc_zfree(conn_data); +} + +/* Destructor for struct sdap_id_conn_data */ +static int sdap_id_conn_data_destroy(struct sdap_id_conn_data *conn_data) +{ + struct sdap_id_op *op; + + /* we clean out list of ops to make sure that order of destruction does not matter */ + while ((op = conn_data->ops) != NULL) { + op->conn_data = NULL; + DLIST_REMOVE(conn_data->ops, op); + } + + return 0; +} + +/* Check whether connection will expire after timeout seconds */ +static bool sdap_is_connection_expired(struct sdap_id_conn_data *conn_data, int timeout) +{ + time_t expire_time; + if (!conn_data || !conn_data->sh || !conn_data->sh->connected) { + return true; + } + + expire_time = conn_data->sh->expire_time; + if ((expire_time != 0) && (expire_time < time( NULL ) + timeout) ) { + return true; + } + + return false; +} + +/* Check whether connection can be reused for next LDAP ID operation */ +static bool sdap_can_reuse_connection(struct sdap_id_conn_data *conn_data) +{ + int timeout; + + if (!conn_data || !conn_data->sh || + !conn_data->sh->connected || conn_data->disconnecting) { + return false; + } + + timeout = dp_opt_get_int(conn_data->conn_cache->id_conn->id_ctx->opts->basic, + SDAP_OPT_TIMEOUT); + return !sdap_is_connection_expired(conn_data, timeout); +} + +/* Set expiration timer for connection if needed */ +static int sdap_id_conn_data_set_expire_timer(struct sdap_id_conn_data *conn_data) +{ + int timeout; + struct timeval tv; + + talloc_zfree(conn_data->expire_timer); + + memset(&tv, 0, sizeof(tv)); + + tv.tv_sec = conn_data->sh->expire_time; + if (tv.tv_sec <= 0) { + return EOK; + } + + timeout = dp_opt_get_int(conn_data->conn_cache->id_conn->id_ctx->opts->basic, + SDAP_OPT_TIMEOUT); + if (timeout > 0) { + tv.tv_sec -= timeout; + } + + if (tv.tv_sec <= time(NULL)) { + DEBUG(SSSDBG_TRACE_ALL, + "Not starting expire timer because connection is already expired\n"); + return EOK; + } + + conn_data->expire_timer = + tevent_add_timer(conn_data->conn_cache->id_conn->id_ctx->be->ev, + conn_data, tv, + sdap_id_conn_data_expire_handler, + conn_data); + if (!conn_data->expire_timer) { + return ENOMEM; + } + + return EOK; +} + +/* Handler for connection expiration timer */ +static void sdap_id_conn_data_expire_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct sdap_id_conn_data *conn_data = talloc_get_type(pvt, + struct sdap_id_conn_data); + struct sdap_id_conn_cache *conn_cache = conn_data->conn_cache; + + if (conn_cache->cached_connection == conn_data) { + DEBUG(SSSDBG_TRACE_ALL, + "Connection is about to expire, releasing it\n"); + conn_cache->cached_connection = NULL; + sdap_id_release_conn_data(conn_data); + } +} + +/* We could simply cancel the idle timer at the beginning of every operation + * then reschedule it at the end of every operation. However, to reduce the + * overhead associated with canceling and rescheduling the timer, we instead + * update conn_data->sh->idle_time at the beginning and end of each operation, + * then have the timer handler check idle_time and reschedule the timer as + * needed. + * + * Note that sdap_id_conn_data_not_idle() and/or sdap_id_conn_data_idle() may be + * called before sdap_id_conn_data_start_idle_timer() is called for a particular + * connection. + */ + +/* Start idle timer for connection if needed */ +static int sdap_id_conn_data_start_idle_timer(struct sdap_id_conn_data *conn_data) +{ + time_t now; + int idle_timeout; + struct timeval tv; + + now = time(NULL); + conn_data->sh->idle_time = now; + + talloc_zfree(conn_data->idle_timer); + + idle_timeout = dp_opt_get_int(conn_data->conn_cache->id_conn->id_ctx->opts->basic, + SDAP_IDLE_TIMEOUT); + conn_data->sh->idle_timeout = idle_timeout; + DEBUG(SSSDBG_CONF_SETTINGS, "idle timeout is %d\n", idle_timeout); + if (idle_timeout <= 0) { + return EOK; + } + + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = now + idle_timeout; + DEBUG(SSSDBG_TRACE_ALL, + "Scheduling connection idle timer to run at %"SPRItime"\n", tv.tv_sec); + + conn_data->idle_timer = + tevent_add_timer(conn_data->conn_cache->id_conn->id_ctx->be->ev, + conn_data, tv, + sdap_id_conn_data_idle_handler, + conn_data); + if (!conn_data->idle_timer) { + return ENOMEM; + } + + return EOK; +} + +/* Handler for idle connection expiration timer */ +static void sdap_id_conn_data_idle_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct sdap_id_conn_data *conn_data = talloc_get_type(pvt, + struct sdap_id_conn_data); + struct sdap_id_conn_cache *conn_cache = conn_data->conn_cache; + + time_t now; + time_t idle_time; + int idle_timeout; + struct timeval tv; + + if (conn_cache->cached_connection != conn_data) { + DEBUG(SSSDBG_TRACE_ALL, "Abandoning idle timer for released connection\n"); + return; + } + + now = time(NULL); + idle_time = conn_data->sh->idle_time; + idle_timeout = conn_data->sh->idle_timeout; + + if (idle_time != 0 && idle_time + idle_timeout <= now) { + DEBUG(SSSDBG_TRACE_ALL, + "Connection has reached idle timeout, releasing it\n"); + conn_cache->cached_connection = NULL; + sdap_id_release_conn_data(conn_data); + return; + } + + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = (idle_time == 0 ? now : idle_time) + idle_timeout; + DEBUG(SSSDBG_TRACE_ALL, + "Rescheduling connection idle timer to run at %"SPRItime"\n", tv.tv_sec); + + conn_data->idle_timer = + tevent_add_timer(conn_data->conn_cache->id_conn->id_ctx->be->ev, + conn_data, tv, + sdap_id_conn_data_idle_handler, + conn_data); + if (!conn_data->idle_timer) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_id_conn_data_idle_handler() failed to reschedule connection idle timer"); + } +} + +/* Mark connection as not idle */ +static void sdap_id_conn_data_not_idle(struct sdap_id_conn_data *conn_data) +{ + if (conn_data && conn_data->sh) { + DEBUG(SSSDBG_TRACE_ALL, "Marking connection as not idle\n"); + conn_data->sh->idle_time = 0; + } +} + +/* Mark connection as idle */ +static void sdap_id_conn_data_idle(struct sdap_id_conn_data *conn_data) +{ + if (conn_data && conn_data->sh) { + DEBUG(SSSDBG_TRACE_ALL, "Marking connection as idle\n"); + conn_data->sh->idle_time = time(NULL); + } +} + +/* Create an operation object */ +struct sdap_id_op *sdap_id_op_create(TALLOC_CTX *memctx, struct sdap_id_conn_cache *conn_cache) +{ + struct sdap_id_op *op = talloc_zero(memctx, struct sdap_id_op); + if (!op) { + return NULL; + } + + op->conn_cache = conn_cache; + + /* Remember the current chain id so we can use it when connection is + * established. This is required since the connection might be done + * by other request that was called before. */ + op->chain_id = sss_chain_id_get(); + + talloc_set_destructor((void*)op, sdap_id_op_destroy); + return op; +} + +/* Attach/detach connection to sdap_id_op */ +static void sdap_id_op_hook_conn_data(struct sdap_id_op *op, struct sdap_id_conn_data *conn_data) +{ + struct sdap_id_conn_data *current; + + if (!op) { + DEBUG(SSSDBG_FATAL_FAILURE, "NULL op passed!!!\n"); + return; + } + + current = op->conn_data; + if (conn_data == current) { + return; + } + + if (current) { + DLIST_REMOVE(current->ops, op); + } + + op->conn_data = conn_data; + + if (conn_data) { + sdap_id_conn_data_not_idle(conn_data); + DLIST_ADD_END(conn_data->ops, op, struct sdap_id_op*); + } + + if (current && !current->ops) { + if (current == current->conn_cache->cached_connection) { + sdap_id_conn_data_idle(current); + } else { + sdap_id_release_conn_data(current); + } + } +} + +/* Destructor for sdap_id_op */ +static int sdap_id_op_destroy(void *pvt) +{ + struct sdap_id_op *op = talloc_get_type(pvt, struct sdap_id_op); + + if (op->conn_data) { + DEBUG(SSSDBG_TRACE_ALL, "releasing operation connection\n"); + sdap_id_op_hook_conn_data(op, NULL); + } + + return 0; +} + +/* Check whether retry with reconnect can be performed for the operation */ +static bool sdap_id_op_can_reconnect(struct sdap_id_op *op) +{ + /* we allow 2 retries for failover server configured: + * - one for connection broken during request execution + * - one for the following (probably failed) reconnect attempt */ + int max_retries; + int count; + + count = be_fo_get_server_count(op->conn_cache->id_conn->id_ctx->be, + op->conn_cache->id_conn->service->name); + max_retries = 2 * count -1; + if (max_retries < 1) { + max_retries = 1; + } + + return op->reconnect_retry_count < max_retries; +} + +/* state of connect request */ +struct sdap_id_op_connect_state { + struct sdap_id_conn_ctx *id_conn; + struct tevent_context *ev; + struct sdap_id_op *op; + int dp_error; + int result; +}; + +/* Destructor for operation connection request */ +static int sdap_id_op_connect_state_destroy(void *pvt) +{ + struct sdap_id_op_connect_state *state = talloc_get_type(pvt, + struct sdap_id_op_connect_state); + if (state->op != NULL) { + /* clear destroyed connection request */ + state->op->connect_req = NULL; + } + + return 0; +} + +/* Begin to connect to LDAP server */ +struct tevent_req *sdap_id_op_connect_send(struct sdap_id_op *op, + TALLOC_CTX *memctx, + int *ret_out) +{ + struct tevent_req *req = NULL; + struct sdap_id_op_connect_state *state; + int ret = EOK; + + if (!memctx) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: no memory context passed.\n"); + ret = EINVAL; + goto done; + } + + if (op->connect_req) { + /* Connection already in progress, invalid operation */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Bug: connection request is already running or completed and leaked.\n"); + ret = EINVAL; + goto done; + } + + req = tevent_req_create(memctx, &state, struct sdap_id_op_connect_state); + if (!req) { + ret = ENOMEM; + goto done; + } + + talloc_set_destructor((void*)state, sdap_id_op_connect_state_destroy); + + state->id_conn = op->conn_cache->id_conn; + state->ev = state->id_conn->id_ctx->be->ev; + state->op = op; + op->connect_req = req; + + if (op->conn_data) { + /* If the operation is already connected, + * reuse existing connection regardless of its status */ + DEBUG(SSSDBG_TRACE_ALL, "reusing operation connection\n"); + ret = EOK; + goto done; + } + + ret = sdap_id_op_connect_step(req); + if (ret != EOK) { + goto done; + } + +done: + if (ret != EOK) { + talloc_zfree(req); + } else if (op->conn_data && !op->conn_data->connect_req) { + /* Connection is already established */ + tevent_req_done(req); + tevent_req_post(req, state->ev); + } + + if (ret_out) { + *ret_out = ret; + } + + return req; +} + +/* Begin a connection retry to LDAP server */ +static int sdap_id_op_connect_step(struct tevent_req *req) +{ + struct sdap_id_op_connect_state *state = + tevent_req_data(req, struct sdap_id_op_connect_state); + struct sdap_id_op *op = state->op; + struct sdap_id_conn_cache *conn_cache = op->conn_cache; + + int ret = EOK; + struct sdap_id_conn_data *conn_data; + struct tevent_req *subreq = NULL; + + /* Try to reuse context cached connection */ + conn_data = conn_cache->cached_connection; + if (conn_data) { + if (conn_data->connect_req) { + DEBUG(SSSDBG_TRACE_ALL, "waiting for connection to complete\n"); + sdap_id_op_hook_conn_data(op, conn_data); + goto done; + } + + if (sdap_can_reuse_connection(conn_data)) { + DEBUG(SSSDBG_TRACE_ALL, "reusing cached connection\n"); + sdap_id_op_hook_conn_data(op, conn_data); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "releasing expired cached connection\n"); + conn_cache->cached_connection = NULL; + sdap_id_release_conn_data(conn_data); + } + + DEBUG(SSSDBG_TRACE_ALL, "beginning to connect\n"); + + conn_data = talloc_zero(conn_cache, struct sdap_id_conn_data); + if (!conn_data) { + ret = ENOMEM; + goto done; + } + + talloc_set_destructor(conn_data, sdap_id_conn_data_destroy); + + conn_data->conn_cache = conn_cache; + subreq = sdap_cli_connect_send(conn_data, state->ev, + state->id_conn->id_ctx->opts, + state->id_conn->id_ctx->be, + state->id_conn->service, false, + CON_TLS_DFL, false); + + if (!subreq) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_id_op_connect_done, conn_data); + conn_data->connect_req = subreq; + + DLIST_ADD(conn_cache->connections, conn_data); + conn_cache->cached_connection = conn_data; + + sdap_id_op_hook_conn_data(op, conn_data); + +done: + if (ret != EOK && conn_data) { + sdap_id_release_conn_data(conn_data); + } + + if (ret != EOK) { + talloc_zfree(subreq); + } + + return ret; +} + +static void sdap_id_op_connect_reinit_done(struct tevent_req *req); + +/* Subrequest callback for connection completion */ +static void sdap_id_op_connect_done(struct tevent_req *subreq) +{ + struct sdap_id_conn_data *conn_data = + tevent_req_callback_data(subreq, struct sdap_id_conn_data); + struct sdap_id_conn_cache *conn_cache = conn_data->conn_cache; + struct sdap_server_opts *srv_opts = NULL; + struct sdap_server_opts *current_srv_opts = NULL; + bool can_retry = false; + bool is_offline = false; + struct tevent_req *reinit_req = NULL; + bool reinit = false; + int ret; + int ret_nonfatal; + + ret = sdap_cli_connect_recv(subreq, conn_data, &can_retry, + &conn_data->sh, &srv_opts); + conn_data->connect_req = NULL; + talloc_zfree(subreq); + + conn_data->notify_lock++; + + if (ret == ENOTSUP) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Authentication mechanism not Supported by server\n"); + } + + if (ret == EOK && (!conn_data->sh || !conn_data->sh->connected)) { + DEBUG(SSSDBG_FATAL_FAILURE, + "sdap_cli_connect_recv returned bogus connection\n"); + ret = EFAULT; + } + + if (ret != EOK && !can_retry) { + if (conn_cache->id_conn->ignore_mark_offline) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to connect to server, but ignore mark offline " + "is enabled.\n"); + } else { + /* be is going offline as there is no more servers to try */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect, going offline (%d [%s])\n", + ret, strerror(ret)); + is_offline = true; + be_mark_offline(conn_cache->id_conn->id_ctx->be); + } + } + + if (ret == EOK) { + current_srv_opts = conn_cache->id_conn->id_ctx->srv_opts; + if (current_srv_opts) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Old USN: %lu, New USN: %lu\n", current_srv_opts->last_usn, srv_opts->last_usn); + + if (strcmp(srv_opts->server_id, current_srv_opts->server_id) == 0 + && srv_opts->supports_usn + && current_srv_opts->last_usn > srv_opts->last_usn) { + DEBUG(SSSDBG_FUNC_DATA, "Server was probably re-initialized\n"); + + current_srv_opts->max_user_value = 0; + current_srv_opts->max_group_value = 0; + current_srv_opts->max_service_value = 0; + current_srv_opts->max_sudo_value = 0; + current_srv_opts->max_iphost_value = 0; + current_srv_opts->max_ipnetwork_value = 0; + current_srv_opts->last_usn = srv_opts->last_usn; + + reinit = true; + } + } + ret_nonfatal = sdap_id_conn_data_set_expire_timer(conn_data); + if (ret_nonfatal != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_id_conn_data_set_expire_timer() failed [%d]: %s", + ret_nonfatal, sss_strerror(ret_nonfatal)); + } + ret_nonfatal = sdap_id_conn_data_start_idle_timer(conn_data); + if (ret_nonfatal != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_id_conn_data_start_idle_timer() failed [%d]: %s", + ret_nonfatal, sss_strerror(ret_nonfatal)); + } + sdap_steal_server_opts(conn_cache->id_conn->id_ctx, &srv_opts); + } + + if (can_retry) { + switch (ret) { + case EOK: + case ENOTSUP: + case EACCES: + case EIO: + case EFAULT: + case ETIMEDOUT: + case ERR_AUTH_FAILED: + break; + + default: + /* do not attempt to retry on errors like ENOMEM */ + DEBUG(SSSDBG_TRACE_FUNC, + "Marking the backend \"%s\" offline [%d]: %s\n", + conn_cache->id_conn->id_ctx->be->domain->name, + ret, sss_strerror(ret)); + can_retry = false; + is_offline = true; + be_mark_offline(conn_cache->id_conn->id_ctx->be); + break; + } + } + + int notify_count = 0; + + /* Notify about connection */ + for(;;) { + struct sdap_id_op *op; + + if (ret == EOK && !conn_data->sh->connected) { + DEBUG(SSSDBG_TRACE_ALL, + "connection was broken after %d notifies\n", notify_count); + } + + DLIST_FOR_EACH(op, conn_data->ops) { + if (op->connect_req) { + break; + } + } + + if (!op) { + break; + } + + /* another operation to notify */ + notify_count++; + + if (ret != EOK || !conn_data->sh->connected) { + /* failed to connect or connection got broken during notify */ + bool retry = false; + + /* drop connection from cache now */ + if (conn_cache->cached_connection == conn_data) { + conn_cache->cached_connection = NULL; + } + + if (can_retry) { + /* determining whether retry is possible */ + if (be_is_offline(conn_cache->id_conn->id_ctx->be)) { + /* be is offline, no retry possible */ + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "skipping automatic retry on op #%d as be is offline\n", notify_count); + ret = EIO; + } + + can_retry = false; + is_offline = true; + } else { + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "attempting automatic retry on op #%d\n", notify_count); + retry = true; + } else if (sdap_id_op_can_reconnect(op)) { + DEBUG(SSSDBG_TRACE_ALL, + "attempting failover retry on op #%d\n", notify_count); + op->reconnect_retry_count++; + retry = true; + } + } + } + + if (retry && op->connect_req) { + int retry_ret = sdap_id_op_connect_step(op->connect_req); + if (retry_ret != EOK) { + can_retry = false; + sdap_id_op_connect_req_complete(op, DP_ERR_FATAL, retry_ret); + } + + continue; + } + } + + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "notify connected to op #%d\n", notify_count); + sdap_id_op_connect_req_complete(op, DP_ERR_OK, ret); + } else if (is_offline) { + DEBUG(SSSDBG_TRACE_ALL, "notify offline to op #%d\n", notify_count); + sdap_id_op_connect_req_complete(op, DP_ERR_OFFLINE, EAGAIN); + } else { + DEBUG(SSSDBG_TRACE_ALL, + "notify error to op #%d: %d [%s]\n", notify_count, ret, strerror(ret)); + sdap_id_op_connect_req_complete(op, DP_ERR_FATAL, ret); + } + } + + /* all operations notified */ + if (conn_data->notify_lock > 0) { + conn_data->notify_lock--; + } + + if ((ret == EOK) + && conn_data->sh->connected + && !be_is_offline(conn_cache->id_conn->id_ctx->be)) { + DEBUG(SSSDBG_TRACE_ALL, + "caching successful connection after %d notifies\n", notify_count); + conn_cache->cached_connection = conn_data; + + /* Run any post-connection routines */ + be_run_unconditional_online_cb(conn_cache->id_conn->id_ctx->be); + be_run_online_cb(conn_cache->id_conn->id_ctx->be); + + } else { + if (conn_cache->cached_connection == conn_data) { + conn_cache->cached_connection = NULL; + } + + sdap_id_release_conn_data(conn_data); + } + + if (reinit) { + DEBUG(SSSDBG_TRACE_FUNC, "Server reinitialization detected. " + "Cleaning cache.\n"); + reinit_req = sdap_reinit_cleanup_send(conn_cache->id_conn->id_ctx->be, + conn_cache->id_conn->id_ctx->be, + conn_cache->id_conn->id_ctx); + if (reinit_req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to perform reinitialization " + "clean up.\n"); + return; + } + + tevent_req_set_callback(reinit_req, sdap_id_op_connect_reinit_done, + NULL); + } +} + +static void sdap_id_op_connect_reinit_done(struct tevent_req *req) +{ + errno_t ret; + + ret = sdap_reinit_cleanup_recv(req); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to perform reinitialization " + "clean up [%d]: %s\n", ret, strerror(ret)); + /* not fatal */ + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Reinitialization clean up completed\n"); +} + +/* Mark operation connection request as complete */ +static void sdap_id_op_connect_req_complete(struct sdap_id_op *op, int dp_error, int ret) +{ + struct tevent_req *req = op->connect_req; + struct sdap_id_op_connect_state *state; + uint64_t old_chain_id; + + if (!req) { + return; + } + + op->connect_req = NULL; + + state = tevent_req_data(req, struct sdap_id_op_connect_state); + state->dp_error = dp_error; + state->result = ret; + + /* Set the chain id to the one associated with this request. */ + old_chain_id = sss_chain_id_set(op->chain_id); + if (ret == EOK) { + tevent_req_done(req); + } else { + sdap_id_op_hook_conn_data(op, NULL); + tevent_req_error(req, ret); + } + sss_chain_id_set(old_chain_id); +} + +/* Get the result of an asynchronous connect operation on sdap_id_op + * + * In dp_error data provider error code is returned: + * DP_ERR_OK - connection established + * DP_ERR_OFFLINE - backend is offline, operation result is set EAGAIN + * DP_ERR_FATAL - operation failed + */ +int sdap_id_op_connect_recv(struct tevent_req *req, int *dp_error) +{ + struct sdap_id_op_connect_state *state = tevent_req_data(req, + struct sdap_id_op_connect_state); + + *dp_error = state->dp_error; + return state->result; +} + +/* Report completion of LDAP operation and release associated connection. + * Returns operation result (possible updated) passed in ret parameter. + * + * In dp_error data provider error code is returned: + * DP_ERR_OK (operation result = EOK) - operation completed + * DP_ERR_OK (operation result != EOK) - operation can be retried + * DP_ERR_OFFLINE - backend is offline, operation result is set EAGAIN + * DP_ERR_FATAL - operation failed */ +int sdap_id_op_done(struct sdap_id_op *op, int retval, int *dp_err_out) +{ + bool communication_error; + struct sdap_id_conn_data *current_conn = op->conn_data; + switch (retval) { + case EIO: + case ETIMEDOUT: + /* this currently the only possible communication error after connection is established */ + communication_error = true; + break; + + default: + communication_error = false; + break; + } + + if (communication_error && current_conn != 0 + && current_conn == op->conn_cache->cached_connection) { + /* do not reuse failed connection */ + op->conn_cache->cached_connection = NULL; + + DEBUG(SSSDBG_FUNC_DATA, + "communication error on cached connection, moving to next server\n"); + be_fo_try_next_server(op->conn_cache->id_conn->id_ctx->be, + op->conn_cache->id_conn->service->name); + } + + int dp_err; + if (retval == EOK) { + dp_err = DP_ERR_OK; + } else if (be_is_offline(op->conn_cache->id_conn->id_ctx->be)) { + /* if backend is already offline, just report offline, do not duplicate errors */ + dp_err = DP_ERR_OFFLINE; + retval = EAGAIN; + DEBUG(SSSDBG_TRACE_ALL, "falling back to offline data...\n"); + } else if (communication_error) { + /* communication error, can try to reconnect */ + + if (!sdap_id_op_can_reconnect(op)) { + dp_err = DP_ERR_FATAL; + DEBUG(SSSDBG_TRACE_ALL, + "too many communication failures, giving up...\n"); + } else { + dp_err = DP_ERR_OK; + retval = EAGAIN; + } + } else { + dp_err = DP_ERR_FATAL; + } + + if (dp_err == DP_ERR_OK && retval != EOK) { + /* reconnect retry */ + op->reconnect_retry_count++; + DEBUG(SSSDBG_TRACE_ALL, + "advising for connection retry #%i\n", op->reconnect_retry_count); + } else { + /* end of request */ + op->reconnect_retry_count = 0; + } + + if (current_conn) { + DEBUG(SSSDBG_TRACE_ALL, "releasing operation connection\n"); + sdap_id_op_hook_conn_data(op, NULL); + } + + *dp_err_out = dp_err; + return retval; +} + +/* Get SDAP handle associated with operation by sdap_id_op_connect */ +struct sdap_handle *sdap_id_op_handle(struct sdap_id_op *op) +{ + return op && op->conn_data ? op->conn_data->sh : NULL; +} diff --git a/src/providers/ldap/sdap_id_op.h b/src/providers/ldap/sdap_id_op.h new file mode 100644 index 0000000..f7f230a --- /dev/null +++ b/src/providers/ldap/sdap_id_op.h @@ -0,0 +1,76 @@ +/* + SSSD + + LDAP ID backend operation retry logic and connection cache + + Authors: + Eugene Indenbom + + Copyright (C) 2008-2010 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_ID_OP_H_ +#define _SDAP_ID_OP_H_ + +struct sdap_id_ctx; +struct sdap_id_conn_ctx; + +/* LDAP async connection cache */ +struct sdap_id_conn_cache; + +/* LDAP async operation tracker: + * - keeps track of connection usage + * - keeps track of operation retries */ +struct sdap_id_op; + +/* Create a connection cache */ +int sdap_id_conn_cache_create(TALLOC_CTX *memctx, + struct sdap_id_conn_ctx *id_conn, + struct sdap_id_conn_cache** conn_cache_out); + +/* Create an operation object */ +struct sdap_id_op *sdap_id_op_create(TALLOC_CTX *memctx, struct sdap_id_conn_cache *cache); + +/* Begin to connect to LDAP server. */ +struct tevent_req *sdap_id_op_connect_send(struct sdap_id_op *op, + TALLOC_CTX *memctx, + int *ret_out); + +/* Get the result of an asynchronous connect operation on sdap_id_op + * + * In dp_error data provider error code is returned: + * DP_ERR_OK - connection established + * DP_ERR_OFFLINE - backend is offline, operation result is set EAGAIN + * DP_ERR_FATAL - operation failed + */ +int sdap_id_op_connect_recv(struct tevent_req *req, int *dp_error); + +/* Report completion of LDAP operation and release associated connection. + * Returns operation result (possible updated) passed in ret parameter. + * + * In dp_error data provider error code is returned: + * DP_ERR_OK (operation result = EOK) - operation completed + * DP_ERR_OK (operation result != EOK) - operation can be retried + * DP_ERR_OFFLINE - backend is offline, operation result is set EAGAIN + * DP_ERR_FATAL - operation failed */ +int sdap_id_op_done(struct sdap_id_op*, int ret, int *dp_error); + +/* Get SDAP handle associated with operation by sdap_id_op_connect */ +struct sdap_handle *sdap_id_op_handle(struct sdap_id_op *op); +/* Get root DSE entry of connected LDAP server */ +const struct sysdb_attrs *sdap_id_op_rootDSE(struct sdap_id_op *op); + +#endif /* _SDAP_ID_OP_H_ */ diff --git a/src/providers/ldap/sdap_idmap.c b/src/providers/ldap/sdap_idmap.c new file mode 100644 index 0000000..3795ed6 --- /dev/null +++ b/src/providers/ldap/sdap_idmap.c @@ -0,0 +1,629 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "util/dlinklist.h" +#include "providers/ldap/sdap_idmap.h" +#include "util/util_sss_idmap.h" + +static errno_t +sdap_idmap_get_configured_external_range(struct sdap_idmap_ctx *idmap_ctx, + struct sss_idmap_range *range) +{ + int int_id; + struct sdap_id_ctx *id_ctx; + uint32_t min; + uint32_t max; + + if (idmap_ctx == NULL) { + return EINVAL; + } + + id_ctx = idmap_ctx->id_ctx; + + int_id = dp_opt_get_int(id_ctx->opts->basic, SDAP_MIN_ID); + if (int_id < 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "ldap_min_id must be greater than 0.\n"); + return EINVAL; + } + min = int_id; + + int_id = dp_opt_get_int(id_ctx->opts->basic, SDAP_MAX_ID); + if (int_id < 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "ldap_max_id must be greater than 0.\n"); + return EINVAL; + } + max = int_id; + + if ((min == 0 && max != 0) || (min != 0 && max == 0)) { + DEBUG(SSSDBG_CONF_SETTINGS, "Both ldap_min_id and ldap_max_id " \ + "either must be 0 (not set) " \ + "or positive integers.\n"); + return EINVAL; + } + + if (min == 0 && max == 0) { + /* ldap_min_id and ldap_max_id not set, using min_id and max_id */ + min = id_ctx->be->domain->id_min; + max = id_ctx->be->domain->id_max; + if (max == 0) { + max = UINT32_MAX; + } + } + + range->min = min; + range->max =max; + + return EOK; +} + +static errno_t +sdap_idmap_add_configured_external_range(struct sdap_idmap_ctx *idmap_ctx) +{ + int ret; + struct sss_idmap_range range; + struct sdap_id_ctx *id_ctx; + enum idmap_error_code err; + + ret = sdap_idmap_get_configured_external_range(idmap_ctx, &range); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_idmap_get_configured_external_range failed.\n"); + return ret; + } + + id_ctx = idmap_ctx->id_ctx; + + err = sss_idmap_add_auto_domain_ex(idmap_ctx->map, + id_ctx->be->domain->name, + id_ctx->be->domain->domain_id, &range, + NULL, 0, true, NULL, NULL); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add domain [%s] to the map: [%d]\n", + id_ctx->be->domain->name, err); + return EIO; + } + + return EOK; +} + +errno_t sdap_idmap_find_new_domain(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str) +{ + int ret; + + ret = sdap_idmap_add_domain(idmap_ctx, + dom_name, dom_sid_str, + -1); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not add new domain [%s]\n", dom_name); + return ret; + } + + return EOK; +} + +errno_t +sdap_idmap_init(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_idmap_ctx **_idmap_ctx) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + enum idmap_error_code err; + size_t i; + struct ldb_result *res; + const char *dom_name; + const char *sid_str; + id_t slice_num; + id_t idmap_lower; + id_t idmap_upper; + id_t rangesize; + bool autorid_mode; + int extra_slice_init; + struct sdap_idmap_ctx *idmap_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + idmap_ctx = talloc_zero(tmp_ctx, struct sdap_idmap_ctx); + if (!idmap_ctx) { + ret = ENOMEM; + goto done; + } + idmap_ctx->id_ctx = id_ctx; + idmap_ctx->find_new_domain = sdap_idmap_find_new_domain; + + idmap_lower = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_LOWER); + idmap_upper = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_UPPER); + rangesize = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_RANGESIZE); + autorid_mode = dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_AUTORID_COMPAT); + extra_slice_init = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_EXTRA_SLICE_INIT); + + /* Validate that the values make sense */ + if (rangesize <= 0 + || idmap_upper <= idmap_lower + || (idmap_upper-idmap_lower) < rangesize) + { + DEBUG(SSSDBG_FATAL_FAILURE, + "Invalid settings for range selection: " + "[%"SPRIid"][%"SPRIid"][%"SPRIid"]\n", + idmap_lower, idmap_upper, rangesize); + ret = EINVAL; + goto done; + } + + if (((idmap_upper - idmap_lower) % rangesize) != 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Range size does not divide evenly. Uppermost range will " + "not be used\n"); + } + + /* Initialize the map */ + err = sss_idmap_init(sss_idmap_talloc, idmap_ctx, + sss_idmap_talloc_free, + &idmap_ctx->map); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not initialize the ID map: [%s]\n", + idmap_error_string(err)); + if (err == IDMAP_OUT_OF_MEMORY) { + ret = ENOMEM; + } else { + ret = EINVAL; + } + goto done; + } + + err = sss_idmap_ctx_set_autorid(idmap_ctx->map, autorid_mode); + err |= sss_idmap_ctx_set_lower(idmap_ctx->map, idmap_lower); + err |= sss_idmap_ctx_set_upper(idmap_ctx->map, idmap_upper); + err |= sss_idmap_ctx_set_rangesize(idmap_ctx->map, rangesize); + err |= sss_idmap_ctx_set_extra_slice_init(idmap_ctx->map, extra_slice_init); + if (err != IDMAP_SUCCESS) { + /* This should never happen */ + DEBUG(SSSDBG_CRIT_FAILURE, "sss_idmap_ctx corrupted\n"); + ret = EIO; + goto done; + } + + + /* Setup range for externally managed IDs, i.e. IDs are read from the + * ldap_user_uid_number and ldap_group_gid_number attributes. */ + if (!dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic, SDAP_ID_MAPPING)) { + ret = sdap_idmap_add_configured_external_range(idmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_idmap_add_configured_external_range failed.\n"); + goto done; + } + } + + /* Read in any existing mappings from the cache */ + ret = sysdb_idmap_get_mappings(tmp_ctx, id_ctx->be->domain, &res); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not read ID mappings from the cache: [%s]\n", + strerror(ret)); + goto done; + } + + if (ret == EOK) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Initializing [%d] domains for ID-mapping\n", res->count); + + for (i = 0; i < res->count; i++) { + dom_name = ldb_msg_find_attr_as_string(res->msgs[i], + SYSDB_NAME, + NULL); + if (!dom_name) { + /* This should never happen */ + ret = EINVAL; + goto done; + } + + sid_str = ldb_msg_find_attr_as_string(res->msgs[i], + SYSDB_IDMAP_SID_ATTR, + NULL); + if (!sid_str) { + /* This should never happen */ + ret = EINVAL; + goto done; + } + + slice_num = ldb_msg_find_attr_as_int(res->msgs[i], + SYSDB_IDMAP_SLICE_ATTR, + -1); + if (slice_num == -1) { + /* This should never happen */ + ret = EINVAL; + goto done; + } + + ret = sdap_idmap_add_domain(idmap_ctx, dom_name, + sid_str, slice_num); + if (ret != EOK) { + if (ret == EINVAL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add domain [%s][%s][%"SPRIid"] " + "to ID map: [%s] " + "Unexpected ID map configuration. Check ID map related " + "parameters in sssd.conf and remove the sssd cache if " + "some of these parameters were changed recently.\n", + dom_name, sid_str, slice_num, strerror(ret)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add domain [%s][%s][%"SPRIid"] " + "to ID map: [%s]\n", + dom_name, sid_str, slice_num, strerror(ret)); + } + + goto done; + } + } + } else { + /* This is the first time we're setting up id-mapping + * Store the default domain as slice 0 + */ + dom_name = dp_opt_get_string(idmap_ctx->id_ctx->opts->basic, SDAP_IDMAP_DEFAULT_DOMAIN); + if (!dom_name) { + /* If it's not explicitly specified, use the SSSD domain name */ + dom_name = idmap_ctx->id_ctx->be->domain->name; + ret = dp_opt_set_string(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_DEFAULT_DOMAIN, + dom_name); + if (ret != EOK) goto done; + } + + sid_str = dp_opt_get_string(idmap_ctx->id_ctx->opts->basic, SDAP_IDMAP_DEFAULT_DOMAIN_SID); + if (sid_str) { + struct sss_domain_info *domain = idmap_ctx->id_ctx->be->domain; + domain->domain_id = talloc_strdup(domain, sid_str); + if (domain->domain_id == NULL) { + ret = ENOMEM; + goto done; + } + + /* Set the default domain as slice 0 */ + ret = sdap_idmap_add_domain(idmap_ctx, dom_name, + sid_str, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add domain [%s][%s][%u] to ID map: [%s]\n", + dom_name, sid_str, 0, strerror(ret)); + goto done; + } + } else { + if (dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic, SDAP_IDMAP_AUTORID_COMPAT)) { + /* In autorid compatibility mode, we MUST have a slice 0 */ + DEBUG(SSSDBG_CRIT_FAILURE, + "WARNING: Autorid compatibility mode selected, " + "but %s is not set. UID/GID values may differ " + "between clients.\n", + idmap_ctx->id_ctx->opts->basic[SDAP_IDMAP_DEFAULT_DOMAIN_SID].opt_name); + } + /* Otherwise, we'll just fall back to hash values as they are seen */ + } + } + + *_idmap_ctx = talloc_steal(mem_ctx, idmap_ctx); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +sdap_idmap_add_domain(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid, + id_t slice) +{ + errno_t ret; + struct sss_idmap_range range; + enum idmap_error_code err; + id_t idmap_upper; + bool external_mapping = true; + + ret = sss_idmap_ctx_get_upper(idmap_ctx->map, &idmap_upper); + if (ret != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get upper bound of available ID range.\n"); + ret = EIO; + goto done; + } + + if (dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic, SDAP_ID_MAPPING)) { + external_mapping = false; + ret = sss_idmap_calculate_range(idmap_ctx->map, dom_sid, &slice, &range); + if (ret != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to calculate range for domain [%s]: [%d]\n", dom_name, + ret); + ret = EIO; + goto done; + } + DEBUG(SSSDBG_TRACE_LIBS, + "Adding domain [%s] as slice [%"SPRIid"]\n", dom_sid, slice); + + if (range.max > idmap_upper) { + /* This should never happen */ + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: Range maximum exceeds the global maximum: " + "%u > %"SPRIid"\n", range.max, idmap_upper); + ret = EINVAL; + goto done; + } + } else { + ret = sdap_idmap_get_configured_external_range(idmap_ctx, &range); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_idmap_get_configured_external_range failed.\n"); + return ret; + } + } + + /* Add this domain to the map */ + err = sss_idmap_add_auto_domain_ex(idmap_ctx->map, dom_name, dom_sid, + &range, NULL, 0, external_mapping, + NULL, NULL); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add domain [%s] to the map: [%d]\n", + dom_name, err); + ret = EIO; + goto done; + } + + /* If algorithmic mapping is used add this domain to the SYSDB cache so it + * will survive reboot */ + if (!external_mapping) { + ret = sysdb_idmap_store_mapping(idmap_ctx->id_ctx->be->domain, + dom_name, dom_sid, + slice); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_idmap_store_mapping failed.\n"); + goto done; + } + } + +done: + return ret; +} + +errno_t +sdap_idmap_get_dom_sid_from_object(TALLOC_CTX *mem_ctx, + const char *object_sid, + char **dom_sid_str) +{ + const char *p; + long long a; + size_t c; + char *endptr; + + if (object_sid == NULL + || strncmp(object_sid, DOM_SID_PREFIX, DOM_SID_PREFIX_LEN) != 0) { + return EINVAL; + } + + p = object_sid + DOM_SID_PREFIX_LEN; + c = 0; + + do { + errno = 0; + a = strtoull(p, &endptr, 10); + if (errno != 0 || a > UINT32_MAX) { + return EINVAL; + } + + if (*endptr == '-') { + p = endptr + 1; + } else { + return EINVAL; + } + c++; + } while(c < 3); + + /* If we made it here, we are now one character past + * the last hyphen in the object-sid. + * Copy the dom-sid substring. + */ + *dom_sid_str = talloc_strndup(mem_ctx, object_sid, + (endptr-object_sid)); + if (!*dom_sid_str) return ENOMEM; + + return EOK; +} + +errno_t +sdap_idmap_sid_to_unix(struct sdap_idmap_ctx *idmap_ctx, + const char *sid_str, + id_t *id) +{ + errno_t ret; + enum idmap_error_code err; + char *dom_sid_str = NULL; + + /* Convert the SID into a UNIX ID */ + err = sss_idmap_sid_to_unix(idmap_ctx->map, + sid_str, + (uint32_t *)id); + switch (err) { + case IDMAP_SUCCESS: + break; + case IDMAP_NO_DOMAIN: + /* This is the first time we've seen this domain + * Create a new domain for it. We'll use the dom-sid + * as the domain name for now, since we don't have + * any way to get the real name. + */ + ret = sdap_idmap_get_dom_sid_from_object(NULL, sid_str, + &dom_sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not parse domain SID from [%s]\n", sid_str); + goto done; + } + + ret = idmap_ctx->find_new_domain(idmap_ctx, dom_sid_str, dom_sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not add new domain for sid [%s]\n", sid_str); + goto done; + } + + /* Now try converting to a UNIX ID again */ + err = sss_idmap_sid_to_unix(idmap_ctx->map, + sid_str, + (uint32_t *)id); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert objectSID [%s] to a UNIX ID\n", + sid_str); + ret = EIO; + goto done; + } + break; + case IDMAP_BUILTIN_SID: + DEBUG(SSSDBG_TRACE_FUNC, + "Object SID [%s] is a built-in one.\n", sid_str); + /* ENOTSUP indicates built-in SID */ + ret = ENOTSUP; + goto done; + break; + case IDMAP_NO_RANGE: + DEBUG(SSSDBG_IMPORTANT_INFO, + "Object SID [%s] has a RID that is larger than the " + "ldap_idmap_range_size. See the \"ID MAPPING\" section of " + "sssd-ad(5) for an explanation of how to resolve this issue.\n", + sid_str); + /* Fall through intentionally */ + SSS_ATTRIBUTE_FALLTHROUGH; + default: + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert objectSID [%s] to a UNIX ID\n", + sid_str); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + talloc_free(dom_sid_str); + return ret; +} + +bool sdap_idmap_domain_has_algorithmic_mapping(struct sdap_idmap_ctx *ctx, + const char *dom_name, + const char *dom_sid) +{ + enum idmap_error_code err; + bool has_algorithmic_mapping; + char *new_dom_sid; + int ret; + TALLOC_CTX *tmp_ctx = NULL; + + if (dp_opt_get_bool(ctx->id_ctx->opts->basic, SDAP_ID_MAPPING) + && dp_target_enabled(ctx->id_ctx->be->provider, "ldap", DPT_ID)) { + return true; + } + + err = sss_idmap_domain_has_algorithmic_mapping(ctx->map, dom_sid, + &has_algorithmic_mapping); + switch (err){ + case IDMAP_SUCCESS: + return has_algorithmic_mapping; + case IDMAP_SID_INVALID: /* FALLTHROUGH */ + case IDMAP_SID_UNKNOWN: /* FALLTHROUGH */ + case IDMAP_NO_DOMAIN: /* FALLTHROUGH */ + /* continue with idmap_domain_by_name */ + break; + default: + return false; + } + + err = sss_idmap_domain_by_name_has_algorithmic_mapping(ctx->map, + dom_name, + &has_algorithmic_mapping); + if (err == IDMAP_SUCCESS) { + return has_algorithmic_mapping; + } else if (err != IDMAP_NAME_UNKNOWN && err != IDMAP_NO_DOMAIN) { + return false; + } + + /* If there is no SID, e.g. IPA without enabled trust support, we cannot + * have algorithmic mapping */ + if (dom_sid == NULL) { + return false; + } + + /* This is the first time we've seen this domain + * Create a new domain for it. We'll use the dom-sid + * as the domain name for now, since we don't have + * any way to get the real name. + */ + + if (is_domain_sid(dom_sid)) { + new_dom_sid = discard_const(dom_sid); + } else { + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return false; + } + + ret = sdap_idmap_get_dom_sid_from_object(tmp_ctx, dom_sid, + &new_dom_sid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not parse domain SID from [%s]\n", dom_sid); + talloc_free(tmp_ctx); + return false; + } + } + + ret = ctx->find_new_domain(ctx, dom_name, new_dom_sid); + talloc_free(tmp_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not add new domain for sid [%s]\n", dom_sid); + return false; + } + + err = sss_idmap_domain_has_algorithmic_mapping(ctx->map, dom_sid, + &has_algorithmic_mapping); + if (err == IDMAP_SUCCESS) { + return has_algorithmic_mapping; + } + + return false; +} diff --git a/src/providers/ldap/sdap_idmap.h b/src/providers/ldap/sdap_idmap.h new file mode 100644 index 0000000..07499dc --- /dev/null +++ b/src/providers/ldap/sdap_idmap.h @@ -0,0 +1,63 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef SDAP_IDMAP_H_ +#define SDAP_IDMAP_H_ + +#include "src/providers/ldap/sdap.h" +#include "src/providers/ldap/ldap_common.h" + +typedef errno_t (find_new_domain_fn_t)(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str); +struct sdap_idmap_ctx { + struct sss_idmap_ctx *map; + + struct sdap_id_ctx *id_ctx; + find_new_domain_fn_t *find_new_domain; +}; + +errno_t sdap_idmap_init(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_idmap_ctx **_idmap_ctx); + +errno_t +sdap_idmap_add_domain(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid, + id_t slice); + +errno_t +sdap_idmap_get_dom_sid_from_object(TALLOC_CTX *mem_ctx, + const char *object_sid, + char **dom_sid_str); + +errno_t +sdap_idmap_sid_to_unix(struct sdap_idmap_ctx *idmap_ctx, + const char *sid_str, + id_t *id); + +bool sdap_idmap_domain_has_algorithmic_mapping(struct sdap_idmap_ctx *ctx, + const char *name, + const char *dom_sid); + +#endif /* SDAP_IDMAP_H_ */ diff --git a/src/providers/ldap/sdap_iphost.c b/src/providers/ldap/sdap_iphost.c new file mode 100644 index 0000000..79c707b --- /dev/null +++ b/src/providers/ldap/sdap_iphost.c @@ -0,0 +1,375 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "db/sysdb_iphosts.h" + +struct sdap_ip_host_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct sdap_id_conn_ctx *conn; + + uint32_t filter_type; + const char *filter_value; + + char *filter; + const char **attrs; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static errno_t +sdap_ip_host_get_retry(struct tevent_req *req); + +static struct tevent_req * +sdap_iphost_get_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + uint32_t filter_type, + const char *filter_value, + bool noexist_delete) +{ + struct tevent_req *req; + struct sdap_ip_host_get_state *state; + const char *attr_name; + char *clean_filter_value; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_ip_host_get_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->id_ctx = id_ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->filter_value = filter_value; + state->filter_type = filter_type; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (state->op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + switch(filter_type) { + case BE_FILTER_NAME: + attr_name = id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NAME].name; + break; + case BE_FILTER_ADDR: + attr_name = id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NUMBER].name; + break; + default: + ret = EINVAL; + goto fail; + } + + ret = sss_filter_sanitize(state, filter_value, &clean_filter_value); + if (ret != EOK) { + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(objectclass=%s)(%s=%s))", + id_ctx->opts->iphost_map[SDAP_OC_IPHOST].name, + attr_name, clean_filter_value); + talloc_zfree(clean_filter_value); + if (state->filter == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to build the base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, id_ctx->opts->iphost_map, + SDAP_OPTS_IPHOST, NULL, &state->attrs, NULL); + if (ret != EOK) { + goto fail; + } + + ret = sdap_ip_host_get_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +sdap_ip_host_get_connect_done(struct tevent_req *subreq); + +static errno_t +sdap_ip_host_get_retry(struct tevent_req *req) +{ + struct sdap_ip_host_get_state *state; + struct tevent_req *subreq; + errno_t ret = EOK; + + state = tevent_req_data(req, struct sdap_ip_host_get_state); + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + return ret; + } + + tevent_req_set_callback(subreq, sdap_ip_host_get_connect_done, req); + + return EOK; +} + +static void +sdap_ip_host_get_done(struct tevent_req *subreq); + +static void +sdap_ip_host_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_ip_host_get_state *state; + int dp_error = DP_ERR_FATAL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ip_host_get_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_iphost_send(state, state->ev, + state->domain, state->sysdb, + state->id_ctx->opts, + state->sdom->iphost_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_ip_host_get_done, req); +} + +static void +sdap_ip_host_get_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_ip_host_get_state *state; + int dp_error = DP_ERR_FATAL; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ip_host_get_state); + + ret = sdap_get_iphost_recv(NULL, subreq, NULL); + talloc_zfree(subreq); + + /* Check whether we need to try again with another + * failover server. */ + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_ip_host_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Return to the mainloop to retry */ + return; + } + state->sdap_ret = ret; + + /* An error occurred. */ + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT && state->noexist_delete == true) { + /* Ensure that this entry is removed from the sysdb */ + switch (state->filter_type) { + case BE_FILTER_NAME: + ret = sysdb_host_delete(state->domain, state->filter_value, + NULL); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + case BE_FILTER_ADDR: + ret = sysdb_host_delete(state->domain, NULL, + state->filter_value); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + default: + tevent_req_error(req, EINVAL); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +static errno_t +sdap_ip_host_get_recv(struct tevent_req *req, + int *dp_error_out, + int *sdap_ret) +{ + struct sdap_ip_host_get_state *state; + + state = tevent_req_data(req, struct sdap_ip_host_get_state); + + if (dp_error_out != NULL) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret != NULL) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_ip_host_handler_state { + struct dp_reply_std reply; +}; + +static void sdap_ip_host_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_iphost_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params) +{ + struct sdap_ip_host_handler_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ip_host_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (resolver_data->filter_type == BE_FILTER_ENUM) { + DEBUG(SSSDBG_TRACE_LIBS, "Skipping enumeration on demand\n"); + ret = EOK; + goto immediately; + } + + subreq = sdap_iphost_get_send(state, params->ev, + resolver_ctx->id_ctx, + resolver_ctx->id_ctx->opts->sdom, + resolver_ctx->id_ctx->conn, + resolver_data->filter_type, + resolver_data->filter_value, + true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_ip_host_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_ip_host_handler_done(struct tevent_req *subreq) +{ + struct sdap_ip_host_handler_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ip_host_handler_state); + + ret = sdap_ip_host_get_recv(subreq, &dp_error, NULL); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +errno_t +sdap_iphost_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_ip_host_handler_state *state; + + state = tevent_req_data(req, struct sdap_ip_host_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/ldap/sdap_ipnetwork.c b/src/providers/ldap/sdap_ipnetwork.c new file mode 100644 index 0000000..b78f50b --- /dev/null +++ b/src/providers/ldap/sdap_ipnetwork.c @@ -0,0 +1,378 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "db/sysdb_ipnetworks.h" + +struct sdap_ipnetwork_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct sdap_id_conn_ctx *conn; + + uint32_t filter_type; + const char *filter_value; + + char *filter; + const char **attrs; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static errno_t +sdap_ipnetwork_get_retry(struct tevent_req *req); + +static struct tevent_req * +sdap_ipnetwork_get_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + uint32_t filter_type, + const char *filter_value, + bool noexist_delete) +{ + struct tevent_req *req; + struct sdap_ipnetwork_get_state *state; + const char *attr_name; + char *clean_filter_value; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_ipnetwork_get_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->id_ctx = id_ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->filter_value = filter_value; + state->filter_type = filter_type; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (state->op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + switch(filter_type) { + case BE_FILTER_NAME: + attr_name = id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NAME].name; + break; + case BE_FILTER_ADDR: + attr_name = id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NUMBER].name; + break; + default: + ret = EINVAL; + goto fail; + } + + ret = sss_filter_sanitize(state, filter_value, &clean_filter_value); + if (ret != EOK) { + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(objectclass=%s)(%s=%s))", + id_ctx->opts->ipnetwork_map[SDAP_OC_IPNETWORK].name, + attr_name, clean_filter_value); + talloc_zfree(clean_filter_value); + if (state->filter == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to build the base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, id_ctx->opts->ipnetwork_map, + SDAP_OPTS_IPNETWORK, NULL, &state->attrs, NULL); + if (ret != EOK) { + goto fail; + } + + ret = sdap_ipnetwork_get_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +sdap_ipnetwork_get_connect_done(struct tevent_req *subreq); + +static errno_t +sdap_ipnetwork_get_retry(struct tevent_req *req) +{ + struct sdap_ipnetwork_get_state *state; + struct tevent_req *subreq; + errno_t ret = EOK; + + state = tevent_req_data(req, struct sdap_ipnetwork_get_state); + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + return ret; + } + + tevent_req_set_callback(subreq, sdap_ipnetwork_get_connect_done, req); + + return EOK; +} + +static void +sdap_ipnetwork_get_done(struct tevent_req *subreq); + +static void +sdap_ipnetwork_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_ipnetwork_get_state *state; + int dp_error = DP_ERR_FATAL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ipnetwork_get_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_ipnetwork_send(state, state->ev, + state->domain, state->sysdb, + state->id_ctx->opts, + state->sdom->ipnetwork_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_ipnetwork_get_done, req); +} + +static void +sdap_ipnetwork_get_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_ipnetwork_get_state *state; + int dp_error = DP_ERR_FATAL; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ipnetwork_get_state); + + ret = sdap_get_ipnetwork_recv(NULL, subreq, NULL); + talloc_zfree(subreq); + + /* Check whether we need to try again with another + * failover server. */ + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_ipnetwork_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Return to the mainloop to retry */ + return; + } + state->sdap_ret = ret; + + /* An error occurred. */ + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT && state->noexist_delete == true) { + /* Ensure that this entry is removed from the sysdb */ + switch (state->filter_type) { + case BE_FILTER_NAME: + ret = sysdb_ipnetwork_delete(state->domain, + state->filter_value, + NULL); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + case BE_FILTER_ADDR: + ret = sysdb_ipnetwork_delete(state->domain, NULL, + state->filter_value); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + default: + tevent_req_error(req, EINVAL); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +static errno_t +sdap_ipnetwork_get_recv(struct tevent_req *req, + int *dp_error_out, + int *sdap_ret) +{ + struct sdap_ipnetwork_get_state *state; + + state = tevent_req_data(req, struct sdap_ipnetwork_get_state); + + if (dp_error_out != NULL) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret != NULL) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_ipnetwork_handler_state { + struct dp_reply_std reply; +}; + +static void +sdap_ipnetwork_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_ipnetwork_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params) +{ + struct sdap_ipnetwork_handler_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ipnetwork_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (resolver_data->filter_type == BE_FILTER_ENUM) { + DEBUG(SSSDBG_TRACE_LIBS, "Skipping enumeration on demand\n"); + ret = EOK; + goto immediately; + } + + subreq = sdap_ipnetwork_get_send(state, params->ev, + resolver_ctx->id_ctx, + resolver_ctx->id_ctx->opts->sdom, + resolver_ctx->id_ctx->conn, + resolver_data->filter_type, + resolver_data->filter_value, + true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_ipnetwork_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void +sdap_ipnetwork_handler_done(struct tevent_req *subreq) +{ + struct sdap_ipnetwork_handler_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ipnetwork_handler_state); + + ret = sdap_ipnetwork_get_recv(subreq, &dp_error, NULL); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +errno_t +sdap_ipnetwork_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_ipnetwork_handler_state *state; + + state = tevent_req_data(req, struct sdap_ipnetwork_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/ldap/sdap_online_check.c b/src/providers/ldap/sdap_online_check.c new file mode 100644 index 0000000..9c104e5 --- /dev/null +++ b/src/providers/ldap/sdap_online_check.c @@ -0,0 +1,244 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2016 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include "util/util.h" +#include "providers/backend.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_online_check_state { + struct sdap_id_ctx *id_ctx; + struct be_ctx *be_ctx; +}; + +static void sdap_online_check_connect_done(struct tevent_req *subreq); +static void sdap_online_check_reinit_done(struct tevent_req *subreq); + +static struct tevent_req *sdap_online_check_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx) +{ + struct sdap_online_check_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_ctx *be_ctx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_online_check_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->id_ctx = id_ctx; + state->be_ctx = be_ctx = id_ctx->be; + + subreq = sdap_cli_connect_send(state, be_ctx->ev, id_ctx->opts, be_ctx, + id_ctx->conn->service, false, + CON_TLS_DFL, false); + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + tevent_req_post(req, be_ctx->ev); + } else { + tevent_req_set_callback(subreq, sdap_online_check_connect_done, req); + } + + return req; +} + +static void sdap_online_check_connect_done(struct tevent_req *subreq) +{ + struct sdap_online_check_state *state; + struct sdap_server_opts *srv_opts; + struct sdap_id_ctx *id_ctx; + struct tevent_req *req; + bool can_retry; + bool reinit = false; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_online_check_state); + + id_ctx = state->id_ctx; + + ret = sdap_cli_connect_recv(subreq, state, &can_retry, NULL, &srv_opts); + talloc_zfree(subreq); + if (ret != EOK) { + if (can_retry == false) { + ret = ERR_OFFLINE; + } + + goto done; + } else { + if (id_ctx->srv_opts == NULL) { + srv_opts->max_user_value = 0; + srv_opts->max_group_value = 0; + srv_opts->max_service_value = 0; + srv_opts->max_sudo_value = 0; + srv_opts->max_iphost_value = 0; + srv_opts->max_ipnetwork_value = 0; + } else if (strcmp(srv_opts->server_id, id_ctx->srv_opts->server_id) == 0 + && srv_opts->supports_usn + && id_ctx->srv_opts->last_usn > srv_opts->last_usn) { + id_ctx->srv_opts->max_user_value = 0; + id_ctx->srv_opts->max_group_value = 0; + id_ctx->srv_opts->max_service_value = 0; + id_ctx->srv_opts->max_sudo_value = 0; + id_ctx->srv_opts->max_iphost_value = 0; + id_ctx->srv_opts->max_ipnetwork_value = 0; + id_ctx->srv_opts->last_usn = srv_opts->last_usn; + + reinit = true; + } + + sdap_steal_server_opts(id_ctx, &srv_opts); + } + + if (reinit) { + DEBUG(SSSDBG_TRACE_FUNC, "Server reinitialization detected. " + "Cleaning cache.\n"); + subreq = sdap_reinit_cleanup_send(state, state->be_ctx, id_ctx); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to perform reinitialization " + "clean up.\n"); + /* not fatal */ + goto done; + } + + tevent_req_set_callback(subreq, sdap_online_check_reinit_done, req); + return; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void sdap_online_check_reinit_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_reinit_cleanup_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to perform reinitialization " + "clean up [%d]: %s\n", ret, strerror(ret)); + /* not fatal */ + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Reinitialization clean up completed\n"); + } + + tevent_req_done(req); +} + +static errno_t sdap_online_check_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_online_check_handler_state { + struct dp_reply_std reply; +}; + +static void sdap_online_check_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_online_check_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + void *data, + struct dp_req_params *params) +{ + struct sdap_online_check_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_online_check_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + subreq = sdap_online_check_send(state, id_ctx); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_online_check_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_online_check_handler_done(struct tevent_req *subreq) +{ + struct sdap_online_check_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_online_check_handler_state); + + ret = sdap_online_check_recv(subreq); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + tevent_req_done(req); +} + +errno_t sdap_online_check_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_online_check_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_online_check_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/ldap/sdap_ops.c b/src/providers/ldap/sdap_ops.c new file mode 100644 index 0000000..2125b21 --- /dev/null +++ b/src/providers/ldap/sdap_ops.c @@ -0,0 +1,553 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "util/util.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_search_bases_ex_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + const char *filter; + const char **attrs; + struct sdap_attr_map *map; + int map_num_attrs; + int timeout; + bool allow_paging; + bool return_first_reply; + const char *base_dn; + + size_t base_iter; + struct sdap_search_base *cur_base; + struct sdap_search_base **bases; + + size_t reply_count; + struct sysdb_attrs **reply; +}; + +static errno_t sdap_search_bases_ex_next_base(struct tevent_req *req); +static void sdap_search_bases_ex_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_search_bases_ex_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map *map, + bool allow_paging, + bool return_first_reply, + int timeout, + const char *filter, + const char **attrs, + const char *base_dn) +{ + struct tevent_req *req; + struct sdap_search_bases_ex_state *state; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_search_bases_ex_state); + if (req == NULL) { + return NULL; + } + + if (bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No search base specified!\n"); + ret = ERR_INTERNAL; + goto immediately; + } + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->bases = bases; + state->map = map; + state->filter = filter; + state->attrs = attrs; + state->allow_paging = allow_paging; + state->return_first_reply = return_first_reply; + state->base_dn = base_dn; + + state->timeout = timeout == 0 + ? dp_opt_get_int(opts->basic, SDAP_SEARCH_TIMEOUT) + : timeout; + + if (state->map != NULL) { + for (state->map_num_attrs = 0; + state->map[state->map_num_attrs].opt_name != NULL; + state->map_num_attrs++) { + /* no op */; + } + } else { + state->map_num_attrs = 0; + } + + if (state->attrs == NULL && state->map != NULL) { + ret = build_attrs_from_map(state, state->map, state->map_num_attrs, + NULL, &state->attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to build attrs from map " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + } + + state->base_iter = 0; + ret = sdap_search_bases_ex_next_base(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t sdap_search_bases_ex_next_base(struct tevent_req *req) +{ + struct sdap_search_bases_ex_state *state; + struct tevent_req *subreq; + const char *base_dn; + char *filter; + + state = tevent_req_data(req, struct sdap_search_bases_ex_state); + state->cur_base = state->bases[state->base_iter]; + if (state->cur_base == NULL) { + return EOK; + } + + /* Combine lookup and search base filters. */ + filter = sdap_combine_filters(state, state->filter, + state->cur_base->filter); + if (filter == NULL) { + return ENOMEM; + } + + base_dn = state->base_dn != NULL ? state->base_dn : state->cur_base->basedn; + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing LDAP lookup with base [%s]\n", base_dn); + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base_dn, state->cur_base->scope, filter, + state->attrs, state->map, + state->map_num_attrs, state->timeout, + state->allow_paging); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_search_bases_ex_done, req); + + state->base_iter++; + return EAGAIN; +} + +static void sdap_search_bases_ex_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_search_bases_ex_state *state; + struct sysdb_attrs **attrs; + size_t count; + size_t i; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_search_bases_ex_state); + + DEBUG(SSSDBG_TRACE_FUNC, "Receiving data from base [%s]\n", + state->cur_base->basedn); + + ret = sdap_get_generic_recv(subreq, state, &count, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Add rules to result. */ + if (count > 0) { + if (state->return_first_reply == false) { + /* Merge with previous reply. */ + state->reply = talloc_realloc(state, state->reply, + struct sysdb_attrs *, + state->reply_count + count); + if (state->reply == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + for (i = 0; i < count; i++) { + state->reply[state->reply_count + i] = talloc_steal(state->reply, + attrs[i]); + } + + state->reply_count += count; + } else { + /* Return the first successful search result. */ + state->reply_count = count; + state->reply = attrs; + tevent_req_done(req); + return; + } + } + + /* Try next search base. */ + ret = sdap_search_bases_ex_next_base(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static int sdap_search_bases_ex_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply) +{ + struct sdap_search_bases_ex_state *state = + tevent_req_data(req, struct sdap_search_bases_ex_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->reply_count; + *reply = talloc_steal(mem_ctx, state->reply); + + return EOK; +} + +struct tevent_req * +sdap_search_bases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map *map, + bool allow_paging, + int timeout, + const char *filter, + const char **attrs, + const char *base_dn) +{ + return sdap_search_bases_ex_send(mem_ctx, ev, opts, sh, bases, map, + allow_paging, false, timeout, + filter, attrs, base_dn); +} + +int sdap_search_bases_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sysdb_attrs ***_reply) +{ + return sdap_search_bases_ex_recv(req, mem_ctx, _reply_count, _reply); +} + +struct tevent_req * +sdap_search_bases_return_first_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map *map, + bool allow_paging, + int timeout, + const char *filter, + const char **attrs, + const char *base_dn) +{ + return sdap_search_bases_ex_send(mem_ctx, ev, opts, sh, bases, map, + allow_paging, true, timeout, + filter, attrs, base_dn); +} + +int sdap_search_bases_return_first_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sysdb_attrs ***_reply) +{ + return sdap_search_bases_ex_recv(req, mem_ctx, _reply_count, _reply); +} + +struct sdap_deref_bases_ex_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + const char *filter; + const char **attrs; + const char *deref_attr; + struct sdap_attr_map_info *maps; + size_t num_maps; + unsigned int flags; + bool return_first_reply; + int timeout; + + size_t base_iter; + struct sdap_search_base *cur_base; + struct sdap_search_base **bases; + + size_t reply_count; + struct sdap_deref_attrs **reply; +}; + +static errno_t sdap_deref_bases_ex_next_base(struct tevent_req *req); +static void sdap_deref_bases_ex_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_deref_bases_ex_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map_info *maps, + const char *filter, + const char **attrs, + const char *deref_attr, + unsigned int flags, + bool return_first_reply, + int timeout) +{ + struct tevent_req *req; + struct sdap_deref_bases_ex_state *state; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_deref_bases_ex_state); + if (req == NULL) { + return NULL; + } + + if (bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No search base specified!\n"); + ret = ERR_INTERNAL; + goto immediately; + } + + if (maps == NULL || attrs == NULL || deref_attr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No attributes or map specified!\n"); + ret = ERR_INTERNAL; + goto immediately; + } + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->bases = bases; + state->maps = maps; + state->filter = filter; + state->attrs = attrs; + state->deref_attr = deref_attr; + state->return_first_reply = return_first_reply; + state->flags = flags; + + state->timeout = timeout == 0 + ? dp_opt_get_int(opts->basic, SDAP_SEARCH_TIMEOUT) + : timeout; + + for (state->num_maps = 0; maps[state->num_maps].map != NULL; + state->num_maps++) { + /* no op */; + } + + state->base_iter = 0; + ret = sdap_deref_bases_ex_next_base(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t sdap_deref_bases_ex_next_base(struct tevent_req *req) +{ + struct sdap_deref_bases_ex_state *state; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct sdap_deref_bases_ex_state); + state->cur_base = state->bases[state->base_iter]; + if (state->cur_base == NULL) { + return EOK; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing LDAP deref lookup with base [%s]\n", + state->cur_base->basedn); + + subreq = sdap_deref_search_with_filter_send(state, state->ev, state->opts, + state->sh, state->cur_base->basedn, state->filter, + state->deref_attr, state->attrs, state->num_maps, state->maps, + state->timeout, state->flags); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_deref_bases_ex_done, req); + + state->base_iter++; + return EAGAIN; +} + +static void sdap_deref_bases_ex_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_deref_bases_ex_state *state; + struct sdap_deref_attrs **attrs; + size_t count; + size_t i; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_deref_bases_ex_state); + + DEBUG(SSSDBG_TRACE_FUNC, "Receiving data from base [%s]\n", + state->cur_base->basedn); + + ret = sdap_deref_search_with_filter_recv(subreq, state, &count, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Add rules to result. */ + if (count > 0) { + if (state->return_first_reply == false) { + /* Merge with previous reply. */ + state->reply = talloc_realloc(state, state->reply, + struct sdap_deref_attrs *, + state->reply_count + count); + if (state->reply == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + for (i = 0; i < count; i++) { + state->reply[state->reply_count + i] = talloc_steal(state->reply, + attrs[i]); + } + + state->reply_count += count; + } else { + /* Return the first successful search result. */ + state->reply_count = count; + state->reply = attrs; + tevent_req_done(req); + return; + } + } + + /* Try next search base. */ + ret = sdap_deref_bases_ex_next_base(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static int sdap_deref_bases_ex_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply) +{ + struct sdap_deref_bases_ex_state *state = + tevent_req_data(req, struct sdap_deref_bases_ex_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->reply_count; + *reply = talloc_steal(mem_ctx, state->reply); + + return EOK; +} + +struct tevent_req * +sdap_deref_bases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map_info *maps, + const char *filter, + const char **attrs, + const char *deref_attr, + unsigned int flags, + int timeout) +{ + return sdap_deref_bases_ex_send(mem_ctx, ev, opts, sh, bases, maps, + filter, attrs, deref_attr, flags, + false, timeout); +} + +int sdap_deref_bases_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sdap_deref_attrs ***_reply) +{ + return sdap_deref_bases_ex_recv(req, mem_ctx, _reply_count, _reply); +} + +struct tevent_req * +sdap_deref_bases_return_first_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map_info *maps, + const char *filter, + const char **attrs, + const char *deref_attr, + unsigned int flags, + int timeout) +{ + return sdap_deref_bases_ex_send(mem_ctx, ev, opts, sh, bases, maps, + filter, attrs, deref_attr, flags, + true, timeout); +} + +int sdap_deref_bases_return_first_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sdap_deref_attrs ***_reply) +{ + return sdap_deref_bases_ex_recv(req, mem_ctx, _reply_count, _reply); +} diff --git a/src/providers/ldap/sdap_ops.h b/src/providers/ldap/sdap_ops.h new file mode 100644 index 0000000..648a2b6 --- /dev/null +++ b/src/providers/ldap/sdap_ops.h @@ -0,0 +1,99 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_OPS_H_ +#define _SDAP_OPS_H_ + +#include +#include +#include "providers/ldap/ldap_common.h" + +struct tevent_req *sdap_search_bases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map *map, + bool allow_paging, + int timeout, + const char *filter, + const char **attrs, + const char *base_dn); + +int sdap_search_bases_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply); + +struct tevent_req * +sdap_search_bases_return_first_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map *map, + bool allow_paging, + int timeout, + const char *filter, + const char **attrs, + const char *base_dn); + +int sdap_search_bases_return_first_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sysdb_attrs ***_reply); + +struct tevent_req * +sdap_deref_bases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map_info *maps, + const char *filter, + const char **attrs, + const char *deref_attr, + unsigned int flags, + int timeout); + +int sdap_deref_bases_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sdap_deref_attrs ***_reply); + +struct tevent_req * +sdap_deref_bases_return_first_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map_info *maps, + const char *filter, + const char **attrs, + const char *deref_attr, + unsigned int flags, + int timeout); + +int sdap_deref_bases_return_first_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sdap_deref_attrs ***_reply); + +#endif /* _SDAP_OPS_H_ */ diff --git a/src/providers/ldap/sdap_range.c b/src/providers/ldap/sdap_range.c new file mode 100644 index 0000000..44c3350 --- /dev/null +++ b/src/providers/ldap/sdap_range.c @@ -0,0 +1,142 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/ldap/sdap_range.h" +#include "util/util.h" +#include "util/strtonum.h" + +#define SDAP_RANGE_STRING "range=" + +errno_t sdap_parse_range(TALLOC_CTX *mem_ctx, + const char *attr_desc, + char **base_attr, + uint32_t *range_offset, + bool disable_range_retrieval) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + char *endptr; + char *end_range; + char *base; + size_t rangestringlen = sizeof(SDAP_RANGE_STRING) - 1; + + *range_offset = 0; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + /* The base_attr is the portion before the semicolon (if it exists) */ + endptr = strchr(attr_desc, ';'); + if (endptr == NULL) { + /* Not a ranged attribute. Just copy the attribute desc */ + *base_attr = talloc_strdup(mem_ctx, attr_desc); + if (!*base_attr) { + ret = ENOMEM; + } else { + ret = EOK; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "No sub-attributes for [%s]\n", attr_desc); + goto done; + } + + /* This is a complex attribute. First get the base attribute name */ + base = talloc_strndup(tmp_ctx, attr_desc, + endptr - attr_desc); + if (!base) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_LIBS, + "Base attribute of [%s] is [%s]\n", + attr_desc, base); + + /* Next, determine if this is a ranged attribute */ + if (strncmp(endptr+1, SDAP_RANGE_STRING, rangestringlen) != 0) { + /* This is some other sub-attribute. We'll just return the whole + * thing in case it's dealt with elsewhere. + */ + *base_attr = talloc_strdup(mem_ctx, attr_desc); + if (!*base_attr) { + ret = ENOMEM; + } else { + ret = EOK; + } + DEBUG(SSSDBG_TRACE_LIBS, + "[%s] contains sub-attribute other than a range, returning whole\n", + attr_desc); + goto done; + } else if (disable_range_retrieval) { + /* This is range sub-attribute, but we want to ignore it. + */ + *base_attr = talloc_strdup(mem_ctx, attr_desc); + if (!*base_attr) { + ret = ENOMEM; + } else { + ret = ECANCELED; + } + goto done; + } + + /* Get the end of the range */ + end_range = strchr(endptr + rangestringlen +1, '-'); + if (!end_range) { + ret = EINVAL; + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot find hyphen in [%s]\n", + endptr + rangestringlen +1); + goto done; + } + end_range++; /* advance past the hyphen */ + + if (*end_range == '*') { + /* this was the last iteration of range retrievals */ + *base_attr = talloc_steal(mem_ctx, base); + *range_offset = 0; + DEBUG(SSSDBG_TRACE_LIBS, + "[%s] contained the last set of values for this attribute\n", + attr_desc); + ret = EOK; + goto done; + } + + *range_offset = strtouint32(end_range, &endptr, 10); + if ((errno != 0) || (*endptr != '\0') || (end_range == endptr)) { + *range_offset = 0; + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "[%s] did not parse as an unsigned integer: [%s]\n", + end_range, strerror(ret)); + goto done; + } + (*range_offset)++; + + *base_attr = talloc_steal(mem_ctx, base); + DEBUG(SSSDBG_TRACE_LIBS, + "Parsed range values: [%s][%d]\n", + base, *range_offset); + + ret = EAGAIN; +done: + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ldap/sdap_range.h b/src/providers/ldap/sdap_range.h new file mode 100644 index 0000000..f11b3be --- /dev/null +++ b/src/providers/ldap/sdap_range.h @@ -0,0 +1,34 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef SDAP_RANGE_H_ +#define SDAP_RANGE_H_ + +#include "src/util/util.h" + +errno_t sdap_parse_range(TALLOC_CTX *mem_ctx, + const char *attr_desc, + char **base_attr, + uint32_t *range_offset, + bool disable_range_retrieval); + +#endif /* SDAP_RANGE_H_ */ diff --git a/src/providers/ldap/sdap_refresh.c b/src/providers/ldap/sdap_refresh.c new file mode 100644 index 0000000..402db53 --- /dev/null +++ b/src/providers/ldap/sdap_refresh.c @@ -0,0 +1,238 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include + +#include "providers/ldap/sdap.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_refresh_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct dp_id_data *account_req; + struct sdap_id_ctx *id_ctx; + struct sss_domain_info *domain; + struct sdap_domain *sdom; + char **names; + size_t index; +}; + +static errno_t sdap_refresh_step(struct tevent_req *req); +static void sdap_refresh_done(struct tevent_req *subreq); + +static struct tevent_req *sdap_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + int entry_type, + char **names, + void *pvt) +{ + struct sdap_refresh_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (names == NULL) { + ret = EOK; + goto immediately; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->domain = domain; + state->id_ctx = talloc_get_type(pvt, struct sdap_id_ctx); + state->names = names; + state->index = 0; + + state->sdom = sdap_domain_get(state->id_ctx->opts, domain); + if (state->sdom == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto immediately; + } + + state->account_req = be_refresh_acct_req(state, entry_type, + BE_FILTER_NAME, domain); + if (state->account_req == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = sdap_refresh_step(req); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Nothing to refresh\n"); + goto immediately; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_refresh_step() failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t sdap_refresh_step(struct tevent_req *req) +{ + struct sdap_refresh_state *state = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + state = tevent_req_data(req, struct sdap_refresh_state); + + if (state->names == NULL) { + ret = EOK; + goto done; + } + + state->account_req->filter_value = state->names[state->index]; + if (state->account_req->filter_value == NULL) { + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing refresh of %s %s\n", + be_req2str(state->account_req->entry_type), + state->account_req->filter_value); + + subreq = sdap_handle_acct_req_send(state, state->be_ctx, + state->account_req, state->id_ctx, + state->sdom, state->id_ctx->conn, true); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_refresh_done, req); + + state->index++; + ret = EAGAIN; + +done: + return ret; +} + +static void sdap_refresh_done(struct tevent_req *subreq) +{ + struct sdap_refresh_state *state = NULL; + struct tevent_req *req = NULL; + const char *err_msg = NULL; + errno_t dp_error; + int sdap_ret; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_refresh_state); + + ret = sdap_handle_acct_req_recv(subreq, &dp_error, &err_msg, &sdap_ret); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to refresh %s [dp_error: %d, " + "sdap_ret: %d, errno: %d]: %s\n", + be_req2str(state->account_req->entry_type), + dp_error, sdap_ret, ret, err_msg); + goto done; + } + + if (state->account_req->entry_type == BE_REQ_INITGROUPS) { + ret = sysdb_set_initgr_expire_timestamp(state->domain, + state->account_req->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to set initgroups expiration for [%s]\n", + state->account_req->filter_value); + } + } + + ret = sdap_refresh_step(req); + if (ret == EAGAIN) { + return; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sdap_refresh_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +REFRESH_SEND_RECV_FNS(sdap_refresh_initgroups, sdap_refresh, BE_REQ_INITGROUPS); +REFRESH_SEND_RECV_FNS(sdap_refresh_users, sdap_refresh, BE_REQ_USER); +REFRESH_SEND_RECV_FNS(sdap_refresh_groups, sdap_refresh, BE_REQ_GROUP); +REFRESH_SEND_RECV_FNS(sdap_refresh_netgroups, sdap_refresh, BE_REQ_NETGROUP); + +errno_t sdap_refresh_init(struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx) +{ + errno_t ret; + struct be_refresh_cb sdap_refresh_callbacks[] = { + { .send_fn = sdap_refresh_initgroups_send, + .recv_fn = sdap_refresh_initgroups_recv, + .pvt = id_ctx, + }, + { .send_fn = sdap_refresh_users_send, + .recv_fn = sdap_refresh_users_recv, + .pvt = id_ctx, + }, + { .send_fn = sdap_refresh_groups_send, + .recv_fn = sdap_refresh_groups_recv, + .pvt = id_ctx, + }, + { .send_fn = sdap_refresh_netgroups_send, + .recv_fn = sdap_refresh_netgroups_recv, + .pvt = id_ctx, + }, + }; + + ret = be_refresh_ctx_init_with_callbacks(be_ctx, + SYSDB_NAME, + sdap_refresh_callbacks); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize background refresh\n"); + return ret; + } + + return ret; +} diff --git a/src/providers/ldap/sdap_reinit.c b/src/providers/ldap/sdap_reinit.c new file mode 100644 index 0000000..1764ecd --- /dev/null +++ b/src/providers/ldap/sdap_reinit.c @@ -0,0 +1,335 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include + +#include "util/util.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_enum.h" +#include "db/sysdb.h" +#include "db/sysdb_services.h" + +struct sdap_reinit_cleanup_state { + struct sss_domain_info *domain; + struct sysdb_ctx *sysdb; +}; + +static errno_t sdap_reinit_clear_usn(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain); +static void sdap_reinit_cleanup_done(struct tevent_req *subreq); +static errno_t sdap_reinit_delete_records(struct sss_domain_info *domain); + +struct tevent_req* sdap_reinit_cleanup_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_reinit_cleanup_state *state; + int ret; + + /* + * 1. remove entryUSN attribute from all entries + * 2. run enumeration + * 3. remove records that doesn't have entryUSN attribute updated + * + * We don't need to do this for sudo rules, they will be refreshed + * automatically during next smart/full refresh, or when an expired rule + * is deleted. + */ + + req = tevent_req_create(mem_ctx, &state, struct sdap_reinit_cleanup_state); + if (req == NULL) { + return NULL; + } + + state->sysdb = be_ctx->domain->sysdb; + state->domain = be_ctx->domain; + + if (!be_ctx->domain->enumerate) { + /* enumeration is disabled, this whole process is meaningless */ + ret = EOK; + goto immediately; + } + + ret = sdap_reinit_clear_usn(state->sysdb, state->domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to clear USN attributes [%d]: %s\n", + ret, strerror(ret)); + goto immediately; + } + + subreq = sdap_dom_enum_send(id_ctx, be_ctx->ev, id_ctx, + id_ctx->opts->sdom, id_ctx->conn); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to issue enumeration request\n"); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_reinit_cleanup_done, req); + + return req; + +immediately: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, be_ctx->ev); + + return req; +} + +static void sdap_delete_msgs_usn(struct sysdb_ctx *sysdb, + struct ldb_message **msgs, + size_t msgs_num) +{ + struct ldb_message_element el = { 0, SYSDB_USN, 0, NULL }; + struct sysdb_attrs usn_el = { 1, &el }; + errno_t ret; + int i; + + for (i = 0; i < msgs_num; i++) { + ret = sysdb_set_entry_attr(sysdb, msgs[i]->dn, &usn_el, SYSDB_MOD_DEL); + if (ret) { + DEBUG(SSSDBG_TRACE_FUNC, "Failed to clean USN on entry: [%s]\n", + ldb_dn_get_linearized(msgs[i]->dn)); + } + } +} + +static errno_t sdap_reinit_clear_usn(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx = NULL; + bool in_transaction = false; + struct ldb_message **msgs = NULL; + size_t msgs_num = 0; + const char *attrs[] = { "dn", NULL }; + int sret; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + goto done; + } + in_transaction = true; + + /* reset users' usn */ + ret = sysdb_search_users(tmp_ctx, domain, + "", attrs, &msgs_num, &msgs); + if (ret != EOK) { + goto done; + } + sdap_delete_msgs_usn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + /* reset groups' usn */ + ret = sysdb_search_groups(tmp_ctx, domain, "", attrs, &msgs_num, &msgs); + if (ret != EOK) { + goto done; + } + sdap_delete_msgs_usn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + /* reset services' usn */ + ret = sysdb_search_services(tmp_ctx, domain, "", attrs, &msgs_num, &msgs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot search services [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + sdap_delete_msgs_usn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + ret = sysdb_transaction_commit(sysdb); + if (ret == EOK) { + in_transaction = false; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not commit transaction\n"); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n"); + } + } + + talloc_free(tmp_ctx); + + return ret; +} + +static void sdap_reinit_cleanup_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_reinit_cleanup_state *state = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_reinit_cleanup_state); + + ret = sdap_dom_enum_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Domain enumeration failed [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + /* Ok, we've completed an enumeration. Save this to the + * sysdb so we can postpone starting up the enumeration + * process on the next SSSD service restart (to avoid + * slowing down system boot-up + */ + ret = sysdb_set_enumerated(state->domain, SYSDB_HAS_ENUMERATED_ID, true); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not mark domain as having " + "enumerated.\n"); + /* This error is non-fatal, so continue */ + } + + ret = sdap_reinit_delete_records(state->domain); + if (ret != EOK) { + goto fail; + } + + tevent_req_done(req); + return; + +fail: + tevent_req_error(req, ret); +} + +static void sdap_delete_msgs_dn(struct sysdb_ctx *sysdb, + struct ldb_message **msgs, + size_t msgs_num) +{ + errno_t ret; + int i; + + for (i = 0; i < msgs_num; i++) { + ret = sysdb_delete_entry(sysdb, msgs[i]->dn, true); + if (ret) { + DEBUG(SSSDBG_TRACE_FUNC, "Failed to delete entry: [%s]\n", + ldb_dn_get_linearized(msgs[i]->dn)); + } + } +} + +static errno_t sdap_reinit_delete_records(struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx = NULL; + bool in_transaction = false; + struct ldb_message **msgs = NULL; + size_t msgs_num = 0; + const char *attrs[] = { "dn", NULL }; + int sret; + errno_t ret; + struct sysdb_ctx *sysdb = domain->sysdb; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + goto done; + } + in_transaction = true; + + /* purge untouched users */ + ret = sysdb_search_users(tmp_ctx, domain, "(!("SYSDB_USN"=*))", + attrs, &msgs_num, &msgs); + if (ret != EOK) { + goto done; + } + sdap_delete_msgs_dn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + /* purge untouched groups */ + ret = sysdb_search_groups(tmp_ctx, domain, "(!("SYSDB_USN"=*))", + attrs, &msgs_num, &msgs); + if (ret != EOK) { + goto done; + } + sdap_delete_msgs_dn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + /* purge untouched services */ + ret = sysdb_search_services(tmp_ctx, domain, "(!("SYSDB_USN"=*))", + attrs, &msgs_num, &msgs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot search services [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + sdap_delete_msgs_dn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + ret = sysdb_transaction_commit(sysdb); + if (ret == EOK) { + in_transaction = false; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not commit transaction\n"); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n"); + } + } + + talloc_free(tmp_ctx); + + return ret; +} + +errno_t sdap_reinit_cleanup_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_sudo.c b/src/providers/ldap/sdap_sudo.c new file mode 100644 index 0000000..8cea919 --- /dev/null +++ b/src/providers/ldap/sdap_sudo.c @@ -0,0 +1,231 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "providers/backend.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_sudo.h" +#include "db/sysdb_sudo.h" + +struct sdap_sudo_handler_state { + uint32_t type; + struct dp_reply_std reply; + struct sdap_sudo_ctx *sudo_ctx; +}; + +static void sdap_sudo_handler_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_sudo_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx, + struct dp_sudo_data *data, + struct dp_req_params *params) +{ + struct sdap_sudo_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->type = data->type; + state->sudo_ctx = sudo_ctx; + + switch (data->type) { + case BE_REQ_SUDO_FULL: + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a full refresh of sudo rules\n"); + subreq = sdap_sudo_full_refresh_send(state, sudo_ctx); + break; + case BE_REQ_SUDO_RULES: + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a refresh of specific sudo rules\n"); + subreq = sdap_sudo_rules_refresh_send(state, sudo_ctx, data->rules); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request type: %d\n", data->type); + ret = EINVAL; + goto immediately; + } + + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request: %d\n", data->type); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_sudo_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_sudo_handler_done(struct tevent_req *subreq) +{ + struct sdap_sudo_handler_state *state; + struct tevent_req *req; + int dp_error; + bool deleted; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_handler_state); + + switch (state->type) { + case BE_REQ_SUDO_FULL: + ret = sdap_sudo_full_refresh_recv(subreq, &dp_error); + talloc_zfree(subreq); + + /* Reschedule the periodic task since the refresh was just finished + * per user request. */ + if (ret == EOK && dp_error == DP_ERR_OK) { + be_ptask_postpone(state->sudo_ctx->full_refresh); + } + break; + case BE_REQ_SUDO_RULES: + ret = sdap_sudo_rules_refresh_recv(subreq, &dp_error, &deleted); + talloc_zfree(subreq); + if (ret == EOK && deleted == true) { + ret = ENOENT; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request type: %d\n", state->type); + dp_error = DP_ERR_FATAL; + ret = ERR_INTERNAL; + break; + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +static errno_t +sdap_sudo_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_sudo_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_sudo_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} + +static void sdap_sudo_online_cb(void *pvt) +{ + struct sdap_sudo_ctx *sudo_ctx; + + sudo_ctx = talloc_get_type(pvt, struct sdap_sudo_ctx); + if (sudo_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "BUG: sudo_ctx is NULL\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "We are back online. SUDO host information will " + "be renewed on next refresh.\n"); + sudo_ctx->run_hostinfo = true; +} + +errno_t sdap_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_attr_map *native_map, + struct dp_method *dp_methods) +{ + struct sdap_sudo_ctx *sudo_ctx; + int ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing sudo LDAP back end\n"); + + sudo_ctx = talloc_zero(mem_ctx, struct sdap_sudo_ctx); + if (sudo_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc() failed\n"); + return ENOMEM; + } + + sudo_ctx->id_ctx = id_ctx; + + ret = ldap_get_sudo_options(be_ctx->cdb, + sysdb_ctx_get_ldb(be_ctx->domain->sysdb), + be_ctx->conf_path, id_ctx->opts, native_map, + &sudo_ctx->use_host_filter, + &sudo_ctx->include_regexp, + &sudo_ctx->include_netgroups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get SUDO options [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (sudo_ctx->use_host_filter) { + ret = be_add_online_cb(sudo_ctx, be_ctx, sdap_sudo_online_cb, + sudo_ctx, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to install online callback " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Obtain hostinfo with the first refresh. */ + sudo_ctx->run_hostinfo = true; + } + + ret = sdap_sudo_ptask_setup(be_ctx, sudo_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to setup periodical refresh of " + "sudo rules [%d]: %s\n", ret, sss_strerror(ret)); + /* periodical updates will not work, but specific-rule update + * is no affected by this, therefore we don't have to fail here */ + } + + dp_set_method(dp_methods, DPM_SUDO_HANDLER, + sdap_sudo_handler_send, sdap_sudo_handler_recv, sudo_ctx, + struct sdap_sudo_ctx, struct dp_sudo_data, struct dp_reply_std); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(sudo_ctx); + } + + return ret; +} diff --git a/src/providers/ldap/sdap_sudo.h b/src/providers/ldap/sdap_sudo.h new file mode 100644 index 0000000..85eeccf --- /dev/null +++ b/src/providers/ldap/sdap_sudo.h @@ -0,0 +1,106 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2011 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_SUDO_H_ +#define _SDAP_SUDO_H_ + +#include "providers/backend.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_sudo_ctx { + struct sdap_id_ctx *id_ctx; + struct be_ptask *full_refresh; + struct be_ptask *smart_refresh; + + char **hostnames; + char **ip_addr; + bool include_netgroups; + bool include_regexp; + bool use_host_filter; + + bool full_refresh_done; + + bool run_hostinfo; +}; + +/* Common functions from ldap_sudo.c */ + +errno_t sdap_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_attr_map *native_map, + struct dp_method *dp_methods); + +/* sdap async interface */ +struct tevent_req *sdap_sudo_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx, + const char *ldap_filter, + const char *sysdb_filter, + bool update_usn); + +int sdap_sudo_refresh_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + int *dp_error, + size_t *num_rules); + +struct tevent_req *sdap_sudo_full_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx); + +int sdap_sudo_full_refresh_recv(struct tevent_req *req, + int *dp_error); + +struct tevent_req *sdap_sudo_smart_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx); + +int sdap_sudo_smart_refresh_recv(struct tevent_req *req, + int *dp_error); + +struct tevent_req *sdap_sudo_rules_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx, + const char **rules); + +int sdap_sudo_rules_refresh_recv(struct tevent_req *req, + int *dp_error, + bool *deleted); + +errno_t +sdap_sudo_ptask_setup(struct be_ctx *be_ctx, struct sdap_sudo_ctx *sudo_ctx); + +/* host info */ +struct tevent_req * sdap_sudo_get_hostinfo_send(TALLOC_CTX *mem_ctx, + struct sdap_options *opts, + struct be_ctx *be_ctx); + +int sdap_sudo_get_hostinfo_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char ***hostnames, char ***ip_addr); + +/* (&(objectClass=sudoRole)(|(cn=defaults)(sudoUser=ALL)%s)) */ +#define SDAP_SUDO_FILTER_USER "(&(objectClass=%s)(|(%s=%s)(%s=ALL)%s))" +#define SDAP_SUDO_FILTER_CLASS "(%s=%s)" +#define SDAP_SUDO_FILTER_DEFAULTS "(&(objectClass=%s)(%s=%s))" +#define SDAP_SUDO_DEFAULTS "defaults" + +#define SDAP_SUDO_FILTER_USERNAME "(%s=%s)" +#define SDAP_SUDO_FILTER_UID "(%s=#%u)" +#define SDAP_SUDO_FILTER_GROUP "(%s=%%%s)" +#define SDAP_SUDO_FILTER_NETGROUP "(%s=+%s)" + +#endif /* _SDAP_SUDO_H_ */ diff --git a/src/providers/ldap/sdap_sudo_refresh.c b/src/providers/ldap/sdap_sudo_refresh.c new file mode 100644 index 0000000..a484c6a --- /dev/null +++ b/src/providers/ldap/sdap_sudo_refresh.c @@ -0,0 +1,485 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "util/util.h" +#include "providers/be_ptask.h" +#include "providers/ldap/sdap_sudo.h" +#include "providers/ldap/sdap_sudo_shared.h" +#include "db/sysdb_sudo.h" + +struct sdap_sudo_full_refresh_state { + struct sdap_sudo_ctx *sudo_ctx; + struct sdap_id_ctx *id_ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + int dp_error; +}; + +static void sdap_sudo_full_refresh_done(struct tevent_req *subreq); + +struct tevent_req *sdap_sudo_full_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_id_ctx *id_ctx = sudo_ctx->id_ctx; + struct sdap_sudo_full_refresh_state *state = NULL; + char *search_filter = NULL; + char *delete_filter = NULL; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_full_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->sudo_ctx = sudo_ctx; + state->id_ctx = id_ctx; + state->sysdb = id_ctx->be->domain->sysdb; + state->domain = id_ctx->be->domain; + + /* Download all rules from LDAP */ + search_filter = talloc_asprintf(state, SDAP_SUDO_FILTER_CLASS, + id_ctx->opts->sudorule_map[SDAP_AT_SUDO_OC].name, + id_ctx->opts->sudorule_map[SDAP_OC_SUDORULE].name); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* Remove all rules from cache */ + delete_filter = talloc_asprintf(state, "(%s=%s)", + SYSDB_OBJECTCLASS, SYSDB_SUDO_CACHE_OC); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a full refresh of sudo rules\n"); + + subreq = sdap_sudo_refresh_send(state, sudo_ctx, search_filter, + delete_filter, true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_sudo_full_refresh_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, id_ctx->be->ev); + + return req; +} + +static void sdap_sudo_full_refresh_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_sudo_full_refresh_state *state = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_full_refresh_state); + + ret = sdap_sudo_refresh_recv(state, subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + /* save the time in the sysdb */ + ret = sysdb_sudo_set_last_full_refresh(state->domain, time(NULL)); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to save time of " + "a successful full refresh\n"); + /* this is only a minor error that does not affect the functionality, + * therefore there is no need to report it with tevent_req_error() + * which would cause problems in the consumers */ + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successful full refresh of sudo rules\n"); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* We just finished full request, we can postpone smart refresh. */ + be_ptask_postpone(state->sudo_ctx->smart_refresh); + + tevent_req_done(req); +} + +int sdap_sudo_full_refresh_recv(struct tevent_req *req, + int *dp_error) +{ + struct sdap_sudo_full_refresh_state *state = NULL; + state = tevent_req_data(req, struct sdap_sudo_full_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +struct sdap_sudo_smart_refresh_state { + struct sdap_id_ctx *id_ctx; + struct sysdb_ctx *sysdb; + int dp_error; +}; + +static void sdap_sudo_smart_refresh_done(struct tevent_req *subreq); + +struct tevent_req *sdap_sudo_smart_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_id_ctx *id_ctx = sudo_ctx->id_ctx; + struct sdap_attr_map *map = id_ctx->opts->sudorule_map; + struct sdap_server_opts *srv_opts = id_ctx->srv_opts; + struct sdap_sudo_smart_refresh_state *state = NULL; + char *search_filter = NULL; + const char *usn; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_smart_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (be_ptask_running(sudo_ctx->full_refresh)) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping smart refresh because " + "there is ongoing full refresh.\n"); + state->dp_error = DP_ERR_OK; + ret = EOK; + goto immediately; + } + + state->id_ctx = id_ctx; + state->sysdb = id_ctx->be->domain->sysdb; + + /* Download all rules from LDAP that are newer than usn */ + if (srv_opts == NULL || srv_opts->max_sudo_value == NULL + || strcmp(srv_opts->max_sudo_value, "0") == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "USN value is unknown, assuming zero and " + "omitting it from the filter.\n"); + usn = "0"; + search_filter = talloc_asprintf(state, "(%s=%s)", + map[SDAP_AT_SUDO_OC].name, + map[SDAP_OC_SUDORULE].name); + } else { + usn = srv_opts->max_sudo_value; + search_filter = talloc_asprintf(state, "(&(%s=%s)(%s>=%s))", + map[SDAP_AT_SUDO_OC].name, + map[SDAP_OC_SUDORULE].name, + map[SDAP_AT_SUDO_USN].name, usn); + } + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* Do not remove any rules that are already in the sysdb + * sysdb_filter = NULL; */ + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a smart refresh of sudo rules " + "(USN >= %s)\n", usn); + + subreq = sdap_sudo_refresh_send(state, sudo_ctx, search_filter, NULL, true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_sudo_smart_refresh_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + tevent_req_post(req, id_ctx->be->ev); + + return req; +} + +static void sdap_sudo_smart_refresh_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_sudo_smart_refresh_state *state = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_smart_refresh_state); + + ret = sdap_sudo_refresh_recv(state, subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successful smart refresh of sudo rules\n"); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_sudo_smart_refresh_recv(struct tevent_req *req, + int *dp_error) +{ + struct sdap_sudo_smart_refresh_state *state = NULL; + state = tevent_req_data(req, struct sdap_sudo_smart_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +struct sdap_sudo_rules_refresh_state { + struct sdap_id_ctx *id_ctx; + size_t num_rules; + int dp_error; + bool deleted; +}; + +static void sdap_sudo_rules_refresh_done(struct tevent_req *subreq); + +struct tevent_req *sdap_sudo_rules_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx, + const char **rules) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_sudo_rules_refresh_state *state = NULL; + struct sdap_id_ctx *id_ctx = sudo_ctx->id_ctx; + struct sdap_options *opts = id_ctx->opts; + TALLOC_CTX *tmp_ctx = NULL; + char *search_filter = NULL; + char *delete_filter = NULL; + char *safe_rule = NULL; + int ret; + int i; + + if (rules == NULL) { + return NULL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_rules_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + search_filter = talloc_zero(tmp_ctx, char); /* assign to tmp_ctx */ + delete_filter = talloc_zero(tmp_ctx, char); /* assign to tmp_ctx */ + + /* Download only selected rules from LDAP */ + /* Remove all selected rules from cache */ + for (i = 0; rules[i] != NULL; i++) { + ret = sss_filter_sanitize(tmp_ctx, rules[i], &safe_rule); + if (ret != EOK) { + ret = ENOMEM; + goto immediately; + } + + search_filter = talloc_asprintf_append_buffer(search_filter, "(%s=%s)", + opts->sudorule_map[SDAP_AT_SUDO_NAME].name, + safe_rule); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + delete_filter = talloc_asprintf_append_buffer(delete_filter, "(%s=%s)", + SYSDB_SUDO_CACHE_AT_CN, + safe_rule); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + } + + state->id_ctx = sudo_ctx->id_ctx; + state->num_rules = i; + + search_filter = talloc_asprintf(tmp_ctx, "(&"SDAP_SUDO_FILTER_CLASS"(|%s))", + opts->sudorule_map[SDAP_AT_SUDO_OC].name, + opts->sudorule_map[SDAP_OC_SUDORULE].name, + search_filter); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + delete_filter = talloc_asprintf(tmp_ctx, "(&(%s=%s)(|%s))", + SYSDB_OBJECTCLASS, SYSDB_SUDO_CACHE_OC, + delete_filter); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_sudo_refresh_send(req, sudo_ctx, search_filter, + delete_filter, false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_sudo_rules_refresh_done, req); + + ret = EOK; +immediately: + talloc_free(tmp_ctx); + + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, id_ctx->be->ev); + } + + return req; +} + +static void sdap_sudo_rules_refresh_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_sudo_rules_refresh_state *state = NULL; + size_t downloaded_rules_num; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_rules_refresh_state); + + ret = sdap_sudo_refresh_recv(state, subreq, &state->dp_error, + &downloaded_rules_num); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + state->deleted = downloaded_rules_num != state->num_rules ? true : false; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_sudo_rules_refresh_recv(struct tevent_req *req, + int *dp_error, + bool *deleted) +{ + struct sdap_sudo_rules_refresh_state *state = NULL; + state = tevent_req_data(req, struct sdap_sudo_rules_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + *deleted = state->deleted; + + return EOK; +} + +static struct tevent_req * +sdap_sudo_ptask_full_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct sdap_sudo_ctx *sudo_ctx; + sudo_ctx = talloc_get_type(pvt, struct sdap_sudo_ctx); + + return sdap_sudo_full_refresh_send(mem_ctx, sudo_ctx); +} + +static errno_t +sdap_sudo_ptask_full_refresh_recv(struct tevent_req *req) +{ + int dp_error; + + return sdap_sudo_full_refresh_recv(req, &dp_error); +} + +static struct tevent_req * +sdap_sudo_ptask_smart_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct sdap_sudo_ctx *sudo_ctx; + sudo_ctx = talloc_get_type(pvt, struct sdap_sudo_ctx); + + return sdap_sudo_smart_refresh_send(mem_ctx, sudo_ctx); +} + +static errno_t +sdap_sudo_ptask_smart_refresh_recv(struct tevent_req *req) +{ + int dp_error; + + return sdap_sudo_smart_refresh_recv(req, &dp_error); +} + +errno_t +sdap_sudo_ptask_setup(struct be_ctx *be_ctx, struct sdap_sudo_ctx *sudo_ctx) +{ + return sdap_sudo_ptask_setup_generic(be_ctx, sudo_ctx->id_ctx->opts->basic, + sdap_sudo_ptask_full_refresh_send, + sdap_sudo_ptask_full_refresh_recv, + sdap_sudo_ptask_smart_refresh_send, + sdap_sudo_ptask_smart_refresh_recv, + sudo_ctx, + &sudo_ctx->full_refresh, + &sudo_ctx->smart_refresh); +} diff --git a/src/providers/ldap/sdap_sudo_shared.c b/src/providers/ldap/sdap_sudo_shared.c new file mode 100644 index 0000000..2ddfbd7 --- /dev/null +++ b/src/providers/ldap/sdap_sudo_shared.c @@ -0,0 +1,227 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include + +#include "util/util.h" +#include "providers/be_ptask.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_sudo_shared.h" +#include "db/sysdb_sudo.h" + +errno_t +sdap_sudo_ptask_setup_generic(struct be_ctx *be_ctx, + struct dp_option *opts, + be_ptask_send_t full_send_fn, + be_ptask_recv_t full_recv_fn, + be_ptask_send_t smart_send_fn, + be_ptask_recv_t smart_recv_fn, + void *pvt, + struct be_ptask **_full_refresh, + struct be_ptask **_smart_refresh) +{ + time_t smart; + time_t full; + time_t delay; + time_t last_refresh; + time_t offset; + errno_t ret; + + smart = dp_opt_get_int(opts, SDAP_SUDO_SMART_REFRESH_INTERVAL); + full = dp_opt_get_int(opts, SDAP_SUDO_FULL_REFRESH_INTERVAL); + offset = dp_opt_get_int(opts, SDAP_SUDO_RANDOM_OFFSET); + + if (smart == 0 && full == 0) { + /* We don't allow both types to be disabled. At least smart refresh + * needs to be enabled. In this case smart refresh will catch up new + * and modified rules and deleted rules are caught when expired. */ + smart = opts[SDAP_SUDO_SMART_REFRESH_INTERVAL].def_val.number; + + DEBUG(SSSDBG_CONF_SETTINGS, "At least smart refresh needs to be " + "enabled. Setting smart refresh interval to default value " + "(%"SPRItime") seconds.\n", smart); + } else if (full > 0 && full <= smart) { + /* In this case it does not make any sense to run smart refresh. */ + smart = 0; + + DEBUG(SSSDBG_CONF_SETTINGS, "Smart refresh interval has to be lower " + "than full refresh interval. Periodical smart refresh will be " + "disabled.\n"); + } + + ret = sysdb_sudo_get_last_full_refresh(be_ctx->domain, &last_refresh); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to obtain time of last full " + "refresh. Assuming none was performed so far.\n"); + last_refresh = 0; + } + + if (last_refresh == 0) { + /* If this is the first startup, we need to kick off an refresh + * immediately, to close a window where clients requesting sudo + * information won't get an immediate reply with no entries */ + delay = 0; + } else { + /* At least one update has previously run, so clients will get cached + * data. We will delay the refresh so we don't slow down the startup + * process if this is happening during system boot. */ + delay = 10; + } + + /* Full refresh. + * + * Disable when offline and run immediately when SSSD goes back online. + * Since we have periodical online check we don't have to run this task + * when offline. */ + if (full > 0) { + ret = be_ptask_create(be_ctx, be_ctx, full, delay, 0, offset, full, 0, + full_send_fn, full_recv_fn, pvt, + "SUDO Full Refresh", + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_SCHEDULE_FROM_LAST, + _full_refresh); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup full refresh ptask " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } + + /* Smart refresh. + * + * Disable when offline and reschedule normally when SSSD goes back online. + * Since we have periodical online check we don't have to run this task + * when offline. */ + if (smart > 0) { + ret = be_ptask_create(be_ctx, be_ctx, smart, delay + smart, smart, + offset, smart, 0, + smart_send_fn, smart_recv_fn, pvt, + "SUDO Smart Refresh", + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_SCHEDULE_FROM_LAST, + _smart_refresh); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup smart refresh ptask " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } + + return EOK; +} + +static char * +sdap_sudo_new_usn(TALLOC_CTX *mem_ctx, + unsigned long usn, + const char *leftover) +{ + const char *str = leftover == NULL ? "" : leftover; + char *newusn; + + /* Current largest USN is unknown so we keep "0" to indicate it. */ + if (usn == 0) { + return talloc_strdup(mem_ctx, "0"); + } + + /* We increment USN number so that we can later use simplified filter + * (just usn >= last+1 instead of usn >= last && usn != last). + */ + usn++; + + /* Convert back to string appending non-converted values since it + * is an indicator that modifyTimestamp is used instead of entryUSN. + * modifyTimestamp contains also timezone specification, usually Z. + * We can't really handle any errors here so we just use what we got. */ + newusn = talloc_asprintf(mem_ctx, "%lu%s", usn, str); + if (newusn == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to change USN value (OOM)!\n"); + return NULL; + } + + return newusn; +} + +void +sdap_sudo_set_usn(struct sdap_server_opts *srv_opts, + const char *usn) +{ + unsigned long usn_number; + char *newusn; + char *timezone = NULL; + errno_t ret; + + if (srv_opts == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Bug: srv_opts is NULL\n"); + return; + } + + if (usn == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Bug: usn is NULL\n"); + return; + } + + /* If usn == 0 it means that no new rules were found. We will use last known + * USN number as the new highest value. However, we need to get the timezone + * information in case this is a modify timestamp attribute instead of usn. + */ + if (!srv_opts->supports_usn && strcmp("0", usn) == 0) { + usn_number = 0; + + /* The value may not be defined yet. */ + if (srv_opts->max_sudo_value == NULL) { + timezone = NULL; + } else { + errno = 0; + strtoul(srv_opts->max_sudo_value, &timezone, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to convert USN %s [%d]: %s\n", + srv_opts->max_sudo_value, ret, sss_strerror(ret)); + return; + } + } + } else { + errno = 0; + usn_number = strtoul(usn, &timezone, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to convert USN %s [%d]: %s\n", + usn, ret, sss_strerror(ret)); + return; + } + } + + if (usn_number > srv_opts->last_usn) { + srv_opts->last_usn = usn_number; + } + + newusn = sdap_sudo_new_usn(srv_opts, srv_opts->last_usn, timezone); + if (newusn == NULL) { + return; + } + + talloc_zfree(srv_opts->max_sudo_value); + srv_opts->max_sudo_value = newusn; + + DEBUG(SSSDBG_FUNC_DATA, "SUDO higher USN value: [%s]\n", + srv_opts->max_sudo_value); +} diff --git a/src/providers/ldap/sdap_sudo_shared.h b/src/providers/ldap/sdap_sudo_shared.h new file mode 100644 index 0000000..846f3f8 --- /dev/null +++ b/src/providers/ldap/sdap_sudo_shared.h @@ -0,0 +1,42 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2015 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_SUDO_SHARED_H_ +#define _SDAP_SUDO_SHARED_H_ + +#include "providers/backend.h" +#include "providers/be_ptask.h" + +errno_t +sdap_sudo_ptask_setup_generic(struct be_ctx *be_ctx, + struct dp_option *opts, + be_ptask_send_t full_send_fn, + be_ptask_recv_t full_recv_fn, + be_ptask_send_t smart_send_fn, + be_ptask_recv_t smart_recv_fn, + void *pvt, + struct be_ptask **_full_refresh, + struct be_ptask **_smart_refresh); + +void +sdap_sudo_set_usn(struct sdap_server_opts *srv_opts, + const char *usn); + +#endif /* _SDAP_SUDO_SHARED_H_ */ diff --git a/src/providers/ldap/sdap_users.h b/src/providers/ldap/sdap_users.h new file mode 100644 index 0000000..74284cd --- /dev/null +++ b/src/providers/ldap/sdap_users.h @@ -0,0 +1,42 @@ +/* + SSSD + + Async LDAP Helper routines + + Copyright (C) Simo Sorce + + 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 3 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; 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, see . +*/ + +#ifndef _SDAP_USERS_H_ +#define _SDAP_USERS_H_ + +#include "config.h" + +/* shared non-async user functions */ + +errno_t sdap_fallback_local_user(TALLOC_CTX *memctx, + const char *name, uid_t uid, + struct sysdb_attrs ***reply); + +int sdap_save_user(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + struct sysdb_attrs *mapped_attrs, + char **_usn_value, + time_t now, + bool set_non_posix); + +#endif /* _SDAP_USERS_H_ */ diff --git a/src/providers/ldap/sdap_utils.c b/src/providers/ldap/sdap_utils.c new file mode 100644 index 0000000..6d54310 --- /dev/null +++ b/src/providers/ldap/sdap_utils.c @@ -0,0 +1,235 @@ +/* + Authors: + Simo Sorce + + Copyright (C) 2013 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include "util/util.h" +#include "providers/ldap/sdap_async.h" + +errno_t +sdap_attrs_add_ldap_attr(struct sysdb_attrs *ldap_attrs, + const char *attr_name, + const char *attr_desc, + bool multivalued, + const char *name, + struct sysdb_attrs *attrs) +{ + errno_t ret; + struct ldb_message_element *el; + const char *objname = name ?: "object"; + const char *desc = attr_desc ?: attr_name; + unsigned int num_values, i; + char *printable; + + ret = sysdb_attrs_get_el(ldap_attrs, attr_name, &el); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get %s from the " + "list of the LDAP attributes [%d]: %s\n", + attr_name, ret, strerror(ret)); + return ret; + } + + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, "%s is not available " + "for [%s].\n", desc, objname); + } else { + num_values = multivalued ? el->num_values : 1; + for (i = 0; i < num_values; i++) { + printable = ldb_binary_encode(ldap_attrs, el->values[i]); + if (printable == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldb_binary_encode failed..\n"); + continue; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Adding %s [%s] to attributes " + "of [%s].\n", desc, printable, objname); + + talloc_zfree(printable); + + ret = sysdb_attrs_add_mem(attrs, attr_name, el->values[i].data, + el->values[i].length); + if (ret) { + return ret; + } + } + } + + return EOK; +} + +errno_t +sdap_save_all_names(const char *name, + struct sysdb_attrs *ldap_attrs, + struct sss_domain_info *dom, + enum sysdb_member_type entry_type, + struct sysdb_attrs *attrs) +{ + const char **aliases = NULL; + const char *sysdb_alias; + errno_t ret; + TALLOC_CTX *tmp_ctx; + int i; + bool lowercase = !dom->case_sensitive; + bool store_as_fqdn; + + switch (entry_type) { + case SYSDB_MEMBER_USER: + case SYSDB_MEMBER_GROUP: + store_as_fqdn = true; + break; + default: + store_as_fqdn = false; + break; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_aliases(tmp_ctx, ldap_attrs, name, + lowercase, &aliases); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get the alias list\n"); + goto done; + } + + for (i = 0; aliases[i]; i++) { + if (store_as_fqdn) { + sysdb_alias = sss_create_internal_fqname(tmp_ctx, aliases[i], + dom->name); + } else { + sysdb_alias = aliases[i]; + } + + if (sysdb_alias == NULL) { + ret = ENOMEM; + goto done; + } + + if (lowercase) { + ret = sysdb_attrs_add_lc_name_alias(attrs, sysdb_alias); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add lower-cased version " + "of alias [%s] into the " + "attribute list\n", aliases[i]); + goto done; + } + } else { + ret = sysdb_attrs_add_string(attrs, SYSDB_NAME_ALIAS, sysdb_alias); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add alias [%s] into the " + "attribute list\n", aliases[i]); + goto done; + } + } + + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t deref_string_to_val(const char *str, int *val) +{ + if (strcasecmp(str, "never") == 0) { + *val = LDAP_DEREF_NEVER; + } else if (strcasecmp(str, "searching") == 0) { + *val = LDAP_DEREF_SEARCHING; + } else if (strcasecmp(str, "finding") == 0) { + *val = LDAP_DEREF_FINDING; + } else if (strcasecmp(str, "always") == 0) { + *val = LDAP_DEREF_ALWAYS; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Illegal deref option [%s].\n", str); + return EINVAL; + } + + return EOK; +} + +static char * +sdap_combine_filters_ex(TALLOC_CTX *mem_ctx, + char operator, + const char *base_filter, + const char *extra_filter) +{ + char *filter = NULL; + + if (extra_filter == NULL || extra_filter[0] == '\0') { + return talloc_strdup(mem_ctx, base_filter); + } else if (base_filter == NULL || base_filter[0] == '\0') { + return talloc_strdup(mem_ctx, extra_filter); + } + + if (extra_filter[0] == '(') { + filter = talloc_asprintf(mem_ctx, "(%c%s%s)", + operator, base_filter, extra_filter); + } else { + filter = talloc_asprintf(mem_ctx, "(%c%s(%s))", + operator, base_filter, extra_filter); + } + + return filter; /* NULL or not */ +} + +char *sdap_or_filters(TALLOC_CTX *mem_ctx, + const char *base_filter, + const char *extra_filter) +{ + return sdap_combine_filters_ex(mem_ctx, '|', base_filter, extra_filter); +} + +char *sdap_combine_filters(TALLOC_CTX *mem_ctx, + const char *base_filter, + const char *extra_filter) +{ + return sdap_combine_filters_ex(mem_ctx, '&', base_filter, extra_filter); +} + +char *get_enterprise_principal_string_filter(TALLOC_CTX *mem_ctx, + const char *attr_name, + const char *princ, + struct dp_option *sdap_basic_opts) +{ + const char *realm; + char *p; + + if (attr_name == NULL || princ == NULL || sdap_basic_opts == NULL) { + return NULL; + } + + realm = dp_opt_get_cstring(sdap_basic_opts, SDAP_KRB5_REALM); + if (realm == NULL) { + return NULL; + } + + p = strchr(princ, '@'); + if (p == NULL) { + return NULL; + } + + return talloc_asprintf(mem_ctx, "(%s=%.*s\\\\@%s@%s)", attr_name, + (int) (p - princ), + princ, + p + 1, realm); +} diff --git a/src/providers/proxy/proxy.h b/src/providers/proxy/proxy.h new file mode 100644 index 0000000..6246eba --- /dev/null +++ b/src/providers/proxy/proxy.h @@ -0,0 +1,187 @@ +/* + SSSD + + Proxy provider, private header file + + Authors: + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#ifndef __PROXY_H__ +#define __PROXY_H__ + +#include +#include +#include + +#include +#include + +#include "util/util.h" +#include "util/nss_dl_load.h" +#include "providers/backend.h" +#include "db/sysdb.h" +#include +#include "sss_iface/sss_iface_async.h" + +#define PROXY_CHILD_PATH "/org/freedesktop/sssd/proxychild" + +struct authtok_conv { + struct sss_auth_token *authtok; + struct sss_auth_token *newauthtok; + + bool sent_old; +}; + +struct proxy_id_ctx { + struct be_ctx *be; + bool fast_alias; + struct sss_nss_ops ops; + struct sss_certmap_ctx *sss_certmap_ctx; +}; + +struct proxy_auth_ctx { + struct be_ctx *be; + char *pam_target; + + uint32_t max_children; + uint32_t running; + uint32_t next_id; + hash_table_t *request_table; + int timeout_ms; +}; + +struct proxy_resolver_ctx { + struct sss_nss_ops ops; +}; + +struct proxy_module_ctx { + struct proxy_id_ctx *id_ctx; + struct proxy_auth_ctx *auth_ctx; + struct proxy_resolver_ctx *resolver_ctx; +}; + +struct proxy_child_ctx { + struct proxy_auth_ctx *auth_ctx; + struct be_req *be_req; + struct pam_data *pd; + + uint32_t id; + pid_t pid; + bool running; + + struct sbus_connection *conn; + struct tevent_timer *timer; + + struct tevent_req *init_req; +}; + +struct pc_init_ctx { + char *command; + pid_t pid; + struct tevent_timer *timeout; + struct tevent_signal *sige; + struct proxy_child_ctx *child_ctx; + struct sbus_connection *conn; +}; + +#define PROXY_CHILD_PIPE "private/proxy_child" +#define DEFAULT_BUFSIZE 4096 +#define MAX_BUF_SIZE 1024*1024 /* max 1MiB */ + +/* From proxy_id.c */ +struct tevent_req * +proxy_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params); + +errno_t proxy_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +/* From proxy_auth.c */ +struct tevent_req * +proxy_pam_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_auth_ctx *proxy_auth_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +proxy_pam_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +/* From proxy_netgroup.c */ +errno_t get_netgroup(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *name); + +errno_t get_serv_byname(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *name, + const char *protocol); + +errno_t +get_serv_byport(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *be_filter, + const char *protocol); + +errno_t enum_services(struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom); + +/* From proxy_hosts.c */ +struct tevent_req * +proxy_hosts_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *proxy_resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params); + +errno_t +proxy_hosts_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +/* From proxy_ipnetworks.c */ +struct tevent_req * +proxy_nets_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *proxy_resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params); + +errno_t +proxy_nets_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +errno_t +proxy_client_init(struct sbus_connection *conn, + struct proxy_auth_ctx *auth_ctx); + +errno_t proxy_init_certmap(TALLOC_CTX *mem_ctx, struct proxy_id_ctx *id_ctx); + + +errno_t proxy_map_cert_to_user(struct proxy_id_ctx *id_ctx, + struct dp_id_data *data); + +int get_pw_name(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *i_name); +#endif /* __PROXY_H__ */ diff --git a/src/providers/proxy/proxy_auth.c b/src/providers/proxy/proxy_auth.c new file mode 100644 index 0000000..7f6f3f2 --- /dev/null +++ b/src/providers/proxy/proxy_auth.c @@ -0,0 +1,877 @@ +/* + SSSD + + proxy_auth.c + + Authors: + Stephen Gallagher + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include + +#include "providers/proxy/proxy.h" +#include "sss_iface/sss_iface_async.h" +#include "util/sss_chain_id.h" + +struct pc_init_ctx; + +static int proxy_child_destructor(TALLOC_CTX *ctx) +{ + struct proxy_child_ctx *child_ctx = + talloc_get_type(ctx, struct proxy_child_ctx); + hash_key_t key; + int hret; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Removing proxy child id [%d]\n", child_ctx->id); + key.type = HASH_KEY_ULONG; + key.ul = child_ctx->id; + hret = hash_delete(child_ctx->auth_ctx->request_table, &key); + if (!(hret == HASH_SUCCESS || + hret == HASH_ERROR_KEY_NOT_FOUND)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Hash error [%d][%s]\n", hret, hash_error_string(hret)); + /* Nothing we can do about this, so just continue */ + } + return 0; +} + +static struct tevent_req *proxy_child_init_send(TALLOC_CTX *mem_ctx, + struct proxy_child_ctx *child_ctx, + struct proxy_auth_ctx *auth_ctx); +static void proxy_child_init_done(struct tevent_req *subreq); +static struct tevent_req *proxy_child_send(TALLOC_CTX *mem_ctx, + struct proxy_auth_ctx *auth_ctx, + struct pam_data *pd) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct proxy_child_ctx *state; + int hret; + hash_key_t key; + hash_value_t value; + uint32_t first; + + req = tevent_req_create(mem_ctx, &state, struct proxy_child_ctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->auth_ctx = auth_ctx; + state->pd = pd; + + /* Find an available key */ + key.type = HASH_KEY_ULONG; + key.ul = auth_ctx->next_id; + + first = auth_ctx->next_id; + while (auth_ctx->next_id == 0 || + hash_has_key(auth_ctx->request_table, &key)) { + /* Handle overflow, zero is a reserved value + * Also handle the unlikely case where the next ID + * is still awaiting being run + */ + auth_ctx->next_id++; + key.ul = auth_ctx->next_id; + + if (auth_ctx->next_id == first) { + /* We've looped through all possible integers! */ + DEBUG(SSSDBG_FATAL_FAILURE, "Serious error: queue is too long!\n"); + talloc_zfree(req); + return NULL; + } + } + + state->id = auth_ctx->next_id; + auth_ctx->next_id++; + + value.type = HASH_VALUE_PTR; + value.ptr = req; + DEBUG(SSSDBG_TRACE_INTERNAL, "Queueing request [%lu]\n", key.ul); + hret = hash_enter(auth_ctx->request_table, + &key, &value); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not add request to the queue\n"); + talloc_zfree(req); + return NULL; + } + + talloc_set_destructor((TALLOC_CTX *) state, + proxy_child_destructor); + + if (auth_ctx->running < auth_ctx->max_children) { + /* There's an available slot; start a child + * to handle the request + */ + + auth_ctx->running++; + subreq = proxy_child_init_send(auth_ctx, state, auth_ctx); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not fork child process\n"); + auth_ctx->running--; + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, proxy_child_init_done, req); + + state->running = true; + } + else { + /* If there was no available slot, it will be queued + * until a slot is available + */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "All available child slots are full, queuing request\n"); + } + return req; +} + +static int pc_init_destructor (TALLOC_CTX *ctx) +{ + struct pc_init_ctx *init_ctx = + talloc_get_type(ctx, struct pc_init_ctx); + + /* If the init request has died, forcibly kill the child */ + kill(init_ctx->pid, SIGKILL); + return 0; +} + +static void pc_init_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt); +static void pc_init_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr); +static struct tevent_req *proxy_child_init_send(TALLOC_CTX *mem_ctx, + struct proxy_child_ctx *child_ctx, + struct proxy_auth_ctx *auth_ctx) +{ + struct tevent_req *req; + struct pc_init_ctx *state; + char **proxy_child_args; + struct timeval tv; + errno_t ret; + pid_t pid; + + req = tevent_req_create(mem_ctx, &state, struct pc_init_ctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not create tevent_req\n"); + return NULL; + } + + state->child_ctx = child_ctx; + + state->command = talloc_asprintf(req, + "%s/proxy_child -d %#.4x --debug-timestamps=%d " + "--debug-microseconds=%d --logger=%s --domain %s --id %d " + "--chain-id=%lu", + SSSD_LIBEXEC_PATH, debug_level, debug_timestamps, + debug_microseconds, sss_logger_str[sss_logger], + auth_ctx->be->domain->name, + child_ctx->id, sss_chain_id_get()); + if (state->command == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + return NULL; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Starting proxy child with args [%s]\n", state->command); + + pid = fork(); + if (pid < 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fork failed [%d][%s].\n", ret, strerror(ret)); + talloc_zfree(req); + return NULL; + } + + if (pid == 0) { /* child */ + proxy_child_args = parse_args(state->command); + execvp(proxy_child_args[0], proxy_child_args); + + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not start proxy child [%s]: [%d][%s].\n", + state->command, ret, strerror(ret)); + + _exit(1); + } + + else { /* parent */ + state->pid = pid; + /* Make sure to kill the child process if we abort */ + talloc_set_destructor((TALLOC_CTX *)state, pc_init_destructor); + + state->sige = tevent_add_signal(auth_ctx->be->ev, req, + SIGCHLD, 0, + pc_init_sig_handler, req); + if (state->sige == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_signal failed.\n"); + talloc_zfree(req); + return NULL; + } + + /* Save the init request to the child context. + * This is technically a layering violation, + * but it's the only sane way to be able to + * identify which client is which when it + * connects to the backend in + * client_registration() + */ + child_ctx->init_req = req; + + /* Wait six seconds for the child to connect + * This is because the connection handler will add + * its own five-second timeout, and we don't want to + * be faster here. + */ + tv = tevent_timeval_current_ofs(6, 0); + state->timeout = tevent_add_timer(auth_ctx->be->ev, req, + tv, pc_init_timeout, req); + + /* processing will continue once the connection is received + * in proxy_client_init() + */ + return req; + } +} + +static void pc_init_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt) +{ + int ret; + int child_status; + struct tevent_req *req; + struct pc_init_ctx *init_ctx; + + if (count <= 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "SIGCHLD handler called with invalid child count\n"); + return; + } + + req = talloc_get_type(pvt, struct tevent_req); + init_ctx = tevent_req_data(req, struct pc_init_ctx); + + DEBUG(SSSDBG_TRACE_LIBS, "Waiting for child [%d].\n", init_ctx->pid); + + errno = 0; + ret = waitpid(init_ctx->pid, &child_status, WNOHANG); + + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "waitpid failed [%d][%s].\n", ret, strerror(ret)); + } else if (ret == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "waitpid did not find a child with changed status.\n"); + } else { + if (WIFEXITED(child_status)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "child [%d] exited with status [%d].\n", ret, + WEXITSTATUS(child_status)); + tevent_req_error(req, EIO); + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "child [%d] was terminate by signal [%d].\n", ret, + WTERMSIG(child_status)); + tevent_req_error(req, EIO); + } else { + if (WIFSTOPPED(child_status)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "child [%d] was stopped by signal [%d].\n", ret, + WSTOPSIG(child_status)); + } + if (WIFCONTINUED(child_status) == true) { + DEBUG(SSSDBG_CRIT_FAILURE, + "child [%d] was resumed by delivery of SIGCONT.\n", + ret); + } + DEBUG(SSSDBG_CRIT_FAILURE, + "Child is still running, no new child is started.\n"); + return; + } + } +} + +static void pc_init_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct tevent_req *req; + + DEBUG(SSSDBG_OP_FAILURE, "Client timed out before Identification!\n"); + req = talloc_get_type(ptr, struct tevent_req); + tevent_req_error(req, ETIMEDOUT); +} + +static errno_t proxy_child_init_recv(struct tevent_req *req, + pid_t *pid, + struct sbus_connection **conn) +{ + struct pc_init_ctx *state; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + state = tevent_req_data(req, struct pc_init_ctx); + + /* Unset the destructor since we initialized successfully. + * We don't want to kill the child now that it's properly + * set up. + */ + talloc_set_destructor((TALLOC_CTX *)state, NULL); + + *pid = state->pid; + *conn = state->conn; + + return EOK; +} + +struct proxy_child_sig_ctx { + struct proxy_auth_ctx *auth_ctx; + pid_t pid; + struct tevent_req *req; +}; +static void proxy_child_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt); +static struct tevent_req * +proxy_pam_conv_send(TALLOC_CTX *mem_ctx, struct proxy_auth_ctx *auth_ctx, + struct sbus_connection *conn, + struct proxy_child_sig_ctx *sig_ctx, struct pam_data *pd, + pid_t pid, uint32_t id); +static void proxy_child_init_conv_done(struct tevent_req *subreq); +static void proxy_child_init_done(struct tevent_req *subreq) { + int ret; + struct tevent_signal *sige; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct proxy_child_ctx *child_ctx = + tevent_req_data(req, struct proxy_child_ctx); + struct proxy_child_sig_ctx *sig_ctx; + + ret = proxy_child_init_recv(subreq, &child_ctx->pid, &child_ctx->conn); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Proxy child init failed [%d]\n", ret); + tevent_req_error(req, ret); + return; + } + + sig_ctx = talloc_zero(child_ctx->auth_ctx, struct proxy_child_sig_ctx); + if (sig_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + + /* An initialized child is available, awaiting the PAM command */ + subreq = proxy_pam_conv_send(req, child_ctx->auth_ctx, + child_ctx->conn, sig_ctx, child_ctx->pd, + child_ctx->pid, child_ctx->id); + if (!subreq) { + talloc_free(sig_ctx); + DEBUG(SSSDBG_CRIT_FAILURE,"Could not start PAM conversation\n"); + tevent_req_error(req, EIO); + return; + } + tevent_req_set_callback(subreq, proxy_child_init_conv_done, req); + + /* Add a signal handler for the child under the auth_ctx, + * that way if the child exits after completion of the + * request, it will still be handled. + */ + sig_ctx->auth_ctx = child_ctx->auth_ctx; + sig_ctx->pid = child_ctx->pid; + + sige = tevent_add_signal(child_ctx->auth_ctx->be->ev, + child_ctx->auth_ctx, + SIGCHLD, 0, + proxy_child_sig_handler, + sig_ctx); + if (sige == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_signal failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + + /* Steal the signal context onto the signal event + * so that when the signal is freed, the context + * will go with it. + */ + talloc_steal(sige, sig_ctx); +} + +static void remove_sige(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt); +static void run_proxy_child_queue(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt); +static void proxy_child_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt) +{ + int ret; + int child_status; + struct proxy_child_sig_ctx *sig_ctx; + struct tevent_immediate *imm; + struct tevent_immediate *imm2; + + if (count <= 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "SIGCHLD handler called with invalid child count\n"); + return; + } + + sig_ctx = talloc_get_type(pvt, struct proxy_child_sig_ctx); + DEBUG(SSSDBG_TRACE_LIBS, "Waiting for child [%d].\n", sig_ctx->pid); + + errno = 0; + ret = waitpid(sig_ctx->pid, &child_status, WNOHANG); + + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "waitpid failed [%d][%s].\n", ret, strerror(ret)); + } else if (ret == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "waitpid did not find a child with changed status.\n"); + } else { + if (WIFEXITED(child_status)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "child [%d] exited with status [%d].\n", ret, + WEXITSTATUS(child_status)); + } else if (WIFSIGNALED(child_status) == true) { + DEBUG(SSSDBG_CONF_SETTINGS, + "child [%d] was terminated by signal [%d].\n", ret, + WTERMSIG(child_status)); + } else { + if (WIFSTOPPED(child_status)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "child [%d] was stopped by signal [%d].\n", ret, + WSTOPSIG(child_status)); + } + if (WIFCONTINUED(child_status) == true) { + DEBUG(SSSDBG_CRIT_FAILURE, + "child [%d] was resumed by delivery of SIGCONT.\n", + ret); + } + DEBUG(SSSDBG_CRIT_FAILURE, + "Child is still running, no new child is started.\n"); + return; + } + + /* Free request if it is still running */ + if (sig_ctx->req != NULL) { + tevent_req_error(sig_ctx->req, ERR_PROXY_CHILD_SIGNAL); + } + + imm = tevent_create_immediate(ev); + if (imm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_create_immediate failed.\n"); + return; + } + + tevent_schedule_immediate(imm, ev, run_proxy_child_queue, + sig_ctx->auth_ctx); + + /* schedule another immediate timer to delete the sigchld handler */ + imm2 = tevent_create_immediate(ev); + if (imm2 == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_create_immediate failed.\n"); + return; + } + + tevent_schedule_immediate(imm2, ev, remove_sige, sige); + } + + return; +} + +static void remove_sige(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + talloc_free(pvt); +} + +struct proxy_conv_ctx { + struct proxy_auth_ctx *auth_ctx; + struct sbus_connection *conn; + struct proxy_child_sig_ctx *sig_ctx; + struct pam_data *pd; + pid_t pid; +}; + +static void proxy_pam_conv_done(struct tevent_req *subreq); + +static struct tevent_req * +proxy_pam_conv_send(TALLOC_CTX *mem_ctx, struct proxy_auth_ctx *auth_ctx, + struct sbus_connection *conn, + struct proxy_child_sig_ctx *sig_ctx, struct pam_data *pd, + pid_t pid, uint32_t id) +{ + struct proxy_conv_ctx *state; + struct tevent_req *req; + struct tevent_req *subreq; + char *sbus_cliname; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct proxy_conv_ctx); + if (req == NULL) { + return NULL; + } + + state->auth_ctx = auth_ctx; + state->conn = conn; + state->sig_ctx = sig_ctx; + state->pd = pd; + state->pid = pid; + + sbus_cliname = sss_iface_proxy_bus(state, id); + if (sbus_cliname == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Sending request with the following data:\n"); + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + + subreq = sbus_call_proxy_auth_PAM_send(state, state->conn, sbus_cliname, + SSS_BUS_PATH, pd); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + state->sig_ctx->req = subreq; + + tevent_req_set_callback(subreq, proxy_pam_conv_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_post(req, auth_ctx->be->ev); + tevent_req_error(req, ret); + } + + return req; +} + +static void proxy_pam_conv_done(struct tevent_req *subreq) +{ + struct pam_data *response; + struct response_data *resp; + struct proxy_conv_ctx *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct proxy_conv_ctx); + + state->sig_ctx->req = NULL; + + ret = sbus_call_proxy_auth_PAM_recv(state, subreq, &response); + talloc_zfree(subreq); + + /* Kill the child */ + kill(state->pid, SIGKILL); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get reply from child [%d]: %s\n", + ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SYSTEM_ERR; + tevent_req_error(req, ret); + return; + } + + state->pd->pam_status = response->pam_status; + state->pd->account_locked = response->account_locked; + + for (resp = response->resp_list; resp != NULL; resp = resp->next) { + talloc_steal(state->pd, resp); + + if (resp->next == NULL) { + resp->next = state->pd->resp_list; + state->pd->resp_list = response->resp_list; + break; + } + } + + DEBUG(SSSDBG_CONF_SETTINGS, "received: [%d][%s]\n", + state->pd->pam_status, + state->pd->domain); + + tevent_req_done(req); +} + +static errno_t proxy_pam_conv_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static void proxy_child_init_conv_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = proxy_pam_conv_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Proxy PAM conversation failed [%d]\n", ret); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int proxy_child_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct pam_data **pd) +{ + struct proxy_child_ctx *ctx; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + ctx = tevent_req_data(req, struct proxy_child_ctx); + *pd = talloc_steal(mem_ctx, ctx->pd); + + return EOK; +} + +static void run_proxy_child_queue(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct proxy_auth_ctx *auth_ctx; + struct hash_iter_context_t *iter; + struct hash_entry_t *entry; + struct tevent_req *req = NULL; + struct tevent_req *subreq; + struct proxy_child_ctx *state = NULL; + + auth_ctx = talloc_get_type(pvt, struct proxy_auth_ctx); + + /* Launch next queued request */ + iter = new_hash_iter_context(auth_ctx->request_table); + if (iter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "new_hash_iter_context failed.\n"); + return; + } + + while ((entry = iter->next(iter)) != NULL) { + req = talloc_get_type(entry->value.ptr, struct tevent_req); + state = tevent_req_data(req, struct proxy_child_ctx); + if (!state->running) { + break; + } + } + free(iter); + + if (!entry) { + /* Nothing pending on the queue */ + return; + } + + if (auth_ctx->running < auth_ctx->max_children) { + /* There's an available slot; start a child + * to handle the request + */ + auth_ctx->running++; + subreq = proxy_child_init_send(auth_ctx, state, auth_ctx); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not fork child process\n"); + auth_ctx->running--; + talloc_zfree(req); + return; + } + tevent_req_set_callback(subreq, proxy_child_init_done, req); + + state->running = true; + } +} + +struct proxy_pam_handler_state { + struct pam_data *pd; + struct proxy_auth_ctx *auth_ctx; + struct be_ctx *be_ctx; +}; + +static void proxy_pam_handler_done(struct tevent_req *subreq); + +struct tevent_req * +proxy_pam_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_auth_ctx *proxy_auth_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct proxy_pam_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct proxy_pam_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->auth_ctx = proxy_auth_ctx; + state->be_ctx = params->be_ctx; + + /* Tell frontend that we do not support Smartcard authentication */ + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_KEYPAD) { + if (pd->cmd == SSS_PAM_PREAUTH) { + /* just return success and let the PAM responder figure out if + * local Smartcard authentication is available. */ + pd->pam_status = PAM_SUCCESS; + } else { + pd->pam_status = PAM_BAD_ITEM; + } + goto immediately; + } + + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + case SSS_PAM_CHAUTHTOK: + case SSS_PAM_CHAUTHTOK_PRELIM: + case SSS_PAM_ACCT_MGMT: + /* Queue the request and spawn a child if there is an available slot. */ + subreq = proxy_child_send(state, proxy_auth_ctx, state->pd); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + tevent_req_set_callback(subreq, proxy_pam_handler_done, req); + break; + case SSS_PAM_SETCRED: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + pd->pam_status = PAM_SUCCESS; + goto immediately; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported PAM task %d\n", pd->cmd); + pd->pam_status = PAM_MODULE_UNKNOWN; + goto immediately; + } + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void proxy_pam_handler_done(struct tevent_req *subreq) +{ + struct proxy_pam_handler_state *state; + struct tevent_immediate *imm; + struct tevent_req *req; + const char *password; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct proxy_pam_handler_state); + + ret = proxy_child_recv(subreq, state, &state->pd); + + /* During the proxy_child_send request SIGKILL will be sent to the child + * process unconditionally, so we can assume here that the child process + * is gone even if the request returns an error. */ + state->auth_ctx->running--; + + talloc_zfree(subreq); + if (ret != EOK) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + /* Start the next auth in the queue, if any */ + imm = tevent_create_immediate(state->be_ctx->ev); + if (imm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_create_immediate failed.\n"); + /* We'll still finish the current request, but we're + * likely to have problems if there are queued events + * if we've gotten into this state. + * Hopefully this is impossible, since freeing req + * above should guarantee that we have enough memory + * to create this immediate event. + */ + } else { + tevent_schedule_immediate(imm, state->be_ctx->ev, + run_proxy_child_queue, + state->auth_ctx); + } + + /* Check if we need to save the cached credentials */ + if ((state->pd->cmd == SSS_PAM_AUTHENTICATE || state->pd->cmd == SSS_PAM_CHAUTHTOK) + && (state->pd->pam_status == PAM_SUCCESS) && state->be_ctx->domain->cache_credentials) { + + ret = sss_authtok_get_password(state->pd->authtok, &password, NULL); + if (ret) { + /* password caching failures are not fatal errors */ + DEBUG(SSSDBG_OP_FAILURE, "Failed to cache password\n"); + goto done; + } + + ret = sysdb_cache_password(state->be_ctx->domain, state->pd->user, password); + + /* password caching failures are not fatal errors */ + /* so we just log it any return */ + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to cache password (%d)[%s]!?\n", + ret, sss_strerror(ret)); + } + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +proxy_pam_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct proxy_pam_handler_state *state = NULL; + + state = tevent_req_data(req, struct proxy_pam_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/proxy/proxy_certmap.c b/src/providers/proxy/proxy_certmap.c new file mode 100644 index 0000000..55fe39f --- /dev/null +++ b/src/providers/proxy/proxy_certmap.c @@ -0,0 +1,191 @@ +/* + SSSD + + Map certificates to users from the proxy provider + + Copyright (C) 2023 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/proxy/proxy.h" +#include "util/util.h" +#include "util/cert.h" +#include "lib/certmap/sss_certmap.h" + +struct priv_sss_debug { + int level; +}; + +static void ext_debug(void *private, const char *file, long line, + const char *function, const char *format, ...) +{ + va_list ap; + struct priv_sss_debug *data = private; + int level = SSSDBG_OP_FAILURE; + + if (data != NULL) { + level = data->level; + } + + va_start(ap, format); + sss_vdebug_fn(file, line, function, level, APPEND_LINE_FEED, format, ap); + va_end(ap); +} + +errno_t proxy_init_certmap(TALLOC_CTX *mem_ctx, struct proxy_id_ctx *id_ctx) +{ + int ret; + bool hint; + struct certmap_info **certmap_list = NULL; + size_t c; + + ret = sysdb_get_certmap(mem_ctx, id_ctx->be->domain->sysdb, + &certmap_list, &hint); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_certmap failed.\n"); + goto done; + } + + if (certmap_list == NULL || *certmap_list == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No certmap data, nothing to do.\n"); + ret = EOK; + goto done; + } + + ret = sss_certmap_init(mem_ctx, ext_debug, NULL, &id_ctx->sss_certmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + goto done; + } + + for (c = 0; certmap_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Trying to add rule [%s][%d][%s][%s].\n", + certmap_list[c]->name, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule); + + ret = sss_certmap_add_rule(id_ctx->sss_certmap_ctx, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule, + certmap_list[c]->domains); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_certmap_add_rule failed for rule [%s] " + "with error [%d][%s], skipping. " + "Please check for typos and if rule syntax is supported.\n", + certmap_list[c]->name, ret, sss_strerror(ret)); + continue; + } + } + + ret = EOK; + +done: + talloc_free(certmap_list); + + return ret; +} + +errno_t proxy_map_cert_to_user(struct proxy_id_ctx *id_ctx, + struct dp_id_data *data) +{ + errno_t ret; + char *filter; + char *user; + struct ldb_message *msg = NULL; + struct sysdb_attrs *attrs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = sss_cert_derb64_to_ldap_filter(tmp_ctx, data->filter_value, "", + id_ctx->sss_certmap_ctx, + id_ctx->be->domain, &filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter failed.\n"); + goto done; + } + if (filter == NULL || filter[0] != '(' + || filter[strlen(filter) - 1] != ')') { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter returned bad filter [%s].\n", + filter); + ret = EINVAL; + goto done; + } + + filter[strlen(filter) - 1] = '\0'; + user = sss_create_internal_fqname(tmp_ctx, &filter[1], + id_ctx->be->domain->name); + if (user == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Certificate mapped to user: [%s].\n", user); + + ret = sysdb_search_user_by_name(tmp_ctx, id_ctx->be->domain, user, NULL, &msg); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "Mapped user [%s] not found in cache.\n", user); + ret = get_pw_name(id_ctx, id_ctx->be->domain, user); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_pw_name() failed.\n"); + } + ret = sysdb_search_user_by_name(tmp_ctx, id_ctx->be->domain, user, NULL, &msg); + } + + if (ret == EOK) { + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_base64_blob(attrs, SYSDB_USER_MAPPED_CERT, + data->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_base64_blob failed.\n"); + goto done; + } + + ret = sysdb_set_entry_attr(id_ctx->be->domain->sysdb, msg->dn, attrs, + SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_entry_attr failed.\n"); + goto done; + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "Mapped user [%s] not found.\n", user); + goto done; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_name failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} diff --git a/src/providers/proxy/proxy_child.c b/src/providers/proxy/proxy_child.c new file mode 100644 index 0000000..dad65e7 --- /dev/null +++ b/src/providers/proxy/proxy_child.c @@ -0,0 +1,606 @@ +/* + SSSD + + Pam Proxy Child + + Authors: + + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util/util.h" +#include "confdb/confdb.h" +#include "providers/proxy/proxy.h" +#include "sss_iface/sss_iface_async.h" +#include "util/sss_chain_id.h" + +#include "providers/backend.h" + +struct pc_ctx { + struct tevent_context *ev; + struct confdb_ctx *cdb; + struct sss_domain_info *domain; + const char *identity; + const char *conf_path; + struct sbus_connection *mon_conn; + struct sbus_connection *conn; + const char *pam_target; + uint32_t id; +}; + +static int proxy_internal_conv(int num_msg, const struct pam_message **msgm, + struct pam_response **response, + void *appdata_ptr) { + int i; + struct pam_response *reply; + struct authtok_conv *auth_data; + const char *password; + size_t pwlen; + errno_t ret; + + auth_data = talloc_get_type(appdata_ptr, struct authtok_conv); + + if (num_msg <= 0) return PAM_CONV_ERR; + + reply = (struct pam_response *) calloc(num_msg, + sizeof(struct pam_response)); + if (reply == NULL) return PAM_CONV_ERR; + + for (i=0; i < num_msg; i++) { + switch( msgm[i]->msg_style ) { + case PAM_PROMPT_ECHO_OFF: + DEBUG(SSSDBG_CONF_SETTINGS, + "Conversation message: [%s]\n", msgm[i]->msg); + reply[i].resp_retcode = 0; + + ret = sss_authtok_get_password(auth_data->authtok, + &password, &pwlen); + if (ret) goto failed; + reply[i].resp = calloc(pwlen + 1, sizeof(char)); + if (reply[i].resp == NULL) goto failed; + memcpy(reply[i].resp, password, pwlen + 1); + + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Conversation style %d not supported.\n", + msgm[i]->msg_style); + goto failed; + } + } + + *response = reply; + reply = NULL; + + return PAM_SUCCESS; + +failed: + free(reply); + return PAM_CONV_ERR; +} + +static int proxy_chauthtok_conv(int num_msg, const struct pam_message **msgm, + struct pam_response **response, + void *appdata_ptr) { + int i; + struct pam_response *reply; + struct authtok_conv *auth_data; + const char *password; + size_t pwlen; + errno_t ret; + + auth_data = talloc_get_type(appdata_ptr, struct authtok_conv); + + if (num_msg <= 0) return PAM_CONV_ERR; + + reply = (struct pam_response *) calloc(num_msg, + sizeof(struct pam_response)); + if (reply == NULL) return PAM_CONV_ERR; + + for (i=0; i < num_msg; i++) { + switch( msgm[i]->msg_style ) { + case PAM_PROMPT_ECHO_OFF: + DEBUG(SSSDBG_CONF_SETTINGS, + "Conversation message: [%s]\n", msgm[i]->msg); + + reply[i].resp_retcode = 0; + if (!auth_data->sent_old) { + /* The first prompt will be asking for the old authtok */ + ret = sss_authtok_get_password(auth_data->authtok, + &password, &pwlen); + if (ret) goto failed; + reply[i].resp = calloc(pwlen + 1, sizeof(char)); + if (reply[i].resp == NULL) goto failed; + memcpy(reply[i].resp, password, pwlen + 1); + auth_data->sent_old = true; + } + else { + /* Subsequent prompts are looking for the new authtok */ + ret = sss_authtok_get_password(auth_data->newauthtok, + &password, &pwlen); + if (ret) goto failed; + reply[i].resp = calloc(pwlen + 1, sizeof(char)); + if (reply[i].resp == NULL) goto failed; + memcpy(reply[i].resp, password, pwlen + 1); + auth_data->sent_old = true; + } + + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Conversation style %d not supported.\n", + msgm[i]->msg_style); + goto failed; + } + } + + *response = reply; + reply = NULL; + + return PAM_SUCCESS; + +failed: + free(reply); + return PAM_CONV_ERR; +} + +static errno_t call_pam_stack(const char *pam_target, struct pam_data *pd) +{ + int ret; + int pam_status; + pam_handle_t *pamh=NULL; + struct authtok_conv *auth_data; + struct pam_conv conv; + char *shortname; + + if (pd->cmd == SSS_PAM_CHAUTHTOK) { + conv.conv=proxy_chauthtok_conv; + } + else { + conv.conv=proxy_internal_conv; + } + auth_data = talloc_zero(pd, struct authtok_conv); + if (auth_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + auth_data->authtok = sss_authtok_new(auth_data); + if (auth_data->authtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_authtok_new failed.\n"); + ret = ENOMEM; + goto fail; + } + auth_data->newauthtok = sss_authtok_new(auth_data); + if (auth_data->newauthtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_authtok_new failed.\n"); + ret = ENOMEM; + goto fail; + } + + conv.appdata_ptr=auth_data; + + ret = sss_parse_internal_fqname(auth_data, pd->user, &shortname, NULL); + if (ret != EOK) { + goto fail; + } + + ret = pam_start(pam_target, shortname, &conv, &pamh); + if (ret == PAM_SUCCESS) { + DEBUG(SSSDBG_TRACE_LIBS, + "Pam transaction started with service name [%s].\n", + pam_target); + ret = pam_set_item(pamh, PAM_TTY, pd->tty); + if (ret != PAM_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Setting PAM_TTY failed: %s.\n", + pam_strerror(pamh, ret)); + } + ret = pam_set_item(pamh, PAM_RUSER, pd->ruser); + if (ret != PAM_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Setting PAM_RUSER failed: %s.\n", + pam_strerror(pamh, ret)); + } + ret = pam_set_item(pamh, PAM_RHOST, pd->rhost); + if (ret != PAM_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Setting PAM_RHOST failed: %s.\n", + pam_strerror(pamh, ret)); + } + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + sss_authtok_copy(pd->authtok, auth_data->authtok); + pam_status = pam_authenticate(pamh, 0); + break; + case SSS_PAM_SETCRED: + pam_status=pam_setcred(pamh, 0); + break; + case SSS_PAM_ACCT_MGMT: + pam_status=pam_acct_mgmt(pamh, 0); + break; + case SSS_PAM_OPEN_SESSION: + pam_status=pam_open_session(pamh, 0); + break; + case SSS_PAM_CLOSE_SESSION: + pam_status=pam_close_session(pamh, 0); + break; + case SSS_PAM_CHAUTHTOK: + sss_authtok_copy(pd->authtok, auth_data->authtok); + if (pd->priv != 1) { + pam_status = pam_authenticate(pamh, 0); + auth_data->sent_old = false; + if (pam_status != PAM_SUCCESS) break; + } + sss_authtok_copy(pd->newauthtok, auth_data->newauthtok); + pam_status = pam_chauthtok(pamh, 0); + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + if (pd->priv != 1) { + sss_authtok_copy(pd->authtok, auth_data->authtok); + pam_status = pam_authenticate(pamh, 0); + } else { + pam_status = PAM_SUCCESS; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "unknown PAM call %d\n", pd->cmd); + pam_status=PAM_ABORT; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Pam result: [%d][%s]\n", pam_status, + pam_strerror(pamh, pam_status)); + + ret = pam_end(pamh, pam_status); + if (ret != PAM_SUCCESS) { + pamh=NULL; + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot terminate pam transaction.\n"); + } + + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to initialize pam transaction.\n"); + pam_status = PAM_SYSTEM_ERR; + } + + pd->pam_status = pam_status; + + return EOK; +fail: + talloc_free(auth_data); + return ret; +} + +static errno_t +pc_pam_handler(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct pc_ctx *pc_ctx, + struct pam_data *pd, + struct pam_data **_response) +{ + errno_t ret; + + pd->pam_status = PAM_SYSTEM_ERR; + pd->domain = talloc_strdup(pd, pc_ctx->domain->name); + if (pd->domain == NULL) { + exit(ENOMEM); + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Got request with the following data\n"); + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + + ret = call_pam_stack(pc_ctx->pam_target, pd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "call_pam_stack failed.\n"); + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Sending result [%d][%s]\n", + pd->pam_status, pd->domain); + + *_response = pd; + + /* We'll return the message and let the + * parent process kill us. + */ + return ret; +} + +static void proxy_cli_init_done(struct tevent_req *subreq); + +static errno_t +proxy_cli_init(struct pc_ctx *ctx) +{ + TALLOC_CTX *tmp_ctx; + struct tevent_req *subreq; + char *sbus_address; + char *sbus_busname; + char *sbus_cliname; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + SBUS_INTERFACE(iface, + sssd_ProxyChild_Auth, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_ProxyChild_Auth, PAM, pc_pam_handler, ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + struct sbus_path paths[] = { + {SSS_BUS_PATH, &iface}, + {NULL, NULL} + }; + + sbus_address = sss_iface_domain_address(tmp_ctx, ctx->domain); + if (sbus_address == NULL) { + ret = ENOMEM; + goto done; + } + + sbus_busname = sss_iface_domain_bus(tmp_ctx, ctx->domain); + if (sbus_busname == NULL) { + ret = ENOMEM; + goto done; + } + + sbus_cliname = sss_iface_proxy_bus(tmp_ctx, ctx->id); + if (sbus_cliname == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_iface_connect_address(ctx, ctx->ev, sbus_cliname, sbus_address, + NULL, &ctx->conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to connect to %s\n", sbus_address); + goto done; + } + + ret = sbus_connection_add_path_map(ctx->conn, paths); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to add paths [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Sending ID to Proxy Backend: (%"PRIu32")\n", + ctx->id); + + subreq = sbus_call_proxy_client_Register_send(ctx, ctx->conn, sbus_busname, + SSS_BUS_PATH, ctx->id); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, proxy_cli_init_done, NULL); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static void proxy_cli_init_done(struct tevent_req *subreq) +{ + errno_t ret; + + ret = sbus_call_proxy_client_Register_recv(subreq); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to register with proxy provider " + "[%d]: %s\n", ret, sss_strerror(ret)); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Got id ack from proxy child\n"); +} + +int proxy_child_process_init(TALLOC_CTX *mem_ctx, const char *domain, + struct tevent_context *ev, struct confdb_ctx *cdb, + const char *pam_target, uint32_t id) +{ + struct pc_ctx *ctx; + int ret; + + ctx = talloc_zero(mem_ctx, struct pc_ctx); + if (!ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing pc_ctx\n"); + return ENOMEM; + } + ctx->ev = ev; + ctx->cdb = cdb; + ctx->pam_target = talloc_steal(ctx, pam_target); + ctx->id = id; + ctx->conf_path = talloc_asprintf(ctx, CONFDB_DOMAIN_PATH_TMPL, domain); + if (!ctx->conf_path) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!?\n"); + return ENOMEM; + } + + ret = confdb_get_domain(cdb, domain, &ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error retrieving domain configuration\n"); + return ret; + } + + ret = proxy_cli_init(ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up server bus\n"); + return ret; + } + + return EOK; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + char *domain = NULL; + char *srv_name = NULL; + char *conf_entry = NULL; + struct main_context *main_ctx; + int ret; + long id = 0; + long chain_id; + char *pam_target = NULL; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + {"domain", 0, POPT_ARG_STRING, &domain, 0, + _("Domain of the information provider (mandatory)"), NULL }, + {"id", 0, POPT_ARG_LONG, &id, 0, + _("Child identifier (mandatory)"), NULL }, + {"chain-id", 0, POPT_ARG_LONG, &chain_id, 0, + _("Tevent chain ID used for logging purposes"), NULL }, + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + ret = chdir("/"); + if (ret != 0) { + fprintf(stderr, "\nFailed to chdir()\n\n"); + return 1; + } + + ret = clearenv(); + if (ret != 0) { + fprintf(stderr, "\nFailed to clear env.\n\n"); + return 1; + } + + umask(SSS_DFL_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + if (domain == NULL) { + fprintf(stderr, "\nMissing option, " + "--domain is a mandatory option.\n\n"); + poptPrintUsage(pc, stderr, 0); + return 1; + } + if (!is_valid_domain_name(domain)) { + fprintf(stderr, "\nInvalid --domain option.\n\n"); + return 1; + } + + if (id == 0) { + fprintf(stderr, "\nMissing option, " + "--id is a mandatory option.\n\n"); + poptPrintUsage(pc, stderr, 0); + return 1; + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = talloc_asprintf(NULL, "proxy_child_%s", domain); + if (!debug_log_file) return 2; + + sss_chain_id_set((uint64_t)chain_id); + + DEBUG_INIT(debug_level, opt_logger); + + srv_name = talloc_asprintf(NULL, "proxy_child[%s]", domain); + if (!srv_name) return 2; + + conf_entry = talloc_asprintf(NULL, CONFDB_DOMAIN_PATH_TMPL, domain); + if (!conf_entry) return 2; + + ret = server_setup(srv_name, false, 0, 0, 0, conf_entry, &main_ctx, true); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not set up mainloop [%d]\n", ret); + return 2; + } + + ret = confdb_get_string(main_ctx->confdb_ctx, main_ctx, conf_entry, + CONFDB_PROXY_PAM_TARGET, NULL, &pam_target); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + return 4; + } + if (pam_target == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing option proxy_pam_target.\n"); + return 4; + } + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, + "Could not set up to exit when parent process does\n"); + } + + ret = proxy_child_process_init(main_ctx, domain, main_ctx->event_ctx, + main_ctx->confdb_ctx, pam_target, + (uint32_t)id); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not initialize proxy child [%d].\n", ret); + return 3; + } + + DEBUG(SSSDBG_IMPORTANT_INFO, + "Proxy child for domain [%s] started!\n", domain); + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} diff --git a/src/providers/proxy/proxy_client.c b/src/providers/proxy/proxy_client.c new file mode 100644 index 0000000..0a4da5c --- /dev/null +++ b/src/providers/proxy/proxy_client.c @@ -0,0 +1,134 @@ +/* + SSSD + + proxy_init.c + + Authors: + Stephen Gallagher + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "util/util.h" +#include "providers/proxy/proxy.h" +#include "sss_iface/sss_iface_async.h" + +struct proxy_client { + struct proxy_auth_ctx *proxy_auth_ctx; + struct sbus_connection *conn; + struct tevent_timer *timeout; +}; + +errno_t +proxy_client_register(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct proxy_auth_ctx *auth_ctx, + uint32_t cli_id) +{ + struct proxy_client *proxy_cli; + struct proxy_child_ctx *child_ctx; + struct pc_init_ctx *init_ctx; + struct tevent_req *req; + hash_value_t value; + hash_key_t key; + int hret; + struct sbus_connection *cli_conn; + + /* When connection is lost we also free the client. */ + proxy_cli = talloc_zero(sbus_req->conn, struct proxy_client); + if (proxy_cli == NULL) { + return ENOMEM; + } + + proxy_cli->proxy_auth_ctx = auth_ctx; + proxy_cli->conn = sbus_req->conn; + + key.type = HASH_KEY_ULONG; + key.ul = cli_id; + if (!hash_has_key(proxy_cli->proxy_auth_ctx->request_table, &key)) { + talloc_free(proxy_cli); + return EIO; + } + + hret = hash_lookup(proxy_cli->proxy_auth_ctx->request_table, &key, &value); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Hash error [%d]: %s\n", hret, hash_error_string(hret)); + talloc_free(proxy_cli); + return EIO; + } + + /* Signal that the child is up and ready to receive the request */ + req = talloc_get_type(value.ptr, struct tevent_req); + child_ctx = tevent_req_data(req, struct proxy_child_ctx); + + if (!child_ctx->running) { + /* This should hopefully be impossible, but protect + * against it anyway. If we're not marked running, then + * the init_req will be NULL below and things will + * break. + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Client connection from a request " + "that's not marked as running\n"); + talloc_free(proxy_cli); + return EIO; + } + + init_ctx = tevent_req_data(child_ctx->init_req, struct pc_init_ctx); + init_ctx->conn = sbus_req->conn; + tevent_req_done(child_ctx->init_req); + child_ctx->init_req = NULL; + + /* Remove the timeout handler added by dp_client_init() */ + cli_conn = sbus_server_find_connection(dp_sbus_server(auth_ctx->be->provider), + sbus_req->sender->name); + if (cli_conn != NULL) { + dp_client_cancel_timeout(cli_conn); + } else { + DEBUG(SSSDBG_TRACE_ALL, "No connection found for [%s].\n", sbus_req->sender->name); + } + + return EOK; +} + +errno_t +proxy_client_init(struct sbus_connection *conn, + struct proxy_auth_ctx *auth_ctx) +{ + errno_t ret; + + SBUS_INTERFACE(iface, + sssd_ProxyChild_Client, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_ProxyChild_Client, Register, proxy_client_register, auth_ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + struct sbus_path paths[] = { + {SSS_BUS_PATH, &iface}, + {NULL, NULL} + }; + + ret = sbus_connection_add_path_map(conn, paths); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to add paths [%d]: %s\n", + ret, sss_strerror(ret)); + } + + return ret; +} diff --git a/src/providers/proxy/proxy_hosts.c b/src/providers/proxy/proxy_hosts.c new file mode 100644 index 0000000..d224829 --- /dev/null +++ b/src/providers/proxy/proxy_hosts.c @@ -0,0 +1,768 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#include "providers/proxy/proxy.h" +#include "db/sysdb_iphosts.h" +#include +#include + +static errno_t +nss_status_to_errno(enum nss_status status) +{ + switch (status) { + case NSS_STATUS_SUCCESS: + return EOK; + case NSS_STATUS_TRYAGAIN: + return EAGAIN; + case NSS_STATUS_NOTFOUND: + return ENOENT; + case NSS_STATUS_UNAVAIL: + default: + break; + } + + return EIO; +} + +static errno_t +parse_hostent(TALLOC_CTX *mem_ctx, + struct hostent *result, + bool case_sensitive, + char **out_name, + char ***out_aliases, + char ***out_addresses) +{ + char **addresses = *out_addresses; + char **aliases = *out_aliases; + int i; + errno_t ret; + + /* Parse addresses */ + for (i = 0; result->h_addr_list[i] != NULL; i++) { + size_t len = talloc_array_length(addresses); + char buf[INET6_ADDRSTRLEN]; + const char *addr = NULL; + bool found = false; + int j; + + if (result->h_length == INADDRSZ) { + addr = inet_ntop(AF_INET, result->h_addr_list[i], + buf, INET6_ADDRSTRLEN); + } else if (result->h_length == IN6ADDRSZ) { + addr = inet_ntop(AF_INET6, result->h_addr_list[i], + buf, INET6_ADDRSTRLEN); + } + + if (addr == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to convert host network address of host " + "'%s' to a character string: %s\n", result->h_name, + strerror(errno)); + continue; + } + + /* Skip duplicates */ + for (j = 0; + j < len && addresses != NULL && addresses[j] != NULL; + j++) { + if (strcasecmp(addresses[j], addr) == 0) { + found = true; + break; + } + } + + if (!found) { + ret = add_string_to_list(mem_ctx, addr, &addresses); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] has address [%s]\n", + result->h_name, addr); + } + } + + for (i = 0; result->h_aliases[i] != NULL; i++) { + size_t len = talloc_array_length(aliases); + const char *alias = result->h_aliases[i]; + bool found = false; + int j; + + for (j = 0; j < len && aliases != NULL && aliases[j] != NULL; j++) { + if (case_sensitive && strcmp(aliases[j], alias) == 0) { + found = true; + break; + } else if (strcasecmp(aliases[j], alias) == 0) { + found = true; + break; + } + } + + if (!found) { + ret = add_string_to_list(mem_ctx, alias, &aliases); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] has alias [%s]\n", + result->h_name, alias); + } + } + + *out_name = talloc_strdup(mem_ctx, result->h_name); + *out_addresses = addresses; + *out_aliases = aliases; + + ret = EOK; +done: + return ret; +} + +static errno_t +proxy_save_host(struct sss_domain_info *domain, + bool lowercase, + uint64_t cache_timeout, + char *name, + char **aliases, + char **addresses) +{ + errno_t ret; + char *cased_name = NULL; + const char **cased_aliases = NULL; + const char **cased_addresses = NULL; + TALLOC_CTX *tmp_ctx; + char *lc_alias = NULL; + time_t now = time(NULL); + + DEBUG(SSSDBG_TRACE_FUNC, "Saving host [%s] into cache, domain [%s]\n", + name, domain->name); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + cased_name = sss_get_cased_name(tmp_ctx, name, + domain->case_preserve); + if (cased_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get cased name.\n"); + ret = ENOMEM; + goto done; + } + + /* Count the aliases */ + ret = sss_get_cased_name_list(tmp_ctx, + (const char * const *) aliases, + !lowercase, &cased_aliases); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get cased aliases.\n"); + goto done; + } + + /* Count the addresses */ + ret = sss_get_cased_name_list(tmp_ctx, + (const char * const *) addresses, + !lowercase, &cased_addresses); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get cased addresses.\n"); + goto done; + } + + if (domain->case_preserve) { + /* Add lowercased alias to allow case-insensitive lookup */ + lc_alias = sss_tc_utf8_str_tolower(tmp_ctx, name); + if (lc_alias == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot convert name to lowercase.\n"); + ret = ENOMEM; + goto done; + } + + ret = add_string_to_list(tmp_ctx, lc_alias, + discard_const_p(char **, &cased_aliases)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add lowercased name alias.\n"); + goto done; + } + } + + ret = sysdb_store_host(domain, cased_name, cased_aliases, cased_addresses, + NULL, NULL, cache_timeout, now); +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +get_host_by_name_internal(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + const char *search_name, int af, + char **out_name, + char ***out_addresses, + char ***out_aliases) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + struct hostent *result = NULL; + enum nss_status status; + int err; + int h_err; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Resolving host [%s] [%s]\n", search_name, + af == AF_INET ? "AF_INET" : "AF_INET6"); + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct hostent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + /* Ask for IPv4 addresses */ + err = 0; + h_err = 0; + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.gethostbyname2_r(search_name, af, result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "gethostbyname2_r (%s) failed for host [%s]: %d, %s, %s.\n", + af == AF_INET ? "AF_INET" : "AF_INET6", + search_name, status, strerror(err), hstrerror(h_err)); + } + + goto done; + } + + ret = parse_hostent(mem_ctx, result, domain->case_sensitive, + out_name, out_aliases, out_addresses); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse hostent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +get_host_byname(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + const char *search_name) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret_v4; + errno_t ret_v6; + errno_t ret; + char *name = NULL; + char **addresses = NULL; + char **aliases = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Processing request for host name [%s]\n", + search_name); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret_v4 = get_host_by_name_internal(ctx, domain, tmp_ctx, search_name, + AF_INET, &name, &addresses, &aliases); + if (ret_v4 != EOK && ret_v4 != ENOENT) { + ret = ret_v4; + goto done; + } + + ret_v6 = get_host_by_name_internal(ctx, domain, tmp_ctx, search_name, + AF_INET6, &name, &addresses, &aliases); + if (ret_v6 != EOK && ret_v6 != ENOENT) { + ret = ret_v6; + goto done; + } + + if (ret_v4 == ENOENT && ret_v6 == ENOENT) { + /* Make sure we remove it from the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] not found, removing from " + "cache\n", name); + sysdb_host_delete(domain, search_name, NULL); + ret = ENOENT; + goto done; + } else { + /* Results found. Save them into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] found, saving into " + "cache\n", name); + ret = proxy_save_host(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, addresses); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store host [%s] [%d]: %s\n", + name, ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +get_host_by_addr_internal(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + const char *addrstr, + char **out_name, + char ***out_addresses, + char ***out_aliases) +{ + TALLOC_CTX *tmp_ctx; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + struct hostent *result = NULL; + enum nss_status status; + int err; + int h_err; + char addrbuf[IN6ADDRSZ]; + socklen_t addrlen = 0; + int af = 0; + errno_t ret; + char *name = NULL; + char **addresses = NULL; + char **aliases = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Resolving host [%s]\n", addrstr); + + if (inet_pton(AF_INET, addrstr, addrbuf)) { + af = AF_INET; + addrlen = INADDRSZ; + } else if (inet_pton(AF_INET6, addrstr, addrbuf)) { + af = AF_INET6; + addrlen = IN6ADDRSZ; + } else { + return EINVAL; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct hostent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + /* Ask for IPv4 addresses */ + err = 0; + h_err = 0; + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.gethostbyaddr_r(addrbuf, addrlen, af, result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "gethostbyaddr_r (%s) failed for host [%s]: %d, %s, %s.\n", + af == AF_INET ? "AF_INET" : "AF_INET6", + addrstr, status, strerror(err), hstrerror(h_err)); + goto done; + } + + if (ret == EOK) { + ret = parse_hostent(tmp_ctx, result, domain->case_sensitive, + &name, &aliases, &addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to parse hostent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + if (name != NULL) { + *out_name = talloc_steal(mem_ctx, name); + } + if (addresses != NULL) { + *out_addresses = talloc_steal(mem_ctx, addresses); + } + if (aliases != NULL) { + *out_aliases = talloc_steal(mem_ctx, aliases); + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +get_host_byaddr(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + const char *address) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char *name = NULL; + char **addresses = NULL; + char **aliases = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Processing request for host address [%s]\n", + address); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = get_host_by_addr_internal(ctx, domain, tmp_ctx, address, + &name, &addresses, &aliases); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + if (ret == ENOENT) { + /* Make sure we remove it from the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] not found, removing from " + "cache\n", address); + sysdb_host_delete(domain, NULL, address); + } else { + /* Results found. Save them into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] found, saving into " + "cache\n", address); + ret = proxy_save_host(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, addresses); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store host [%s] [%d]: %s\n", + name, ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +gethostent_internal(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + char **out_name, + char ***out_addresses, + char ***out_aliases) + +{ + TALLOC_CTX *tmp_ctx = NULL; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + enum nss_status status; + struct hostent *result = NULL; + char *name = NULL; + char **addresses = NULL; + char **aliases = NULL; + int err; + int h_err; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct hostent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.gethostent_r(result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "gethostent_r failed: %d, %s, %s.\n", + status, strerror(err), hstrerror(h_err)); + goto done; + } + + if (ret == EOK) { + ret = parse_hostent(tmp_ctx, result, domain->case_sensitive, + &name, &aliases, &addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to parse hostent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + if (name != NULL) { + *out_name = talloc_steal(mem_ctx, name); + } + if (addresses != NULL) { + *out_addresses = talloc_steal(mem_ctx, addresses); + } + if (aliases != NULL) { + *out_aliases = talloc_steal(mem_ctx, aliases); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +enum_iphosts(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain) +{ + struct sysdb_ctx *sysdb = domain->sysdb; + TALLOC_CTX *tmp_ctx = NULL; + bool in_transaction = false; + enum nss_status status; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Enumerating iphosts\n"); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.sethostent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + char *name = NULL; + char **addresses = NULL; + char **aliases = NULL; + + ret = gethostent_internal(ctx, domain, tmp_ctx, &name, + &addresses, &aliases); + if (ret == EOK) { + /* Results found. Save them into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] found, saving into " + "cache\n", name); + + proxy_save_host(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, addresses); + } + + /* Free children to avoid using too much memory */ + talloc_free_children(tmp_ctx); + } while (ret == EOK); + + if (ret == ENOENT) { + /* We are done, commit transaction and stop loop */ + DEBUG(SSSDBG_TRACE_FUNC, "Enumeration completed.\n"); + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "gethostent_r failed [%d]: %s\n", + ret, strerror(ret)); + } + +done: + talloc_free(tmp_ctx); + if (in_transaction) { + errno_t sret; + + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not cancel transaction! [%s]\n", + strerror(sret)); + } + } + ctx->ops.endhostent(); + return ret; +} + +static struct dp_reply_std +proxy_hosts_info(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *ctx, + struct dp_resolver_data *data, + struct be_ctx *be_ctx, + struct sss_domain_info *domain) +{ + struct dp_reply_std reply; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Processing host request, filter type [%d]\n", + data->filter_type); + + switch (data->filter_type) { + case BE_FILTER_NAME: + ret = get_host_byname(ctx, domain, data->filter_value); + break; + + case BE_FILTER_ADDR: + ret = get_host_byaddr(ctx, domain, data->filter_value); + break; + + case BE_FILTER_ENUM: + ret = enum_iphosts(ctx, domain); + break; + + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + + if (ret) { + if (ret == ENXIO) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy returned UNAVAIL error, going offline!\n"); + be_mark_offline(be_ctx); + } + + dp_reply_std_set(&reply, DP_ERR_FATAL, ret, NULL); + return reply; + } + + dp_reply_std_set(&reply, DP_ERR_OK, EOK, NULL); + return reply; +} + +struct proxy_hosts_handler_state { + int dummy; + struct dp_reply_std reply; +}; + +struct tevent_req * +proxy_hosts_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params) +{ + struct proxy_hosts_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct proxy_hosts_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->reply = proxy_hosts_info(state, resolver_ctx, resolver_data, + params->be_ctx, params->be_ctx->domain); + + tevent_req_done(req); + return tevent_req_post(req, params->ev); +} + +errno_t +proxy_hosts_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct proxy_hosts_handler_state *state; + + state = tevent_req_data(req, struct proxy_hosts_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/proxy/proxy_id.c b/src/providers/proxy/proxy_id.c new file mode 100644 index 0000000..b1d0c22 --- /dev/null +++ b/src/providers/proxy/proxy_id.c @@ -0,0 +1,1962 @@ +/* + SSSD + + proxy_id.c + + Authors: + Stephen Gallagher + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include +#include "config.h" + +#include "util/sss_format.h" +#include "util/strtonum.h" +#include "providers/proxy/proxy.h" + +/* =Getpwnam-wrapper======================================================*/ + +static int save_user(struct sss_domain_info *domain, + struct passwd *pwd, const char *real_name, + const char *alias); + +static int +handle_getpw_result(enum nss_status status, struct passwd *pwd, + struct sss_domain_info *dom, bool *del_user); + +static int +delete_user(struct sss_domain_info *domain, + const char *name, uid_t uid); + +int get_pw_name(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *i_name) +{ + TALLOC_CTX *tmpctx; + struct passwd *pwd; + enum nss_status status; + char *buffer; + size_t buflen; + int ret; + uid_t uid; + bool del_user; + struct ldb_result *cached_pwd = NULL; + const char *real_name = NULL; + char *shortname_or_alias; + + DEBUG(SSSDBG_TRACE_FUNC, "Searching user by name (%s)\n", i_name); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmpctx, i_name, &shortname_or_alias, NULL); + if (ret != EOK) { + goto done; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible? */ + status = ctx->ops.getpwnam_r(shortname_or_alias, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwnam failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (del_user) { + ret = delete_user(dom, i_name, 0); + goto done; + } + + uid = pwd->pw_uid; + + /* Canonicalize the username in case it was actually an alias */ + + if (ctx->fast_alias == true) { + ret = sysdb_getpwuid(tmpctx, dom, uid, &cached_pwd); + if (ret != EOK) { + /* Non-fatal, attempt to canonicalize online */ + DEBUG(SSSDBG_TRACE_FUNC, "Request to cache failed [%d]: %s\n", + ret, strerror(ret)); + } + + if (ret == EOK && cached_pwd->count == 1) { + real_name = ldb_msg_find_attr_as_string(cached_pwd->msgs[0], + SYSDB_NAME, NULL); + if (!real_name) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cached user has no name?\n"); + } + } + } + + if (real_name == NULL) { + memset(buffer, 0, buflen); + + status = ctx->ops.getpwuid_r(uid, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwuid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + real_name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (real_name == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (del_user) { + ret = delete_user(dom, i_name, uid); + goto done; + } + + /* Both lookups went fine, we can save the user now */ + ret = save_user(dom, pwd, real_name, i_name); + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy -> getpwnam_r failed for '%s' <%d>: %s\n", + i_name, ret, strerror(ret)); + } + return ret; +} + +static int +handle_getpw_result(enum nss_status status, struct passwd *pwd, + struct sss_domain_info *dom, bool *del_user) +{ + int ret = EOK; + + if (!del_user) { + return EINVAL; + } + *del_user = false; + + switch (status) { + case NSS_STATUS_NOTFOUND: + + DEBUG(SSSDBG_TRACE_FUNC, "User not found.\n"); + *del_user = true; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(SSSDBG_TRACE_FUNC, "User found: (%s, %"SPRIuid", %"SPRIgid")\n", + pwd->pw_name, pwd->pw_uid, pwd->pw_gid); + + /* uid=0 or gid=0 are invalid values */ + /* also check that the id is in the valid range for this domain */ + if (OUT_OF_ID_RANGE(pwd->pw_uid, dom->id_min, dom->id_max) || + OUT_OF_ID_RANGE(pwd->pw_gid, dom->id_min, dom->id_max)) { + + DEBUG(SSSDBG_MINOR_FAILURE, + "User filtered out! (id out of range)\n"); + *del_user = true; + break; + } + break; + + case NSS_STATUS_UNAVAIL: + DEBUG(SSSDBG_MINOR_FAILURE, + "Remote back end is not available. Entering offline mode\n"); + ret = ENXIO; + break; + + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown return code %d\n", status); + ret = EIO; + break; + } + + return ret; +} + +static int +delete_user(struct sss_domain_info *domain, + const char *name, uid_t uid) +{ + int ret = EOK; + + if (name != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "User %s does not exist (or is invalid) on remote server," + " deleting!\n", name); + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "User with UID %"SPRIuid" does not exist (or is invalid) " + "on remote server, deleting!\n", uid); + } + + ret = sysdb_delete_user(domain, name, uid); + if (ret == ENOENT) { + ret = EOK; + } + + return ret; +} + +static int +prepare_attrs_for_saving_ops(TALLOC_CTX *mem_ctx, + bool case_sensitive, + const char *real_name, /* already_qualified */ + const char *alias, /* already qualified */ + struct sysdb_attrs **attrs) +{ + const char *lc_name = NULL; + const char *cased_alias = NULL; + errno_t ret; + + if (!case_sensitive || alias != NULL) { + if (*attrs == NULL) { + *attrs = sysdb_new_attrs(mem_ctx); + if (*attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Allocation error?!\n"); + ret = ENOMEM; + goto done; + } + } + } + + if (!case_sensitive) { + lc_name = sss_tc_utf8_str_tolower(*attrs, real_name); + if (lc_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot convert name to lowercase.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(*attrs, SYSDB_NAME_ALIAS, lc_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add name alias\n"); + ret = ENOMEM; + goto done; + } + + } + + if (alias != NULL) { + cased_alias = sss_get_cased_name(*attrs, alias, case_sensitive); + if (cased_alias == NULL) { + ret = ENOMEM; + goto done; + } + + /* Add the alias only if it differs from lowercased pw_name */ + if (lc_name == NULL || strcmp(cased_alias, lc_name) != 0) { + ret = sysdb_attrs_add_string(*attrs, SYSDB_NAME_ALIAS, + cased_alias); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add name alias\n"); + goto done; + } + } + } + + ret = EOK; +done: + return ret; +} + +static int save_user(struct sss_domain_info *domain, + struct passwd *pwd, + const char *real_name, /* already qualified */ + const char *alias) /* already qualified */ +{ + const char *shell; + const char *gecos; + struct sysdb_attrs *attrs = NULL; + errno_t ret; + + if (pwd->pw_shell && pwd->pw_shell[0] != '\0') { + shell = pwd->pw_shell; + } else { + shell = NULL; + } + + if (pwd->pw_gecos && pwd->pw_gecos[0] != '\0') { + gecos = pwd->pw_gecos; + } else { + gecos = NULL; + } + + ret = prepare_attrs_for_saving_ops(NULL, domain->case_sensitive, + real_name, alias, &attrs); + if (ret != EOK) { + goto done; + } + + ret = sysdb_store_user(domain, + real_name, + pwd->pw_passwd, + pwd->pw_uid, + pwd->pw_gid, + gecos, + pwd->pw_dir, + shell, + NULL, + attrs, + NULL, + domain->user_timeout, + 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add user to cache\n"); + goto done; + } + +done: + talloc_zfree(attrs); + return ret; +} + +/* =Getpwuid-wrapper======================================================*/ + +static int get_pw_uid(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + uid_t uid) +{ + TALLOC_CTX *tmpctx; + struct passwd *pwd; + enum nss_status status; + char *buffer; + size_t buflen; + bool del_user = false; + int ret; + char *name; + + DEBUG(SSSDBG_TRACE_FUNC, "Searching user by uid (%"SPRIuid")\n", uid); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getpwuid_r(uid, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwuid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (del_user) { + ret = delete_user(dom, NULL, uid); + goto done; + } + + name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to qualify name '%s'\n", + pwd->pw_name); + goto done; + } + ret = save_user(dom, pwd, name, NULL); + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "proxy -> getpwuid_r failed for '%"SPRIuid"' <%d>: %s\n", + uid, ret, strerror(ret)); + } + return ret; +} + +/* =Getpwent-wrapper======================================================*/ + +static int enum_users(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct passwd *pwd; + enum nss_status status; + size_t buflen; + char *buffer; + char *newbuf; + int ret; + errno_t sret; + bool again; + char *name; + + DEBUG(SSSDBG_TRACE_LIBS, "Enumerating users\n"); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.setpwent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + again = false; + + /* always zero out the pwd structure */ + memset(pwd, 0, sizeof(struct passwd)); + + /* get entry */ + status = ctx->ops.getpwent_r(pwd, buffer, buflen, &ret); + + switch (status) { + case NSS_STATUS_TRYAGAIN: + /* buffer too small? */ + if (buflen < MAX_BUF_SIZE) { + buflen *= 2; + } + if (buflen > MAX_BUF_SIZE) { + buflen = MAX_BUF_SIZE; + } + newbuf = talloc_realloc_size(tmpctx, buffer, buflen); + if (!newbuf) { + ret = ENOMEM; + goto done; + } + buffer = newbuf; + again = true; + break; + + case NSS_STATUS_NOTFOUND: + + /* we are done here */ + DEBUG(SSSDBG_TRACE_LIBS, "Enumeration completed.\n"); + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(SSSDBG_TRACE_LIBS, + "User found (%s, %"SPRIuid", %"SPRIgid")\n", + pwd->pw_name, pwd->pw_uid, pwd->pw_gid); + + /* uid=0 or gid=0 are invalid values */ + /* also check that the id is in the valid range for this domain + */ + if (OUT_OF_ID_RANGE(pwd->pw_uid, dom->id_min, dom->id_max) || + OUT_OF_ID_RANGE(pwd->pw_gid, dom->id_min, dom->id_max)) { + + DEBUG(SSSDBG_OP_FAILURE, "User [%s] filtered out! (id out" + " of range)\n", pwd->pw_name); + + again = true; + break; + } + + name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "failed to create internal name '%s'\n", + pwd->pw_name); + goto done; + } + ret = save_user(dom, pwd, name, NULL); + if (ret) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(SSSDBG_OP_FAILURE, "Failed to store user %s." + " Ignoring.\n", pwd->pw_name); + } + again = true; + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + break; + + default: + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, "proxy -> getpwent_r failed (%d)[%s]" + "\n", ret, strerror(ret)); + break; + } + } while (again); + +done: + talloc_zfree(tmpctx); + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + ctx->ops.endpwent(); + return ret; +} + +/* =Save-group-utilities=================================================*/ +#define DEBUG_GR_MEM(level, grp) \ + do { \ + if (!grp->gr_mem || !grp->gr_mem[0]) { \ + DEBUG(level, "Group %s has no members!\n", \ + grp->gr_name); \ + } else { \ + int i = 0; \ + while (grp->gr_mem[i]) { \ + /* count */ \ + i++; \ + } \ + DEBUG(level, "Group %s has %d members!\n", \ + grp->gr_name, i); \ + } \ + } while(0) + + +static errno_t remove_duplicate_group_members(TALLOC_CTX *mem_ctx, + const struct group *orig_grp, + struct group **_grp) +{ + TALLOC_CTX *tmp_ctx; + hash_table_t *member_tbl = NULL; + struct hash_iter_context_t *iter; + hash_entry_t *entry; + hash_key_t key; + hash_value_t value; + struct group *grp; + size_t orig_member_count= 0; + size_t member_count= 0; + size_t i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + grp = talloc(tmp_ctx, struct group); + if (grp == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + + grp->gr_gid = orig_grp->gr_gid; + + grp->gr_name = talloc_strdup(grp, orig_grp->gr_name); + if (grp->gr_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + grp->gr_passwd = talloc_strdup(grp, orig_grp->gr_passwd); + if (grp->gr_passwd == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + if (orig_grp->gr_mem == NULL) { + grp->gr_mem = NULL; + ret = EOK; + goto done; + } + + for (i=0; orig_grp->gr_mem[i] != NULL; ++i) /* no-op: just counting */; + + orig_member_count = i; + + if (orig_member_count == 0) { + grp->gr_mem = talloc_zero_array(grp, char *, 1); + if (grp->gr_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + grp->gr_mem[0] = NULL; + ret = EOK; + goto done; + } + + ret = sss_hash_create(tmp_ctx, orig_member_count, &member_tbl); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create hash table.\n"); + ret = ENOMEM; + goto done; + } + + for (i=0; i < orig_member_count; ++i) { + key.type = HASH_KEY_STRING; + key.str = orig_grp->gr_mem[i]; /* hash_enter() makes copy itself */ + + value.type = HASH_VALUE_PTR; + /* no need to put copy in hash_table since + copy will be created during construction of new grp */ + value.ptr = orig_grp->gr_mem[i]; + + ret = hash_enter(member_tbl, &key, &value); + if (ret != HASH_SUCCESS) { + ret = ENOMEM; + goto done; + } + } + + member_count = hash_count(member_tbl); + if (member_count == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Empty resulting hash table - must be internal bug.\n"); + ret = EINVAL; + goto done; + } + + grp->gr_mem = talloc_zero_array(grp, char *, member_count + 1); + if (grp->gr_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + iter = new_hash_iter_context(member_tbl); + if (iter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "new_hash_iter_context failed.\n"); + ret = EINVAL; + goto done; + } + + i = 0; + while ((entry = iter->next(iter)) != NULL) { + grp->gr_mem[i] = talloc_strdup(grp, entry->key.str); + if (grp->gr_mem[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + i++; + } + grp->gr_mem[i] = NULL; + + ret = EOK; + +done: + if (ret == EOK) { + *_grp = talloc_steal(mem_ctx, grp); + } + talloc_zfree(tmp_ctx); + + return ret; +} + +static errno_t proxy_process_missing_users(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sysdb_attrs *group_attrs, + const char *const*fq_gr_mem, + time_t now); +static int save_group(struct sysdb_ctx *sysdb, struct sss_domain_info *dom, + const struct group *grp, + const char *real_name, /* already qualified */ + const char *alias) /* already qualified */ +{ + errno_t ret, sret; + struct group *ngroup = NULL; + struct sysdb_attrs *attrs = NULL; + TALLOC_CTX *tmp_ctx; + time_t now = time(NULL); + bool in_transaction = false; + char **fq_gr_mem; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = remove_duplicate_group_members(tmp_ctx, grp, &ngroup); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to remove duplicate group members\n"); + goto done; + } + + DEBUG_GR_MEM(SSSDBG_TRACE_LIBS, ngroup); + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + if (ngroup->gr_mem && ngroup->gr_mem[0]) { + attrs = sysdb_new_attrs(tmp_ctx); + if (!attrs) { + DEBUG(SSSDBG_CRIT_FAILURE, "Allocation error?!\n"); + ret = ENOMEM; + goto done; + } + + fq_gr_mem = sss_create_internal_fqname_list( + tmp_ctx, + (const char *const*) ngroup->gr_mem, + dom->name); + if (fq_gr_mem == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_users_from_str_list( + attrs, SYSDB_MEMBER, dom->name, + (const char *const *) fq_gr_mem); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group members\n"); + goto done; + } + + /* Create ghost users */ + ret = proxy_process_missing_users(sysdb, dom, attrs, + (const char *const*) fq_gr_mem, now); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add missing members\n"); + goto done; + } + } + + ret = prepare_attrs_for_saving_ops(tmp_ctx, dom->case_sensitive, + real_name, alias, &attrs); + if (ret != EOK) { + goto done; + } + + ret = sysdb_store_group(dom, + real_name, + ngroup->gr_gid, + attrs, + dom->group_timeout, + now); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group to cache\n"); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not commit transaction: [%s]\n", + strerror(ret)); + goto done; + } + in_transaction = false; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t proxy_process_missing_users(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sysdb_attrs *group_attrs, + const char *const*fq_gr_mem, + time_t now) +{ + errno_t ret; + size_t i; + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_message *msg; + + if (!sysdb || !fq_gr_mem) return EINVAL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + for (i = 0; fq_gr_mem[i]; i++) { + ret = sysdb_search_user_by_name(tmp_ctx, domain, fq_gr_mem[i], + NULL, &msg); + if (ret == EOK) { + /* Member already exists in the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Member [%s] already cached\n", fq_gr_mem[i]); + /* clean up */ + talloc_zfree(msg); + continue; + } else if (ret == ENOENT) { + /* No entry for this user. Create a ghost user */ + DEBUG(SSSDBG_TRACE_LIBS, + "Member [%s] not cached, creating ghost user entry\n", + fq_gr_mem[i]); + + ret = sysdb_attrs_add_string(group_attrs, SYSDB_GHOST, fq_gr_mem[i]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot store ghost user entry: [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } else { + /* Unexpected error */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Error searching cache for user [%s]: [%s]\n", + fq_gr_mem[i], strerror(ret)); + goto done; + } + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +/* =Getgrnam-wrapper======================================================*/ +static char * +grow_group_buffer(TALLOC_CTX *mem_ctx, + char **buffer, size_t *buflen) +{ + char *newbuf; + + if (*buflen == 0) { + *buflen = DEFAULT_BUFSIZE; + } + if (*buflen < MAX_BUF_SIZE) { + *buflen *= 2; + } + if (*buflen > MAX_BUF_SIZE) { + *buflen = MAX_BUF_SIZE; + } + + newbuf = talloc_realloc_size(mem_ctx, *buffer, *buflen); + if (!newbuf) { + return NULL; + } + *buffer = newbuf; + + return *buffer; +} + +static errno_t +handle_getgr_result(enum nss_status status, struct group *grp, + struct sss_domain_info *dom, + bool *delete_group) +{ + if (delete_group) { + *delete_group = false; + } + + switch (status) { + case NSS_STATUS_TRYAGAIN: + DEBUG(SSSDBG_MINOR_FAILURE, "Buffer too small\n"); + return EAGAIN; + + case NSS_STATUS_NOTFOUND: + DEBUG(SSSDBG_MINOR_FAILURE, "Group not found.\n"); + if (delete_group) { + *delete_group = true; + } + break; + + case NSS_STATUS_SUCCESS: + DEBUG(SSSDBG_FUNC_DATA, "Group found: (%s, %"SPRIgid")\n", + grp->gr_name, grp->gr_gid); + + /* gid=0 is an invalid value */ + /* also check that the id is in the valid range for this domain */ + if (OUT_OF_ID_RANGE(grp->gr_gid, dom->id_min, dom->id_max)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Group filtered out! (id out of range)\n"); + if (delete_group) { + *delete_group = true; + } + break; + } + break; + + case NSS_STATUS_UNAVAIL: + DEBUG(SSSDBG_MINOR_FAILURE, + "Remote back end is not available. Entering offline mode\n"); + return ENXIO; + + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown return code %d\n", status); + return EIO; + } + + return EOK; +} + +static int get_gr_name(struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const char *i_name) +{ + TALLOC_CTX *tmpctx; + struct group *grp; + enum nss_status status; + char *buffer = 0; + size_t buflen = 0; + bool delete_group = false; + int ret; + gid_t gid; + struct ldb_result *cached_grp = NULL; + const char *real_name = NULL; + char *shortname_or_alias; + + DEBUG(SSSDBG_FUNC_DATA, "Searching group by name (%s)\n", i_name); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmpctx, i_name, &shortname_or_alias, NULL); + if (ret != EOK) { + goto done; + } + + grp = talloc(tmpctx, struct group); + if (!grp) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "talloc() failed\n"); + goto done; + } + + do { + /* always zero out the grp structure */ + memset(grp, 0, sizeof(struct group)); + buffer = grow_group_buffer(tmpctx, &buffer, &buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getgrnam_r(shortname_or_alias, grp, buffer, + buflen, &ret); + ret = handle_getgr_result(status, grp, dom, &delete_group); + } while (ret == EAGAIN); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "getgrnam failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (delete_group) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group %s does not exist (or is invalid) on remote server," + " deleting!\n", i_name); + + ret = sysdb_delete_group(dom, i_name, 0); + if (ret == ENOENT) { + ret = EOK; + } + goto done; + } + + gid = grp->gr_gid; + + /* Canonicalize the group name in case it was actually an alias */ + if (ctx->fast_alias == true) { + ret = sysdb_getgrgid(tmpctx, dom, gid, &cached_grp); + if (ret != EOK) { + /* Non-fatal, attempt to canonicalize online */ + DEBUG(SSSDBG_TRACE_FUNC, "Request to cache failed [%d]: %s\n", + ret, strerror(ret)); + } + + if (ret == EOK && cached_grp->count == 1) { + real_name = ldb_msg_find_attr_as_string(cached_grp->msgs[0], + SYSDB_NAME, NULL); + if (!real_name) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cached group has no name?\n"); + } + } + } + + if (real_name == NULL) { + talloc_zfree(buffer); + buflen = 0; + + do { + memset(grp, 0, sizeof(struct group)); + buffer = grow_group_buffer(tmpctx, &buffer, &buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getgrgid_r(gid, grp, buffer, buflen, &ret); + + ret = handle_getgr_result(status, grp, dom, &delete_group); + } while (ret == EAGAIN); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "getgrgid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + real_name = sss_create_internal_fqname(tmpctx, grp->gr_name, dom->name); + if (real_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create fqdn '%s'\n", + grp->gr_name); + ret = ENOMEM; + goto done; + } + } + + if (delete_group) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group %s does not exist (or is invalid) on remote server," + " deleting!\n", i_name); + + ret = sysdb_delete_group(dom, i_name, gid); + if (ret == ENOENT) { + ret = EOK; + } + goto done; + } + + ret = save_group(sysdb, dom, grp, real_name, i_name); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save group [%d]: %s\n", ret, strerror(ret)); + goto done; + } + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy -> getgrnam_r failed for '%s' <%d>: %s\n", + i_name, ret, strerror(ret)); + } + return ret; +} + +/* =Getgrgid-wrapper======================================================*/ +static int get_gr_gid(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + gid_t gid, + time_t now) +{ + TALLOC_CTX *tmpctx; + struct group *grp; + enum nss_status status; + char *buffer = NULL; + size_t buflen = 0; + bool delete_group = false; + int ret; + char *name; + + DEBUG(SSSDBG_TRACE_FUNC, "Searching group by gid (%"SPRIgid")\n", gid); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + grp = talloc(tmpctx, struct group); + if (!grp) { + ret = ENOMEM; + goto done; + } + + do { + /* always zero out the grp structure */ + memset(grp, 0, sizeof(struct group)); + buffer = grow_group_buffer(tmpctx, &buffer, &buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getgrgid_r(gid, grp, buffer, buflen, &ret); + + ret = handle_getgr_result(status, grp, dom, &delete_group); + } while (ret == EAGAIN); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "getgrgid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (delete_group) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group %"SPRIgid" does not exist (or is invalid) on remote " + "server, deleting!\n", gid); + + ret = sysdb_delete_group(dom, NULL, gid); + if (ret == ENOENT) { + ret = EOK; + } + goto done; + } + + name = sss_create_internal_fqname(tmpctx, grp->gr_name, dom->name); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + ret = save_group(sysdb, dom, grp, name, NULL); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save user [%d]: %s\n", ret, strerror(ret)); + goto done; + } + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy -> getgrgid_r failed for '%"SPRIgid"' <%d>: %s\n", + gid, ret, strerror(ret)); + } + return ret; +} + +/* =Getgrent-wrapper======================================================*/ + +static int enum_groups(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct group *grp; + enum nss_status status; + size_t buflen; + char *buffer; + char *newbuf; + int ret; + errno_t sret; + bool again; + char *name; + + DEBUG(SSSDBG_TRACE_LIBS, "Enumerating groups\n"); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + grp = talloc(tmpctx, struct group); + if (!grp) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.setgrent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + again = false; + + /* always zero out the grp structure */ + memset(grp, 0, sizeof(struct group)); + + /* get entry */ + status = ctx->ops.getgrent_r(grp, buffer, buflen, &ret); + + switch (status) { + case NSS_STATUS_TRYAGAIN: + /* buffer too small? */ + if (buflen < MAX_BUF_SIZE) { + buflen *= 2; + } + if (buflen > MAX_BUF_SIZE) { + buflen = MAX_BUF_SIZE; + } + newbuf = talloc_realloc_size(tmpctx, buffer, buflen); + if (!newbuf) { + ret = ENOMEM; + goto done; + } + buffer = newbuf; + again = true; + break; + + case NSS_STATUS_NOTFOUND: + + /* we are done here */ + DEBUG(SSSDBG_TRACE_LIBS, "Enumeration completed.\n"); + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(SSSDBG_OP_FAILURE, "Group found (%s, %"SPRIgid")\n", + grp->gr_name, grp->gr_gid); + + /* gid=0 is an invalid value */ + /* also check that the id is in the valid range for this domain + */ + if (OUT_OF_ID_RANGE(grp->gr_gid, dom->id_min, dom->id_max)) { + + DEBUG(SSSDBG_OP_FAILURE, "Group [%s] filtered out! (id" + "out of range)\n", grp->gr_name); + + again = true; + break; + } + + name = sss_create_internal_fqname(tmpctx, grp->gr_name, + dom->name); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create internal fqname " + "Ignoring\n"); + ret = ENOMEM; + } + ret = save_group(sysdb, dom, grp, name, NULL); + if (ret) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(SSSDBG_OP_FAILURE, "Failed to store group." + "Ignoring\n"); + } + again = true; + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + break; + + default: + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, "proxy -> getgrent_r failed (%d)[%s]" + "\n", ret, strerror(ret)); + break; + } + } while (again); + +done: + talloc_zfree(tmpctx); + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + ctx->ops.endgrent(); + return ret; +} + + +/* =Initgroups-wrapper====================================================*/ + +static int get_initgr_groups_process(TALLOC_CTX *memctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct passwd *pwd); + +static int get_initgr(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const char *i_name) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct passwd *pwd; + enum nss_status status; + char *buffer; + size_t buflen; + int ret; + errno_t sret; + bool del_user; + uid_t uid; + struct ldb_result *cached_pwd = NULL; + const char *real_name = NULL; + char *shortname_or_alias; + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmpctx, i_name, &shortname_or_alias, NULL); + if (ret != EOK) { + goto done; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto fail; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible? */ + status = ctx->ops.getpwnam_r(shortname_or_alias, pwd, + buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwnam failed [%d]: %s\n", ret, strerror(ret)); + goto fail; + } + + if (del_user) { + ret = delete_user(dom, i_name, 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not delete user\n"); + goto fail; + } + goto done; + } + + uid = pwd->pw_uid; + + /* Canonicalize the username in case it was actually an alias */ + if (ctx->fast_alias == true) { + ret = sysdb_getpwuid(tmpctx, dom, uid, &cached_pwd); + if (ret != EOK) { + /* Non-fatal, attempt to canonicalize online */ + DEBUG(SSSDBG_TRACE_FUNC, "Request to cache failed [%d]: %s\n", + ret, strerror(ret)); + } + + if (ret == EOK && cached_pwd->count == 1) { + real_name = ldb_msg_find_attr_as_string(cached_pwd->msgs[0], + SYSDB_NAME, NULL); + if (!real_name) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cached user has no name?\n"); + } + } + } + + if (real_name == NULL) { + memset(buffer, 0, buflen); + + status = ctx->ops.getpwuid_r(uid, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwuid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + real_name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (real_name == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (del_user) { + ret = delete_user(dom, i_name, uid); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not delete user\n"); + goto fail; + } + goto done; + } + + ret = save_user(dom, pwd, real_name, i_name); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not save user\n"); + goto fail; + } + + ret = get_initgr_groups_process(tmpctx, ctx, sysdb, dom, pwd); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not process initgroups\n"); + goto fail; + } + +done: + ret = sysdb_transaction_commit(sysdb); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + +fail: + talloc_zfree(tmpctx); + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + return ret; +} + +static int remove_group_members(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const struct passwd *pwd, + long int num_gids, + const gid_t *gids, + long int num_cached_gids, + const gid_t *cached_gids) +{ + TALLOC_CTX *tmp_ctx = NULL; + int i = 0, j = 0; + int ret = EOK; + const char *groupname = NULL; + const char *username = NULL; + bool group_found = false; + struct ldb_result *res = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + username = sss_create_internal_fqname(tmp_ctx, pwd->pw_name, dom->name); + if (username == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create fqdn '%s'\n", pwd->pw_name); + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_cached_gids; i++) { + group_found = false; + /* group 0 is the primary group so it can be skipped */ + for (j = 1; j < num_gids; j++) { + if (cached_gids[i] == gids[j]) { + group_found = true; + break; + } + } + + if (!group_found) { + ret = sysdb_getgrgid(tmp_ctx, dom, cached_gids[i], &res); + if (ret != EOK || res->count != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_getgrgid failed for GID [%d].\n", cached_gids[i]); + continue; + } + + groupname = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, NULL); + if (groupname == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Attribute is missing but this should never happen!\n"); + continue; + } + + ret = sysdb_remove_group_member(dom, groupname, + username, + SYSDB_MEMBER_USER, false); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not remove member [%s] from group [%s]\n", + username, groupname); + continue; + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int get_cached_user_groups(struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const struct passwd *pwd, + unsigned int *_num_cached_gids, + gid_t **_cached_gids) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret = EOK; + int i = 0, j = 0; + gid_t gid = 0; + gid_t *cached_gids = NULL; + const char *username = NULL; + struct ldb_result *res = NULL; + + if (_num_cached_gids == NULL || _cached_gids == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + goto done; + } + + username = sss_create_internal_fqname(tmp_ctx, pwd->pw_name, dom->name); + if (username == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create fqdn '%s'\n", pwd->pw_name); + ret = ENOMEM; + goto done; + } + + ret = sysdb_initgroups(tmp_ctx, dom, username, &res); + /* the first element is the user itself so it can be skipped */ + if (ret == EOK && res->count > 1) { + cached_gids = talloc_array(tmp_ctx, gid_t, res->count - 1); + + for (i = 1; i < res->count; i++) { + gid = ldb_msg_find_attr_as_uint(res->msgs[i], SYSDB_GIDNUM, 0); + if (gid != 0) { + cached_gids[j] = gid; + j++; + } + } + + *_num_cached_gids = j; + *_cached_gids = talloc_steal(sysdb, cached_gids); + } else if (ret == EOK) { + *_num_cached_gids = 0; + *_cached_gids = NULL; + } else { + goto done; + } + + ret = EOK; + +done: + talloc_zfree(tmp_ctx); + + return ret; +} + +static int get_initgr_groups_process(TALLOC_CTX *memctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct passwd *pwd) +{ + enum nss_status status; + long int limit; + long int size; + long int num; + long int num_gids; + gid_t *gids; + int ret; + int i; + time_t now; + gid_t *cached_gids = NULL; + unsigned int num_cached_gids = 0; + + num_gids = 0; + limit = 4096; + num = 4096; + size = num*sizeof(gid_t); + gids = talloc_size(memctx, size); + if (!gids) { + return ENOMEM; + } + + /* nss modules may skip the primary group when we pass it in so always add + * it in advance */ + gids[0] = pwd->pw_gid; + num_gids++; + + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible? */ + do { + status = ctx->ops.initgroups_dyn(pwd->pw_name, pwd->pw_gid, &num_gids, + &num, &gids, limit, &ret); + + if (status == NSS_STATUS_TRYAGAIN) { + /* buffer too small? */ + if (size < MAX_BUF_SIZE) { + num *= 2; + size = num*sizeof(gid_t); + } + if (size > MAX_BUF_SIZE) { + size = MAX_BUF_SIZE; + num = size/sizeof(gid_t); + } + limit = num; + gids = talloc_realloc_size(memctx, gids, size); + if (!gids) { + return ENOMEM; + } + } + } while(status == NSS_STATUS_TRYAGAIN); + + switch (status) { + case NSS_STATUS_NOTFOUND: + DEBUG(SSSDBG_FUNC_DATA, "The initgroups call returned 'NOTFOUND'. " + "Assume the user is only member of its " + "primary group (%"SPRIgid")\n", pwd->pw_gid); + /* fall through */ + SSS_ATTRIBUTE_FALLTHROUGH; + case NSS_STATUS_SUCCESS: + DEBUG(SSSDBG_CONF_SETTINGS, "User [%s] appears to be member of %lu " + "groups\n", pwd->pw_name, num_gids); + + ret = get_cached_user_groups(sysdb, dom, pwd, &num_cached_gids, &cached_gids); + if (ret) { + return ret; + } + ret = remove_group_members(ctx, dom, pwd, num_gids, gids, num_cached_gids, cached_gids); + talloc_free(cached_gids); + if (ret) { + return ret; + } + + now = time(NULL); + for (i = 0; i < num_gids; i++) { + ret = get_gr_gid(memctx, ctx, sysdb, dom, gids[i], now); + if (ret) { + return ret; + } + } + ret = EOK; + + break; + + default: + DEBUG(SSSDBG_OP_FAILURE, "proxy -> initgroups_dyn failed (%d)[%s]\n", + ret, strerror(ret)); + ret = EIO; + break; + } + + return ret; +} + +/* =Proxy_Id-Functions====================================================*/ + +static struct dp_reply_std +proxy_account_info(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct dp_id_data *data, + struct be_ctx *be_ctx, + struct sss_domain_info *domain) +{ + struct dp_reply_std reply; + struct sysdb_ctx *sysdb; + uid_t uid; + gid_t gid; + errno_t ret; + char *endptr; + + sysdb = domain->sysdb; + + /* Proxy provider does not support security ID lookups. */ + if (data->filter_type == BE_FILTER_SECID) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENOSYS, + "Security lookups are not supported"); + return reply; + } + + switch (data->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: /* user */ + switch (data->filter_type) { + case BE_FILTER_ENUM: + ret = enum_users(mem_ctx, ctx, sysdb, domain); + break; + + case BE_FILTER_NAME: + ret = get_pw_name(ctx, domain, data->filter_value); + break; + + case BE_FILTER_IDNUM: + uid = (uid_t) strtouint32(data->filter_value, &endptr, 10); + if (errno || *endptr || (data->filter_value == endptr)) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid attr type"); + return reply; + } + ret = get_pw_uid(ctx, domain, uid); + break; + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + break; + + case BE_REQ_GROUP: /* group */ + switch (data->filter_type) { + case BE_FILTER_ENUM: + ret = enum_groups(mem_ctx, ctx, sysdb, domain); + break; + case BE_FILTER_NAME: + ret = get_gr_name(ctx, sysdb, domain, data->filter_value); + break; + case BE_FILTER_IDNUM: + gid = (gid_t) strtouint32(data->filter_value, &endptr, 10); + if (errno || *endptr || (data->filter_value == endptr)) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid attr type"); + return reply; + } + ret = get_gr_gid(mem_ctx, ctx, sysdb, domain, gid, 0); + break; + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + break; + + case BE_REQ_INITGROUPS: /* init groups for user */ + if (data->filter_type != BE_FILTER_NAME) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + if (ctx->ops.initgroups_dyn == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Initgroups call not supported"); + return reply; + } + ret = get_initgr(mem_ctx, ctx, sysdb, domain, data->filter_value); + break; + + case BE_REQ_NETGROUP: + if (data->filter_type != BE_FILTER_NAME) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + if (ctx->ops.setnetgrent == NULL || ctx->ops.getnetgrent_r == NULL || + ctx->ops.endnetgrent == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Netgroups are not supported"); + return reply; + } + + ret = get_netgroup(ctx, domain, data->filter_value); + break; + + case BE_REQ_SERVICES: + switch (data->filter_type) { + case BE_FILTER_NAME: + if (ctx->ops.getservbyname_r == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Services are not supported"); + return reply; + } + ret = get_serv_byname(ctx, domain, + data->filter_value, + data->extra_value); + break; + case BE_FILTER_IDNUM: + if (ctx->ops.getservbyport_r == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Services are not supported"); + return reply; + } + ret = get_serv_byport(ctx, domain, + data->filter_value, + data->extra_value); + break; + case BE_FILTER_ENUM: + if (!ctx->ops.setservent + || !ctx->ops.getservent_r + || !ctx->ops.endservent) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Services are not supported"); + return reply; + } + ret = enum_services(ctx, sysdb, domain); + break; + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + break; + + case BE_REQ_BY_CERT: + if (data->filter_type != BE_FILTER_CERT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected filter type for lookup by cert: %d\n", + data->filter_type); + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Unexpected filter type for lookup by cert"); + return reply; + } + + if (ctx->sss_certmap_ctx == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Certificate mapping not configured.\n"); + ret = EOK; + break; + } + + ret = proxy_map_cert_to_user(ctx, data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "proxy_map_cert_to_user failed\n"); + } + break; + + default: /*fail*/ + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + + if (ret) { + if (ret == ENXIO) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy returned UNAVAIL error, going offline!\n"); + be_mark_offline(be_ctx); + } + + dp_reply_std_set(&reply, DP_ERR_FATAL, ret, NULL); + return reply; + } + + dp_reply_std_set(&reply, DP_ERR_OK, EOK, NULL); + return reply; +} + +struct proxy_account_info_handler_state { + struct dp_reply_std reply; +}; + +struct tevent_req * +proxy_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params) +{ + struct proxy_account_info_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct proxy_account_info_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->reply = proxy_account_info(state, id_ctx, data, params->be_ctx, + params->be_ctx->domain); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +errno_t proxy_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct proxy_account_info_handler_state *state = NULL; + + state = tevent_req_data(req, struct proxy_account_info_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/proxy/proxy_init.c b/src/providers/proxy/proxy_init.c new file mode 100644 index 0000000..b3ffad8 --- /dev/null +++ b/src/providers/proxy/proxy_init.c @@ -0,0 +1,519 @@ +/* + SSSD + + proxy_init.c + + Authors: + Stephen Gallagher + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "config.h" + +#include "util/sss_format.h" +#include "providers/proxy/proxy.h" + +#define OPT_MAX_CHILDREN_DEFAULT 10 + +static errno_t proxy_id_conf(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + char **_libname, + bool *_fast_alias) +{ + TALLOC_CTX *tmp_ctx; + char *libname; + bool fast_alias; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = confdb_get_string(be_ctx->cdb, tmp_ctx, be_ctx->conf_path, + CONFDB_PROXY_LIBNAME, NULL, &libname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read confdb [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } else if (libname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No library name given\n"); + ret = ENOENT; + goto done; + } + + ret = confdb_get_bool(be_ctx->cdb, be_ctx->conf_path, + CONFDB_PROXY_FAST_ALIAS, false, &fast_alias); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read confdb [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + *_libname = talloc_steal(mem_ctx, libname); + *_fast_alias = fast_alias; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +#define LOCAL_AUTH_POLICY_MATCH "match" +#define LOCAL_AUTH_POLICY_ONLY "only" +#define LOCAL_AUTH_POLICY_ENABLE "enable" + +static bool local_auth_enabled(struct be_ctx *be_ctx) +{ + int ret; + char *local_policy = NULL; + bool res; + + ret = confdb_get_string(be_ctx->cdb, NULL, be_ctx->conf_path, + CONFDB_DOMAIN_LOCAL_AUTH_POLICY, + LOCAL_AUTH_POLICY_MATCH, &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get the confdb local_auth_policy\n"); + return false; + } + + res = (strcasecmp(local_policy, LOCAL_AUTH_POLICY_ONLY) == 0 + || strcasestr(local_policy, LOCAL_AUTH_POLICY_ENABLE":") != NULL); + + talloc_free(local_policy); + + return res; +} + +static errno_t proxy_auth_conf(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + char **_pam_target) +{ + char *pam_target; + errno_t ret; + + ret = confdb_get_string(be_ctx->cdb, mem_ctx, be_ctx->conf_path, + CONFDB_PROXY_PAM_TARGET, NULL, &pam_target); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read confdb [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (pam_target == NULL) { + if (local_auth_enabled(be_ctx)) { + DEBUG(SSSDBG_TRACE_FUNC, + "Option ["CONFDB_PROXY_PAM_TARGET"] is missing but local " \ + "authentication is enabled.\n"); + return EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing option "CONFDB_PROXY_PAM_TARGET" and local " \ + "authentication isn't enable as well.\n"); + return EINVAL; + } + } + + *_pam_target = pam_target; + + return EOK; +} + +static errno_t proxy_resolver_conf(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + char **_libname) +{ + TALLOC_CTX *tmp_ctx; + char *libname; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = confdb_get_string(be_ctx->cdb, tmp_ctx, be_ctx->conf_path, + CONFDB_PROXY_RESOLVER_LIBNAME, NULL, &libname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read confdb [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } else if (libname == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No resolver library name given\n"); + ret = ENOENT; + goto done; + } + + *_libname = talloc_steal(mem_ctx, libname); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t proxy_init_auth_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + struct proxy_auth_ctx **_auth_ctx) +{ + struct proxy_auth_ctx *auth_ctx; + errno_t ret; + int hret; + int max_children; + + auth_ctx = talloc_zero(mem_ctx, struct proxy_auth_ctx); + if (auth_ctx == NULL) { + return ENOMEM; + } + + auth_ctx->be = be_ctx; + auth_ctx->timeout_ms = SSS_CLI_SOCKET_TIMEOUT / 4; + auth_ctx->next_id = 1; + + ret = proxy_auth_conf(auth_ctx, be_ctx, &auth_ctx->pam_target); + if (ret != EOK) { + goto done; + } + + ret = proxy_client_init(dp_sbus_conn(be_ctx->provider), auth_ctx); + if (ret != EOK) { + goto done; + } + + /* Set up request hash table */ + ret = confdb_get_int(be_ctx->cdb, be_ctx->conf_path, + CONFDB_PROXY_MAX_CHILDREN, + OPT_MAX_CHILDREN_DEFAULT, + &max_children); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to read confdb [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + if (max_children < 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Option " CONFDB_PROXY_MAX_CHILDREN " must be higher then 0\n"); + ret = EINVAL; + goto done; + } + auth_ctx->max_children = max_children; + + hret = hash_create(auth_ctx->max_children * 2, &auth_ctx->request_table, + NULL, NULL); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not initialize request table\n"); + ret = EIO; + goto done; + } + + *_auth_ctx = auth_ctx; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(auth_ctx); + } + + return ret; +} + +errno_t sssm_proxy_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data) +{ + struct proxy_module_ctx *module_ctx; + errno_t ret; + + module_ctx = talloc_zero(mem_ctx, struct proxy_module_ctx); + if (module_ctx == NULL) { + return ENOMEM; + } + + if (dp_target_enabled(provider, module_name, + DPT_ACCESS, DPT_AUTH, DPT_CHPASS)) { + /* Initialize auth_ctx since one of the access, auth or chpass is + * set. */ + ret = proxy_init_auth_ctx(module_ctx, be_ctx, provider, + &module_ctx->auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create auth context [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(module_ctx); + return ret; + } + } + + *_module_data = module_ctx; + + return EOK; +} + +static errno_t proxy_load_nss_symbols(struct sss_nss_ops *ops, + const char *libname) +{ + errno_t ret; + struct sss_nss_symbols syms[] = { + {(void*)&ops->getpwnam_r, true, "getpwnam_r" }, + {(void*)&ops->getpwuid_r, true, "getpwuid_r" }, + {(void*)&ops->setpwent, true, "setpwent" }, + {(void*)&ops->getpwent_r, true, "getpwent_r" }, + {(void*)&ops->endpwent, true, "endpwent" }, + {(void*)&ops->getgrnam_r, true, "getgrnam_r" }, + {(void*)&ops->getgrgid_r, true, "getgrgid_r" }, + {(void*)&ops->setgrent, true, "setgrent" }, + {(void*)&ops->getgrent_r, true, "getgrent_r" }, + {(void*)&ops->endgrent, true, "endgrent" }, + {(void*)&ops->initgroups_dyn, false, "initgroups_dyn" }, + {(void*)&ops->setnetgrent, false, "setnetgrent" }, + {(void*)&ops->getnetgrent_r, false, "getnetgrent_r" }, + {(void*)&ops->endnetgrent, false, "endnetgrent" }, + {(void*)&ops->getservbyname_r, false, "getservbyname_r" }, + {(void*)&ops->getservbyport_r, false, "getservbyport_r" }, + {(void*)&ops->setservent, false, "setservent" }, + {(void*)&ops->getservent_r, false, "getservent_r" }, + {(void*)&ops->endservent, false, "endservent" }, + }; + size_t nsyms = sizeof(syms) / sizeof(struct sss_nss_symbols); + + ret = sss_load_nss_symbols(ops, libname, syms, nsyms); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static errno_t proxy_load_nss_hosts_symbols(struct sss_nss_ops *ops, + const char *libname) +{ + errno_t ret; + struct sss_nss_symbols syms[] = { + {(void*)&ops->gethostbyname_r, true, "gethostbyname_r"}, + {(void*)&ops->gethostbyname2_r, true, "gethostbyname2_r"}, + {(void*)&ops->gethostbyaddr_r, true, "gethostbyaddr_r"}, + {(void*)&ops->sethostent, false, "sethostent"}, + {(void*)&ops->gethostent_r, false, "gethostent_r"}, + {(void*)&ops->endhostent, false, "endhostent"}, + }; + size_t nsyms = sizeof(syms) / sizeof(struct sss_nss_symbols); + + ret = sss_load_nss_symbols(ops, libname, syms, nsyms); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static errno_t proxy_load_nss_nets_symbols(struct sss_nss_ops *ops, + const char *libname) +{ + errno_t ret; + struct sss_nss_symbols syms[] = { + {(void*)&ops->getnetbyname_r, true, "getnetbyname_r"}, + {(void*)&ops->getnetbyaddr_r, true, "getnetbyaddr_r"}, + {(void*)&ops->setnetent, false, "setnetent"}, + {(void*)&ops->getnetent_r, false, "getnetent_r"}, + {(void*)&ops->endnetent, false, "endnetent"}, + }; + size_t nsyms = sizeof(syms) / sizeof(struct sss_nss_symbols); + + ret = sss_load_nss_symbols(ops, libname, syms, nsyms); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t sssm_proxy_id_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct proxy_module_ctx *module_ctx; + char *libname; + errno_t ret; + + module_ctx = talloc_get_type(module_data, struct proxy_module_ctx); + module_ctx->id_ctx = talloc_zero(module_ctx, struct proxy_id_ctx); + if (module_ctx->id_ctx == NULL) { + return ENOMEM; + } + + module_ctx->id_ctx->be = be_ctx; + + ret = proxy_id_conf(module_ctx->id_ctx, be_ctx, &libname, + &module_ctx->id_ctx->fast_alias); + if (ret != EOK) { + goto done; + } + + ret = proxy_load_nss_symbols(&module_ctx->id_ctx->ops, libname); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load NSS symbols [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_certmap_to_sysdb(be_ctx->cdb, be_ctx->domain, true); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize certificate mapping rules. " + "Authentication with certificates/Smartcards might not work " + "as expected.\n"); + /* not fatal, ignored */ + } else { + ret = proxy_init_certmap(module_ctx->id_ctx, module_ctx->id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "files_init_certmap failed. " + "Authentication with certificates/Smartcards might not work " + "as expected.\n"); + /* not fatal, ignored */ + } + } + + dp_set_method(dp_methods, DPM_ACCOUNT_HANDLER, + proxy_account_info_handler_send, proxy_account_info_handler_recv, + module_ctx->id_ctx, struct proxy_id_ctx, struct dp_id_data, + struct dp_reply_std); + + dp_set_method(dp_methods, DPM_ACCT_DOMAIN_HANDLER, + default_account_domain_send, default_account_domain_recv, NULL, + void, struct dp_get_acct_domain_data, struct dp_reply_std); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(module_ctx->id_ctx); + } + + return ret; +} + +errno_t sssm_proxy_auth_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct proxy_module_ctx *module_ctx; + + module_ctx = talloc_get_type(module_data, struct proxy_module_ctx); + + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + proxy_pam_handler_send, proxy_pam_handler_recv, + module_ctx->auth_ctx, struct proxy_auth_ctx, + struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_proxy_chpass_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + return sssm_proxy_auth_init(mem_ctx, be_ctx, module_data, dp_methods); +} + +errno_t sssm_proxy_access_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct proxy_module_ctx *module_ctx; + + module_ctx = talloc_get_type(module_data, struct proxy_module_ctx); + + dp_set_method(dp_methods, DPM_ACCESS_HANDLER, + proxy_pam_handler_send, proxy_pam_handler_recv, + module_ctx->auth_ctx, struct proxy_auth_ctx, + struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_proxy_resolver_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct proxy_module_ctx *module_ctx; + char *libname; + errno_t ret; + + module_ctx = talloc_get_type(module_data, struct proxy_module_ctx); + + module_ctx->resolver_ctx = talloc_zero(mem_ctx, struct proxy_resolver_ctx); + if (module_ctx->resolver_ctx == NULL) { + return ENOMEM; + } + + ret = proxy_resolver_conf(module_ctx->resolver_ctx, be_ctx, &libname); + if (ret == ENOENT) { + ret = ENOTSUP; + goto done; + } else if (ret != EOK) { + goto done; + } + + ret = proxy_load_nss_hosts_symbols(&module_ctx->resolver_ctx->ops, libname); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load NSS symbols [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = proxy_load_nss_nets_symbols(&module_ctx->resolver_ctx->ops, libname); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load NSS symbols [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + dp_set_method(dp_methods, DPM_RESOLVER_HOSTS_HANDLER, + proxy_hosts_handler_send, proxy_hosts_handler_recv, + module_ctx->resolver_ctx, struct proxy_resolver_ctx, + struct dp_resolver_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_RESOLVER_IP_NETWORK_HANDLER, + proxy_nets_handler_send, proxy_nets_handler_recv, + module_ctx->resolver_ctx, struct proxy_resolver_ctx, + struct dp_resolver_data, struct dp_reply_std); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(module_ctx->resolver_ctx); + } + + return ret; +} diff --git a/src/providers/proxy/proxy_ipnetworks.c b/src/providers/proxy/proxy_ipnetworks.c new file mode 100644 index 0000000..73919b8 --- /dev/null +++ b/src/providers/proxy/proxy_ipnetworks.c @@ -0,0 +1,628 @@ +/* + SSSD + + Authors: + Samuel Cabrero + + Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 3 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; 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, see . +*/ + +#include "providers/proxy/proxy.h" +#include "db/sysdb_ipnetworks.h" +#include +#include + +static errno_t +nss_status_to_errno(enum nss_status status) +{ + switch (status) { + case NSS_STATUS_SUCCESS: + return EOK; + case NSS_STATUS_TRYAGAIN: + return EAGAIN; + case NSS_STATUS_NOTFOUND: + return ENOENT; + case NSS_STATUS_UNAVAIL: + default: + break; + } + + return EIO; +} + +static errno_t +parse_netent(TALLOC_CTX *mem_ctx, + struct netent *result, + bool case_sensitive, + char **out_name, + char ***out_aliases, + char **out_address) +{ + char **aliases = *out_aliases; + char addrbuf[INET_ADDRSTRLEN]; + const char *addr = NULL; + int i; + errno_t ret; + + if (result->n_addrtype == AF_INET) { + /* result->n_net is represented in host byte order, but the NSS + * client, inet_ntop and inet_pton functions expect the address + * in network byte order + */ + uint32_t tmpaddr = htonl(result->n_net); + addr = inet_ntop(AF_INET, &tmpaddr, addrbuf, INET_ADDRSTRLEN); + } + + if (addr == NULL) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to convert address of network '%s' to a character " + "string: %s\n", result->n_name, strerror(ret)); + return ret; + } + + for (i = 0; result->n_aliases[i] != NULL; i++) { + size_t len = talloc_array_length(aliases); + const char *alias = result->n_aliases[i]; + bool found = false; + int j; + + for (j = 0; j < len && aliases != NULL && aliases[j] != NULL; j++) { + if (case_sensitive && strcmp(aliases[j], alias) == 0) { + found = true; + break; + } else if (strcasecmp(aliases[j], alias) == 0) { + found = true; + break; + } + } + + if (!found) { + ret = add_string_to_list(mem_ctx, alias, &aliases); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Network [%s] has alias [%s]\n", + result->n_name, alias); + } + } + + *out_name = talloc_strdup(mem_ctx, result->n_name); + *out_address = talloc_strdup(mem_ctx, addr); + *out_aliases = aliases; + + ret = EOK; +done: + return ret; +} + +static errno_t +proxy_save_ipnetwork(struct sss_domain_info *domain, + bool lowercase, + uint64_t cache_timeout, + char *name, + char **aliases, + char *address) +{ + errno_t ret; + char *cased_name = NULL; + const char **cased_aliases = NULL; + TALLOC_CTX *tmp_ctx; + char *lc_alias = NULL; + time_t now = time(NULL); + + DEBUG(SSSDBG_TRACE_FUNC, "Saving network [%s] into cache, domain [%s]\n", + name, domain->name); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + cased_name = sss_get_cased_name(tmp_ctx, name, + domain->case_preserve); + if (cased_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get cased name.\n"); + ret = ENOMEM; + goto done; + } + + /* Count the aliases */ + ret = sss_get_cased_name_list(tmp_ctx, + (const char * const *) aliases, + !lowercase, &cased_aliases); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get cased aliases.\n"); + goto done; + } + + if (domain->case_preserve) { + /* Add lowercased alias to allow case-insensitive lookup */ + lc_alias = sss_tc_utf8_str_tolower(tmp_ctx, name); + if (lc_alias == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot convert name to lowercase.\n"); + ret = ENOMEM; + goto done; + } + + ret = add_string_to_list(tmp_ctx, lc_alias, + discard_const_p(char **, &cased_aliases)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add lowercased name alias.\n"); + goto done; + } + } + + ret = sysdb_store_ipnetwork(domain, cased_name, cased_aliases, address, + NULL, NULL, cache_timeout, now); +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +get_net_byname(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + const char *search_name) +{ + TALLOC_CTX *tmp_ctx; + struct netent *result = NULL; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + int err = 0; + int h_err = 0; + enum nss_status status; + errno_t ret; + char *name = NULL; + char *address = NULL; + char **aliases = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Resolving network [%s]\n", search_name); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct netent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getnetbyname_r(search_name, result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "getnetbyname_r failed for network [%s]: %d, %s, %s.\n", + search_name, status, strerror(err), hstrerror(h_err)); + goto done; + } + + if (ret == ENOENT) { + /* Not found, make sure we remove it from the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Network [%s] not found, removing from " + "cache\n", search_name); + sysdb_ipnetwork_delete(domain, search_name, NULL); + ret = ENOENT; + goto done; + } else { + /* Found, parse result */ + ret = parse_netent(tmp_ctx, result, domain->case_sensitive, + &name, &aliases, &address); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to parse netent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Save result into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Network [%s] found as [%s], saving into " + "cache\n", search_name, name); + ret = proxy_save_ipnetwork(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, address); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store network [%s] [%d]: %s\n", + name, ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +get_net_byaddr(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + const char *search_addrstr) +{ + TALLOC_CTX *tmp_ctx; + struct netent *result = NULL; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + int err = 0; + int h_err = 0; + uint32_t addrbuf; + enum nss_status status; + errno_t ret; + char *name = NULL; + char *address = NULL; + char **aliases = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Resolving network [%s]\n", search_addrstr); + + if (inet_pton(AF_INET, search_addrstr, &addrbuf) != 1) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct netent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + /* getnetbyaddr_r expects address in host byte order */ + addrbuf = ntohl(addrbuf); + + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getnetbyaddr_r(addrbuf, AF_INET, result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "getnetbyname_r failed for network [%s]: %d, %s, %s.\n", + search_addrstr, status, strerror(err), hstrerror(h_err)); + goto done; + } + + if (ret == ENOENT) { + /* Not found, make sure we remove it from the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Network [%s] not found, removing from " + "cache\n", search_addrstr); + sysdb_ipnetwork_delete(domain, NULL, search_addrstr); + ret = ENOENT; + goto done; + } else { + /* Found, parse result */ + ret = parse_netent(tmp_ctx, result, domain->case_sensitive, + &name, &aliases, &address); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to parse netent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Save result into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Network [%s] found as [%s], saving into " + "cache\n", search_addrstr, name); + ret = proxy_save_ipnetwork(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, address); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store network [%s] [%d]: %s\n", + name, ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + + +static errno_t +getnetent_internal(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + char **out_name, + char **out_address, + char ***out_aliases) + +{ + TALLOC_CTX *tmp_ctx = NULL; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + enum nss_status status; + struct netent *result = NULL; + char *name = NULL; + char *address = NULL; + char **aliases = NULL; + int err; + int h_err; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct netent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getnetent_r(result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "getnetent_r failed: %d, %s, %s.\n", + status, strerror(err), hstrerror(h_err)); + goto done; + } + + if (ret == EOK) { + ret = parse_netent(tmp_ctx, result, domain->case_sensitive, + &name, &aliases, &address); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to parse netent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + if (name != NULL) { + *out_name = talloc_steal(mem_ctx, name); + } + if (address != NULL) { + *out_address = talloc_steal(mem_ctx, address); + } + if (aliases != NULL) { + *out_aliases = talloc_steal(mem_ctx, aliases); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +enum_ipnetworks(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain) +{ + struct sysdb_ctx *sysdb = domain->sysdb; + TALLOC_CTX *tmp_ctx = NULL; + bool in_transaction = false; + enum nss_status status; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Enumerating IP networks\n"); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.setnetent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + char *name = NULL; + char *address = NULL; + char **aliases = NULL; + + ret = getnetent_internal(ctx, domain, tmp_ctx, &name, + &address, &aliases); + if (ret == EOK) { + /* Results found. Save them into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "IP network [%s] found, saving into " + "cache\n", name); + + proxy_save_ipnetwork(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, address); + } + + /* Free children to avoid using too much memory */ + talloc_free_children(tmp_ctx); + } while (ret == EOK); + + if (ret == ENOENT) { + /* We are done, commit transaction and stop loop */ + DEBUG(SSSDBG_TRACE_FUNC, "IP networks enumeration completed.\n"); + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "getnetent_r failed [%d]: %s\n", + ret, strerror(ret)); + } + +done: + talloc_free(tmp_ctx); + if (in_transaction) { + errno_t sret; + + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not cancel transaction! [%s]\n", + strerror(sret)); + } + } + ctx->ops.endnetent(); + return ret; +} + +static struct dp_reply_std +proxy_nets_info(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *ctx, + struct dp_resolver_data *data, + struct be_ctx *be_ctx, + struct sss_domain_info *domain) +{ + struct dp_reply_std reply; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Processing networks request, filter type [%d]\n", + data->filter_type); + + switch (data->filter_type) { + case BE_FILTER_NAME: + ret = get_net_byname(ctx, domain, data->filter_value); + break; + + case BE_FILTER_ADDR: + ret = get_net_byaddr(ctx, domain, data->filter_value); + break; + + case BE_FILTER_ENUM: + ret = enum_ipnetworks(ctx, domain); + break; + + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + + if (ret) { + if (ret == ENXIO) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy returned UNAVAIL error, going offline!\n"); + be_mark_offline(be_ctx); + } + + dp_reply_std_set(&reply, DP_ERR_FATAL, ret, NULL); + return reply; + } + + dp_reply_std_set(&reply, DP_ERR_OK, EOK, NULL); + return reply; +} + +struct proxy_nets_handler_state { + struct dp_reply_std reply; +}; + +struct tevent_req * +proxy_nets_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params) +{ + struct proxy_nets_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct proxy_nets_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->reply = proxy_nets_info(state, resolver_ctx, resolver_data, + params->be_ctx, params->be_ctx->domain); + + tevent_req_done(req); + return tevent_req_post(req, params->ev); +} + +errno_t +proxy_nets_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct proxy_nets_handler_state *state; + + state = tevent_req_data(req, struct proxy_nets_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/proxy/proxy_netgroup.c b/src/providers/proxy/proxy_netgroup.c new file mode 100644 index 0000000..566af74 --- /dev/null +++ b/src/providers/proxy/proxy_netgroup.c @@ -0,0 +1,206 @@ +/* + SSSD + + Proxy netgroup handler + + Authors: + + Sumit Bose + + Copyright (C) 2010 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/proxy/proxy.h" +#include "util/util.h" + +#define BUFLEN 1024 + +#define get_triple_el(s) ((s) ? (s) : "") + +static errno_t make_netgroup_attr(struct __netgrent netgrent, + struct sysdb_attrs *attrs) +{ + int ret; + char *dummy; + + if (netgrent.type == group_val) { + ret =sysdb_attrs_add_string(attrs, SYSDB_NETGROUP_MEMBER, + netgrent.val.group); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_add_string failed.\n"); + return ret; + } + } else if (netgrent.type == triple_val) { + dummy = talloc_asprintf(attrs, "(%s,%s,%s)", + get_triple_el(netgrent.val.triple.host), + get_triple_el(netgrent.val.triple.user), + get_triple_el(netgrent.val.triple.domain)); + if (dummy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + ret = sysdb_attrs_add_string(attrs, SYSDB_NETGROUP_TRIPLE, dummy); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_add_string failed.\n"); + return ret; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown netgrent entry type [%d].\n", netgrent.type); + return EINVAL; + } + + return EOK; +} + +static errno_t save_netgroup(struct sss_domain_info *domain, + const char *name, + struct sysdb_attrs *attrs, + bool lowercase, + uint64_t cache_timeout) +{ + errno_t ret; + + if (lowercase) { + ret = sysdb_attrs_add_lc_name_alias(attrs, name); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add name alias\n"); + return ret; + } + } + + ret = sysdb_add_netgroup(domain, name, NULL, attrs, NULL, + cache_timeout, 0); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_add_netgroup failed.\n"); + return ret; + } + + return EOK; +} + +static errno_t handle_error(enum nss_status status, + struct sss_domain_info *domain, const char *name) +{ + errno_t ret; + + switch (status) { + case NSS_STATUS_SUCCESS: + DEBUG(SSSDBG_TRACE_INTERNAL, "Netgroup lookup succeeded\n"); + ret = EOK; + break; + + case NSS_STATUS_NOTFOUND: + DEBUG(SSSDBG_MINOR_FAILURE, "The netgroup was not found\n"); + ret = sysdb_delete_netgroup(domain, name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot delete netgroup: %d\n", ret); + ret = EIO; + } + break; + + case NSS_STATUS_UNAVAIL: + DEBUG(SSSDBG_TRACE_LIBS, + "The proxy target did not respond, going offline\n"); + ret = ENXIO; + break; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected error looking up netgroup\n"); + ret = EIO; + break; + } + + return ret; +} + +errno_t get_netgroup(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *name) +{ + struct __netgrent result; + enum nss_status status; + char buffer[BUFLEN]; + int ret; + TALLOC_CTX *tmp_ctx = NULL; + struct sysdb_attrs *attrs; + + memset(&result, 0, sizeof(result)); + status = ctx->ops.setnetgrent(name, &result); + if (status != NSS_STATUS_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "setnetgrent failed for netgroup [%s].\n", name); + ret = handle_error(status, dom, name); + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + do { + status = ctx->ops.getnetgrent_r(&result, buffer, BUFLEN, &ret); + if (status != NSS_STATUS_SUCCESS && + status != NSS_STATUS_RETURN && + status != NSS_STATUS_NOTFOUND) { + ret = handle_error(status, dom, name); + DEBUG(SSSDBG_OP_FAILURE, + "getnetgrent_r failed for netgroup [%s]: [%d][%s].\n", + name, ret, strerror(ret)); + goto done; + } + + if (status == NSS_STATUS_SUCCESS) { + ret = make_netgroup_attr(result, attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "make_netgroup_attr failed.\n"); + goto done; + } + } + } while (status != NSS_STATUS_RETURN && status != NSS_STATUS_NOTFOUND); + + status = ctx->ops.endnetgrent(&result); + if (status != NSS_STATUS_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "endnetgrent failed.\n"); + ret = handle_error(status, dom, name); + goto done; + } + + ret = save_netgroup(dom, name, attrs, + !dom->case_sensitive, + dom->netgroup_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "save_netgroup failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/proxy/proxy_services.c b/src/providers/proxy/proxy_services.c new file mode 100644 index 0000000..856da09 --- /dev/null +++ b/src/providers/proxy/proxy_services.c @@ -0,0 +1,372 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + 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 3 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; 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, see . +*/ + +#include "providers/proxy/proxy.h" +#include "util/util.h" +#include "util/strtonum.h" +#include "db/sysdb_services.h" + +#define BUFLEN 1024 + +errno_t +proxy_save_service(struct sss_domain_info *domain, + struct servent *svc, + bool lowercase, + uint64_t cache_timeout) +{ + errno_t ret; + char *cased_name; + const char **protocols; + const char **cased_aliases; + TALLOC_CTX *tmp_ctx; + char *lc_alias = NULL; + time_t now = time(NULL); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + cased_name = sss_get_cased_name(tmp_ctx, svc->s_name, + domain->case_preserve); + if (!cased_name) { + ret = ENOMEM; + goto done; + } + + protocols = talloc_array(tmp_ctx, const char *, 2); + if (!protocols) { + ret = ENOMEM; + goto done; + } + + protocols[0] = sss_get_cased_name(protocols, svc->s_proto, + !lowercase); + if (!protocols[0]) { + ret = ENOMEM; + goto done; + } + protocols[1] = NULL; + + /* Count the aliases */ + ret = sss_get_cased_name_list(tmp_ctx, + (const char * const *) svc->s_aliases, + !lowercase, &cased_aliases); + if (ret != EOK) { + goto done; + } + + if (domain->case_preserve) { + /* Add lowercased alias to allow case-insensitive lookup */ + lc_alias = sss_tc_utf8_str_tolower(tmp_ctx, svc->s_name); + if (lc_alias == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot convert name to lowercase.\n"); + ret = ENOMEM; + goto done; + } + + ret = add_string_to_list(tmp_ctx, lc_alias, + discard_const_p(char **, &cased_aliases)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add lowercased name alias.\n"); + goto done; + } + } + + ret = sysdb_store_service(domain, + cased_name, + ntohs(svc->s_port), + cased_aliases, + protocols, + NULL, NULL, + cache_timeout, + now); +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +get_serv_byname(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *name, + const char *protocol) +{ + errno_t ret; + enum nss_status status; + struct servent *result; + TALLOC_CTX *tmp_ctx; + char buffer[BUFLEN]; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + result = talloc_zero(tmp_ctx, struct servent); + if (!result) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getservbyname_r(name, protocol, result, + buffer, BUFLEN, &ret); + if (status != NSS_STATUS_SUCCESS && status != NSS_STATUS_NOTFOUND) { + DEBUG(SSSDBG_MINOR_FAILURE, + "getservbyname_r failed for service [%s].\n", name); + goto done; + } + + if (status == NSS_STATUS_NOTFOUND) { + /* Make sure we remove it from the cache */ + ret = sysdb_svc_delete(dom, name, 0, protocol); + } else { + + /* Results found. Save them into the cache */ + ret = proxy_save_service(dom, result, + !dom->case_sensitive, + dom->service_timeout); + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +get_serv_byport(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *be_filter, + const char *protocol) +{ + errno_t ret; + enum nss_status status; + struct servent *result; + TALLOC_CTX *tmp_ctx; + uint16_t port; + char buffer[BUFLEN]; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + result = talloc_zero(tmp_ctx, struct servent); + if (!result) { + ret = ENOMEM; + goto done; + } + + port = htons(strtouint16(be_filter, NULL, 0)); + if (errno) { + ret = errno; + goto done; + } + + status = ctx->ops.getservbyport_r(port, protocol, result, + buffer, BUFLEN, &ret); + if (status != NSS_STATUS_SUCCESS && status != NSS_STATUS_NOTFOUND) { + DEBUG(SSSDBG_MINOR_FAILURE, + "getservbyport_r failed for service [%s].\n", be_filter); + goto done; + } + + if (status == NSS_STATUS_NOTFOUND) { + /* Make sure we remove it from the cache */ + ret = sysdb_svc_delete(dom, NULL, port, protocol); + } else { + /* Results found. Save them into the cache */ + ret = proxy_save_service(dom, result, + !dom->case_sensitive, + dom->service_timeout); + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +enum_services(struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct servent *svc; + enum nss_status status; + size_t buflen; + char *buffer; + char *newbuf; + errno_t ret, sret; + time_t now = time(NULL); + const char **protocols; + const char **cased_aliases; + bool again; + + DEBUG(SSSDBG_TRACE_FUNC, "Enumerating services\n"); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + svc = talloc(tmpctx, struct servent); + if (!svc) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + protocols = talloc_zero_array(tmpctx, const char *, 2); + if (protocols == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.setservent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + again = false; + + /* always zero out the svc structure */ + memset(svc, 0, sizeof(struct servent)); + + /* get entry */ + status = ctx->ops.getservent_r(svc, buffer, buflen, &ret); + + switch (status) { + case NSS_STATUS_TRYAGAIN: + /* buffer too small? */ + if (buflen < MAX_BUF_SIZE) { + buflen *= 2; + } + if (buflen > MAX_BUF_SIZE) { + buflen = MAX_BUF_SIZE; + } + newbuf = talloc_realloc_size(tmpctx, buffer, buflen); + if (!newbuf) { + ret = ENOMEM; + goto done; + } + buffer = newbuf; + again = true; + break; + + case NSS_STATUS_NOTFOUND: + + /* we are done here */ + DEBUG(SSSDBG_TRACE_FUNC, "Enumeration completed.\n"); + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + + in_transaction = false; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Service found (%s, %d/%s)\n", + svc->s_name, svc->s_port, svc->s_proto); + + protocols[0] = sss_get_cased_name(protocols, svc->s_proto, + dom->case_sensitive); + if (!protocols[0]) { + ret = ENOMEM; + goto done; + } + protocols[1] = NULL; + + ret = sss_get_cased_name_list(tmpctx, + (const char * const *) svc->s_aliases, + dom->case_sensitive, &cased_aliases); + if (ret != EOK) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store service [%s]. Ignoring.\n", + strerror(ret)); + again = true; + break; + } + + ret = sysdb_store_service(dom, + svc->s_name, + svc->s_port, + cased_aliases, + protocols, + NULL, NULL, + dom->service_timeout, + now); + if (ret) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store service [%s]. Ignoring.\n", + strerror(ret)); + } + again = true; + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + break; + + default: + ret = EIO; + DEBUG(SSSDBG_CRIT_FAILURE, + "proxy -> getservent_r failed (%d)[%s]\n", + ret, strerror(ret)); + break; + } + } while (again); + +done: + talloc_zfree(tmpctx); + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not cancel transaction! [%s]\n", + strerror(sret)); + } + } + ctx->ops.endservent(); + return ret; +} diff --git a/src/providers/simple/simple_access.c b/src/providers/simple/simple_access.c new file mode 100644 index 0000000..49226ad --- /dev/null +++ b/src/providers/simple/simple_access.c @@ -0,0 +1,346 @@ +/* + SSSD + + Simple access control + + Copyright (C) Sumit Bose 2010 + + 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 3 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; 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, see . +*/ + +#include + +#include "providers/simple/simple_access.h" +#include "providers/simple/simple_access_pvt.h" +#include "util/sss_utf8.h" +#include "providers/backend.h" +#include "db/sysdb.h" + +#define CONFDB_SIMPLE_ALLOW_USERS "simple_allow_users" +#define CONFDB_SIMPLE_DENY_USERS "simple_deny_users" + +#define CONFDB_SIMPLE_ALLOW_GROUPS "simple_allow_groups" +#define CONFDB_SIMPLE_DENY_GROUPS "simple_deny_groups" + +#define TIMEOUT_OF_REFRESH_FILTER_LISTS 5 + +static errno_t simple_access_parse_names(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + char **list, + char ***_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + char **out = NULL; + size_t size; + size_t i; + errno_t ret; + char *domname = NULL; + char *shortname = NULL; + struct sss_domain_info *domain; + + if (list == NULL) { + *_out = NULL; + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + ret = ENOMEM; + goto done; + } + + for (size = 0; list[size] != NULL; size++) { + /* count size */ + } + + out = talloc_zero_array(tmp_ctx, char*, size + 1); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + /* Since this is access provider, we should fail on any error so we don't + * allow unauthorized access. */ + for (i = 0; i < size; i++) { + ret = sss_parse_name(tmp_ctx, be_ctx->domain->names, list[i], + &domname, &shortname); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_parse_name failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (domname != NULL) { + domain = find_domain_by_name(be_ctx->domain, domname, true); + if (domain == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + } else { + domain = be_ctx->domain; + } + + out[i] = sss_create_internal_fqname(out, shortname, domain->name); + if (out[i] == NULL) { + ret = EIO; + goto done; + } + } + + *_out = talloc_steal(mem_ctx, out); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int simple_access_obtain_filter_lists(struct simple_ctx *ctx) +{ + struct be_ctx *bectx = ctx->be_ctx; + int ret; + int i; + struct { + const char *name; + const char *option; + char **orig_list; + char **ctx_list; + } lists[] = {{"Allow users", CONFDB_SIMPLE_ALLOW_USERS, NULL, NULL}, + {"Deny users", CONFDB_SIMPLE_DENY_USERS, NULL, NULL}, + {"Allow groups", CONFDB_SIMPLE_ALLOW_GROUPS, NULL, NULL}, + {"Deny groups", CONFDB_SIMPLE_DENY_GROUPS, NULL, NULL}, + {NULL, NULL, NULL, NULL}}; + + + ret = sysdb_master_domain_update(bectx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_FUNC_DATA, "Update of master domain failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto failed; + } + + for (i = 0; lists[i].name != NULL; i++) { + ret = confdb_get_string_as_list(bectx->cdb, ctx, bectx->conf_path, + lists[i].option, &lists[i].orig_list); + if (ret == ENOENT) { + DEBUG(SSSDBG_FUNC_DATA, "%s list is empty.\n", lists[i].name); + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "confdb_get_string_as_list failed.\n"); + goto failed; + } + + ret = simple_access_parse_names(ctx, bectx, lists[i].orig_list, + &lists[i].ctx_list); + talloc_free(lists[i].orig_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse %s list [%d]: %s\n", + lists[i].name, ret, sss_strerror(ret)); + goto failed; + } + } + + talloc_free(ctx->allow_users); + ctx->allow_users = talloc_steal(ctx, lists[0].ctx_list); + + talloc_free(ctx->deny_users); + ctx->deny_users = talloc_steal(ctx, lists[1].ctx_list); + + talloc_free(ctx->allow_groups); + ctx->allow_groups = talloc_steal(ctx, lists[2].ctx_list); + + talloc_free(ctx->deny_groups); + ctx->deny_groups = talloc_steal(ctx, lists[3].ctx_list); + + if (!ctx->allow_users && + !ctx->allow_groups && + !ctx->deny_users && + !ctx->deny_groups) { + DEBUG(SSSDBG_OP_FAILURE, + "No rules supplied for simple access provider. " + "Access will be granted for all users.\n"); + } + + + return EOK; + +failed: + for (i = 0; lists[i].name != NULL; i++) { + talloc_free(lists[i].ctx_list); + } + + return ret; +} + +struct simple_access_handler_state { + struct pam_data *pd; +}; + +static void simple_access_handler_done(struct tevent_req *subreq); + +struct tevent_req * +simple_access_handler_send(TALLOC_CTX *mem_ctx, + struct simple_ctx *simple_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct simple_access_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + time_t now; + + req = tevent_req_create(mem_ctx, &state, + struct simple_access_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + + pd->pam_status = PAM_SYSTEM_ERR; + if (pd->cmd != SSS_PAM_ACCT_MGMT) { + DEBUG(SSSDBG_CONF_SETTINGS, + "simple access does not handle pam task %d.\n", pd->cmd); + pd->pam_status = PAM_MODULE_UNKNOWN; + goto immediately; + } + + now = time(NULL); + if ((now - simple_ctx->last_refresh_of_filter_lists) + > TIMEOUT_OF_REFRESH_FILTER_LISTS) { + + ret = simple_access_obtain_filter_lists(simple_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to refresh filter lists, denying all access\n"); + pd->pam_status = PAM_PERM_DENIED; + goto immediately; + } + simple_ctx->last_refresh_of_filter_lists = now; + } + + subreq = simple_access_check_send(state, params->ev, simple_ctx, pd->user); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, simple_access_handler_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void simple_access_handler_done(struct tevent_req *subreq) +{ + struct simple_access_handler_state *state; + struct tevent_req *req; + bool access_granted; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct simple_access_handler_state); + + ret = simple_access_check_recv(subreq, &access_granted); + talloc_free(subreq); + if (ret != EOK) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + if (access_granted) { + state->pd->pam_status = PAM_SUCCESS; + } else { + state->pd->pam_status = PAM_PERM_DENIED; + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +simple_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct simple_access_handler_state *state = NULL; + + state = tevent_req_data(req, struct simple_access_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} + +errno_t sssm_simple_access_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct simple_ctx *ctx; + int ret; + int i; + char *simple_list_values = NULL; + const char *simple_access_lists[] = {CONFDB_SIMPLE_ALLOW_USERS, + CONFDB_SIMPLE_DENY_USERS, + CONFDB_SIMPLE_ALLOW_GROUPS, + CONFDB_SIMPLE_DENY_GROUPS, + NULL}; + + ctx = talloc_zero(mem_ctx, struct simple_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed.\n"); + return ENOMEM; + } + + for (i = 0; simple_access_lists[i] != NULL; i++) { + ret = confdb_get_string(be_ctx->cdb, mem_ctx, be_ctx->conf_path, + simple_access_lists[i], NULL, + &simple_list_values); + + if (simple_list_values == NULL) { + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "confdb_get_string failed.\n"); + return ret; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "%s values: [%s]\n", + simple_access_lists[i], + simple_list_values); + } + + ctx->domain = be_ctx->domain; + ctx->be_ctx = be_ctx; + ctx->last_refresh_of_filter_lists = 0; + + dp_set_method(dp_methods, DPM_ACCESS_HANDLER, + simple_access_handler_send, simple_access_handler_recv, ctx, + struct simple_ctx, struct pam_data, struct pam_data *); + + return EOK; +} diff --git a/src/providers/simple/simple_access.h b/src/providers/simple/simple_access.h new file mode 100644 index 0000000..a618b2e --- /dev/null +++ b/src/providers/simple/simple_access.h @@ -0,0 +1,47 @@ +/* + SSSD + + Simple access control + + Copyright (C) Sumit Bose 2010 + + 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 3 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; 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, see . +*/ + +#ifndef __SIMPLE_ACCESS_H__ +#define __SIMPLE_ACCESS_H__ + +#include "util/util.h" + +struct simple_ctx { + struct sss_domain_info *domain; + struct be_ctx *be_ctx; + + char **allow_users; + char **deny_users; + char **allow_groups; + char **deny_groups; + + time_t last_refresh_of_filter_lists; +}; + +struct tevent_req *simple_access_check_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct simple_ctx *ctx, + const char *username); + +errno_t simple_access_check_recv(struct tevent_req *req, + bool *access_granted); + +#endif /* __SIMPLE_ACCESS_H__ */ diff --git a/src/providers/simple/simple_access_check.c b/src/providers/simple/simple_access_check.c new file mode 100644 index 0000000..06f3c39 --- /dev/null +++ b/src/providers/simple/simple_access_check.c @@ -0,0 +1,847 @@ +/* + SSSD + + Simple access control + + Copyright (C) Sumit Bose 2010 + + 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 3 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; 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, see . +*/ + +#include "providers/backend.h" +#include "providers/simple/simple_access.h" +#include "util/sss_utf8.h" +#include "db/sysdb.h" + +#define NON_EXIST_USR_ALLOW "The user %s does not exist. Possible typo in simple_allow_users.\n" +#define NON_EXIST_USR_DENY "The user %s does not exist. Possible typo in simple_deny_users.\n" +#define NON_EXIST_GRP_ALLOW "The group %s does not exist. Possible typo in simple_allow_groups.\n" +#define NON_EXIST_GRP_DENY "The group %s does not exist. Possible typo in simple_deny_groups.\n" + +static bool +is_posix(const struct ldb_message *group) +{ + const char *val; + + val = ldb_msg_find_attr_as_string(group, SYSDB_POSIX, NULL); + if (!val || /* Groups are posix by default */ + strcasecmp(val, "TRUE") == 0) { + return true; + } + + return false; +} + +/* Returns EOK if the result is definitive, EAGAIN if only partial result + */ +static errno_t +simple_check_users(struct simple_ctx *ctx, const char *username, + bool *access_granted) +{ + struct sss_domain_info *domain = NULL; + int i; + + /* First, check whether the user is in the allowed users list */ + if (ctx->allow_users != NULL) { + for(i = 0; ctx->allow_users[i] != NULL; i++) { + DEBUG(SSSDBG_TRACE_ALL, + "Checking against allow list username [%s].\n", + ctx->allow_users[i]); + domain = find_domain_by_object_name(ctx->domain, + ctx->allow_users[i]); + if (domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, NON_EXIST_USR_ALLOW, + ctx->allow_users[i]); + sss_log(SSS_LOG_CRIT, NON_EXIST_USR_ALLOW, + ctx->allow_users[i]); + continue; + } + + if (sss_string_equal(domain->case_sensitive, username, + ctx->allow_users[i])) { + DEBUG(SSSDBG_TRACE_LIBS, + "User [%s] found in allow list, access granted.\n", + username); + + /* Do not return immediately on explicit allow + * We need to make sure none of the user's groups + * are denied. But there's no need to check username + * matches any more. + */ + *access_granted = true; + break; + } + } + } else if (!ctx->allow_groups) { + /* If neither allow rule is in place, we'll assume allowed + * unless a deny rule disables us below. + */ + DEBUG(SSSDBG_TRACE_LIBS, + "No allow rule, assuming allow unless explicitly denied\n"); + *access_granted = true; + } + + /* Next check whether this user has been specifically denied */ + if (ctx->deny_users != NULL) { + for(i = 0; ctx->deny_users[i] != NULL; i++) { + DEBUG(SSSDBG_TRACE_ALL, + "Checking against deny list username [%s].\n", + ctx->deny_users[i]); + domain = find_domain_by_object_name(ctx->domain, + ctx->deny_users[i]); + if (domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, NON_EXIST_USR_DENY, + ctx->deny_users[i]); + sss_log(SSS_LOG_CRIT, NON_EXIST_USR_DENY, + ctx->deny_users[i]); + return EINVAL; + } + + if (sss_string_equal(domain->case_sensitive, username, + ctx->deny_users[i])) { + DEBUG(SSSDBG_TRACE_LIBS, + "User [%s] found in deny list, access denied.\n", + ctx->deny_users[i]); + + /* Return immediately on explicit denial */ + *access_granted = false; + return EOK; + } + } + } + + return EAGAIN; +} + +static errno_t +simple_check_groups(struct simple_ctx *ctx, const char **group_names, + bool *access_granted) +{ + struct sss_domain_info *domain = NULL; + bool matched; + int i, j; + + /* Now process allow and deny group rules + * If access was already granted above, we'll skip + * this redundant rule check + */ + if (ctx->allow_groups && !*access_granted) { + matched = false; + for (i = 0; ctx->allow_groups[i]; i++) { + DEBUG(SSSDBG_TRACE_ALL, + "Checking against allow list group name [%s].\n", + ctx->allow_groups[i]); + domain = find_domain_by_object_name(ctx->domain, + ctx->allow_groups[i]); + if (domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, NON_EXIST_GRP_ALLOW, + ctx->allow_groups[i]); + sss_log(SSS_LOG_CRIT, NON_EXIST_GRP_ALLOW, + ctx->allow_groups[i]); + + continue; + } + + for(j = 0; group_names[j]; j++) { + if (sss_string_equal(domain->case_sensitive, + group_names[j], ctx->allow_groups[i])) { + matched = true; + break; + } + } + + /* If any group has matched, we can skip out on the + * processing early + */ + if (matched) { + DEBUG(SSSDBG_TRACE_LIBS, + "Group [%s] found in allow list, access granted.\n", + group_names[j]); + *access_granted = true; + break; + } + } + } + + /* Finally, process the deny group rules */ + if (ctx->deny_groups) { + matched = false; + for (i = 0; ctx->deny_groups[i]; i++) { + DEBUG(SSSDBG_TRACE_ALL, + "Checking against deny list group name [%s].\n", + ctx->deny_groups[i]); + domain = find_domain_by_object_name(ctx->domain, + ctx->deny_groups[i]); + if (domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, NON_EXIST_GRP_DENY, + ctx->deny_groups[i]); + sss_log(SSS_LOG_CRIT, NON_EXIST_GRP_DENY, + ctx->deny_groups[i]); + + return EINVAL; + } + + for(j = 0; group_names[j]; j++) { + if (sss_string_equal(domain->case_sensitive, + group_names[j], ctx->deny_groups[i])) { + matched = true; + break; + } + } + + /* If any group has matched, we can skip out on the + * processing early + */ + if (matched) { + DEBUG(SSSDBG_TRACE_LIBS, + "Group [%s] found in deny list, access denied.\n", + group_names[j]); + *access_granted = false; + break; + } + } + } + + return EOK; +} + +struct simple_resolve_group_state { + struct sss_domain_info *domain; + gid_t gid; + struct simple_ctx *ctx; + + const char *name; +}; + +static errno_t +simple_resolve_group_check(struct simple_resolve_group_state *state); +static void simple_resolve_group_done(struct tevent_req *subreq); + +static struct tevent_req * +simple_resolve_group_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct simple_ctx *ctx, + struct sss_domain_info *domain, + gid_t gid) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct simple_resolve_group_state *state; + struct dp_id_data *ar; + + req = tevent_req_create(mem_ctx, &state, + struct simple_resolve_group_state); + if (!req) return NULL; + + state->domain = domain; + state->gid = gid; + state->ctx = ctx; + + /* First check if the group was updated already. If it was (maybe its + * parent was updated first), then just shortcut */ + ret = simple_resolve_group_check(state); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_LIBS, "Group already updated\n"); + ret = EOK; + goto done; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot check if group was already updated [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + /* EAGAIN - still needs update */ + + ar = talloc(state, struct dp_id_data); + if (!ar) { + ret = ENOMEM; + goto done; + } + + ar->entry_type = BE_REQ_GROUP; + ar->filter_type = BE_FILTER_IDNUM; + ar->filter_value = talloc_asprintf(ar, "%llu", (unsigned long long) gid); + ar->domain = talloc_strdup(ar, state->domain->name); + if (!ar->domain || !ar->filter_value) { + ret = ENOMEM; + goto done; + } + + subreq = dp_req_send(state, ctx->be_ctx->provider, ar->domain, + "Simple Resolve Group", 0, NULL, + DPT_ID, DPM_ACCOUNT_HANDLER, 0, ar, NULL); + if (!subreq) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, simple_resolve_group_done, req); + + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t +simple_resolve_group_check(struct simple_resolve_group_state *state) +{ + errno_t ret; + struct ldb_message *group; + const char *group_attrs[] = { SYSDB_NAME, SYSDB_POSIX, + SYSDB_GIDNUM, NULL }; + + /* Check the cache by GID again and fetch the name */ + ret = sysdb_search_group_by_gid(state, state->domain, state->gid, + group_attrs, &group); + if (ret == ENOENT) { + /* The group is missing, we will try to update it. */ + return EAGAIN; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not look up group by gid [%"SPRIgid"]: [%d][%s]\n", + state->gid, ret, sss_strerror(ret)); + return ret; + } + + state->name = ldb_msg_find_attr_as_string(group, SYSDB_NAME, NULL); + if (!state->name) { + DEBUG(SSSDBG_OP_FAILURE, "No group name\n"); + return ERR_ACCOUNT_UNKNOWN; + } + + if (is_posix(group) == false) { + DEBUG(SSSDBG_TRACE_LIBS, + "The group is still non-POSIX\n"); + return EAGAIN; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Got POSIX group\n"); + return EOK; +} + +static void simple_resolve_group_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct simple_resolve_group_state *state; + struct dp_reply_std *reply; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct simple_resolve_group_state); + + ret = dp_req_recv_ptr(state, subreq, struct dp_reply_std, &reply); + talloc_zfree(subreq); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "dp_req_recv failed\n"); + tevent_req_error(req, ret); + return; + } + + if (reply->dp_error != DP_ERR_OK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot refresh data from DP: %u,%u: %s\n", + reply->dp_error, reply->error, reply->message); + tevent_req_error(req, EIO); + return; + } + + /* Check the cache by GID again and fetch the name */ + ret = simple_resolve_group_check(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Refresh failed\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t +simple_resolve_group_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + const char **name) +{ + struct simple_resolve_group_state *state; + + state = tevent_req_data(req, struct simple_resolve_group_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *name = talloc_strdup(mem_ctx, state->name); + return EOK; +} + +struct simple_group { + struct sss_domain_info *domain; + gid_t gid; +}; + +struct simple_check_groups_state { + struct tevent_context *ev; + struct simple_ctx *ctx; + struct sss_domain_info *domain; + + struct simple_group *lookup_groups; + size_t num_groups; + size_t giter; + + const char **group_names; + size_t num_names; + + bool failed_to_resolve_groups; +}; + +static void simple_check_get_groups_next(struct tevent_req *subreq); + +static errno_t +simple_check_get_groups_primary(struct simple_check_groups_state *state, + gid_t gid); +static errno_t +simple_check_process_group(struct simple_check_groups_state *state, + struct ldb_message *group); + +static struct tevent_req * +simple_check_get_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct simple_ctx *ctx, + const char *username) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct simple_check_groups_state *state; + const char *attrs[] = { SYSDB_NAME, SYSDB_POSIX, SYSDB_GIDNUM, + SYSDB_SID_STR, NULL }; + size_t group_count; + struct ldb_message *user; + struct ldb_message **groups; + int i; + gid_t gid; + + req = tevent_req_create(mem_ctx, &state, + struct simple_check_groups_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->failed_to_resolve_groups = false; + + DEBUG(SSSDBG_TRACE_LIBS, "Looking up groups for user %s\n", username); + + /* get domain from username */ + state->domain = find_domain_by_object_name(ctx->domain, username); + if (state->domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid user %s!\n", username); + ret = EINVAL; + goto done; + } + + ret = sysdb_search_user_by_name(state, state->domain, username, attrs, + &user); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "No such user %s\n", username); + ret = ERR_ACCOUNT_UNKNOWN; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not look up username [%s]: [%d][%s]\n", + username, ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_asq_search(state, state->domain, + user->dn, NULL, SYSDB_MEMBEROF, + attrs, &group_count, &groups); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "User %s is a member of %zu supplemental groups\n", + username, group_count); + + /* One extra space for terminator, one extra space for private group */ + state->group_names = talloc_zero_array(state, const char *, group_count + 2); + state->lookup_groups = talloc_zero_array(state, struct simple_group, + group_count + 2); + if (!state->group_names || !state->lookup_groups) { + ret = ENOMEM; + goto done; + } + + for (i=0; i < group_count; i++) { + /* Some providers (like the AD provider) might perform initgroups + * without resolving the group names. In order for the simple access + * provider to work correctly, we need to resolve the groups before + * performing the access check. In AD provider, the situation is + * even more tricky b/c the groups HAVE name, but their name + * attribute is set to SID and they are set as non-POSIX + */ + ret = simple_check_process_group(state, groups[i]); + if (ret != EOK) { + goto done; + } + } + + gid = ldb_msg_find_attr_as_uint64(user, SYSDB_GIDNUM, 0); + if (!gid) { + DEBUG(SSSDBG_MINOR_FAILURE, "User %s has no gid?\n", username); + ret = EINVAL; + goto done; + } + + ret = simple_check_get_groups_primary(state, gid); + if (ret != EOK) { + goto done; + } + + if (state->num_groups == 0) { + /* If all groups could have been resolved by name, we are + * done + */ + DEBUG(SSSDBG_TRACE_FUNC, "All groups had name attribute\n"); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Need to resolve %zu groups\n", + state->num_groups); + state->giter = 0; + subreq = simple_resolve_group_send(req, state->ev, state->ctx, + state->lookup_groups[state->giter].domain, + state->lookup_groups[state->giter].gid); + if (!subreq) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, simple_check_get_groups_next, req); + + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static void simple_check_get_groups_next(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct simple_check_groups_state *state = + tevent_req_data(req, struct simple_check_groups_state); + errno_t ret; + + ret = simple_resolve_group_recv(subreq, state->group_names, + &state->group_names[state->num_names]); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not resolve name of group with GID %"SPRIgid"\n", + state->lookup_groups[state->giter].gid); + state->failed_to_resolve_groups = true; + } else { + state->num_names++; + } + state->giter++; + + if (state->giter < state->num_groups) { + subreq = simple_resolve_group_send(req, state->ev, state->ctx, + state->lookup_groups[state->giter].domain, + state->lookup_groups[state->giter].gid); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, simple_check_get_groups_next, req); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "All groups resolved. Done.\n"); + tevent_req_done(req); +} + +static errno_t +simple_check_process_group(struct simple_check_groups_state *state, + struct ldb_message *group) +{ + const char *name; + const char *group_sid; + struct sss_domain_info *domain; + gid_t gid; + bool posix; + + posix = is_posix(group); + name = ldb_msg_find_attr_as_string(group, SYSDB_NAME, NULL); + gid = ldb_msg_find_attr_as_uint64(group, SYSDB_GIDNUM, 0); + + /* With the current sysdb layout, every group has a name */ + if (name == NULL) { + return EINVAL; + } + + if (gid == 0) { + if (posix == true) { + DEBUG(SSSDBG_CRIT_FAILURE, "POSIX group without GID\n"); + return EINVAL; + } + + /* Non-POSIX group with a name. Still can be used for access + * control as the name should point to the real name, no SID + */ + state->group_names[state->num_names] = talloc_strdup(state->group_names, + name); + if (!state->group_names[state->num_names]) { + return ENOMEM; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Adding group %s\n", name); + state->num_names++; + return EOK; + } + + /* Here are only groups with a name and gid. POSIX group can already + * be used, non-POSIX groups can be resolved */ + if (posix) { + state->group_names[state->num_names] = talloc_strdup(state->group_names, + name); + if (!state->group_names[state->num_names]) { + return ENOMEM; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Adding group %s\n", name); + state->num_names++; + return EOK; + } + + /* Try to get group SID and assign it a domain */ + group_sid = ldb_msg_find_attr_as_string(group, SYSDB_SID_STR, NULL); + if (group_sid == NULL) { + /* We will look it up in main domain. */ + domain = state->ctx->domain; + } else { + domain = find_domain_by_sid(state->ctx->domain, group_sid); + if (domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "There is no domain information for " + "SID %s\n", group_sid); + return ENOENT; + } + } + + /* It is a non-POSIX group with a GID. Needs resolving */ + state->lookup_groups[state->num_groups].domain = domain; + state->lookup_groups[state->num_groups].gid = gid; + DEBUG(SSSDBG_TRACE_INTERNAL, "Adding GID %"SPRIgid"\n", gid); + state->num_groups++; + return EOK; +} + +static errno_t +simple_check_get_groups_primary(struct simple_check_groups_state *state, + gid_t gid) +{ + errno_t ret; + const char *group_attrs[] = { SYSDB_NAME, SYSDB_POSIX, + SYSDB_GIDNUM, SYSDB_SID_STR, NULL }; + struct ldb_message *msg; + + ret = sysdb_search_group_by_gid(state, state->domain, gid, group_attrs, + &msg); + if (ret != EOK) { + DEBUG(SSSDBG_FUNC_DATA, + "Could not look up primary group [%"SPRIgid"]: [%d][%s]\n", + gid, ret, sss_strerror(ret)); + /* We have to treat this as non-fatal, because the primary + * group may be local to the machine and not available in + * our ID provider. + */ + } else { + ret = simple_check_process_group(state, msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot process primary group\n"); + return ret; + } + } + + return EOK; +} + +static errno_t +simple_check_get_groups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + const char ***_group_names) +{ + struct simple_check_groups_state *state; + + state = tevent_req_data(req, struct simple_check_groups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_group_names = talloc_steal(mem_ctx, state->group_names); + if (state->failed_to_resolve_groups) { + return ERR_SIMPLE_GROUPS_MISSING; + } + return EOK; +} + +struct simple_access_check_state { + bool access_granted; + struct simple_ctx *ctx; + const char *username; + + const char **group_names; +}; + +static void simple_access_check_done(struct tevent_req *subreq); + +struct tevent_req *simple_access_check_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct simple_ctx *ctx, + const char *username) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct simple_access_check_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct simple_access_check_state); + if (!req) return NULL; + + state->access_granted = false; + state->ctx = ctx; + state->username = talloc_strdup(state, username); + if (!state->username) { + ret = ENOMEM; + goto immediate; + } + + DEBUG(SSSDBG_FUNC_DATA, "Simple access check for %s\n", username); + + ret = simple_check_users(ctx, username, &state->access_granted); + if (ret == EOK) { + goto immediate; + } else if (ret != EAGAIN) { + ret = ERR_INTERNAL; + goto immediate; + } + + /* EAGAIN -- check groups */ + + if (!ctx->allow_groups && !ctx->deny_groups) { + /* There are no group restrictions, so just return + * here with whatever we've decided. + */ + DEBUG(SSSDBG_TRACE_LIBS, "No group restrictions, end request\n"); + ret = EOK; + goto immediate; + } + + /* The group names might not be available. Fire a request to + * gather them. In most cases, the request will just shortcut + */ + subreq = simple_check_get_groups_send(state, ev, ctx, username); + if (!subreq) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, simple_access_check_done, req); + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + + +static void simple_access_check_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct simple_access_check_state *state = + tevent_req_data(req, struct simple_access_check_state); + errno_t ret; + + /* We know the names now. Run the check. */ + ret = simple_check_get_groups_recv(subreq, state, &state->group_names); + + talloc_zfree(subreq); + if (ret == ENOENT) { + /* If the user wasn't found, just shortcut */ + state->access_granted = false; + tevent_req_done(req); + return; + } else if (ret == ERR_SIMPLE_GROUPS_MISSING) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not collect groups of user %s\n", state->username); + if (state->ctx->deny_groups == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "But no deny groups were defined so we can continue.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Some deny groups were defined, we can't continue\n"); + tevent_req_error(req, ret); + return; + } + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not collect groups of user %s\n", state->username); + tevent_req_error(req, ret); + return; + } + + ret = simple_check_groups(state->ctx, state->group_names, + &state->access_granted); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not check group access [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ERR_INTERNAL); + return; + } + + /* Now just return whatever we decided */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Group check done\n"); + tevent_req_done(req); +} + +errno_t simple_access_check_recv(struct tevent_req *req, bool *access_granted) +{ + struct simple_access_check_state *state = + tevent_req_data(req, struct simple_access_check_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + DEBUG(SSSDBG_TRACE_LIBS, + "Access %sgranted\n", state->access_granted ? "" : "not "); + if (access_granted) { + *access_granted = state->access_granted; + } + + return EOK; +} diff --git a/src/providers/simple/simple_access_pvt.h b/src/providers/simple/simple_access_pvt.h new file mode 100644 index 0000000..c133e1c --- /dev/null +++ b/src/providers/simple/simple_access_pvt.h @@ -0,0 +1,43 @@ +/* + SSSD + + Simple access control + + Copyright (C) Sumit Bose 2010 + + 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 3 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; 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, see . +*/ + +#ifndef __SIMPLE_ACCESS_PVT_H__ +#define __SIMPLE_ACCESS_PVT_H__ + +#include "providers/data_provider/dp.h" + +/* We only 'export' the functions in a private header file to be able to call + * them from unit tests + */ +struct tevent_req * +simple_access_handler_send(TALLOC_CTX *mem_ctx, + struct simple_ctx *simple_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +simple_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +int simple_access_obtain_filter_lists(struct simple_ctx *ctx); + +#endif /* __SIMPLE_ACCESS_PVT_H__ */ diff --git a/src/providers/sssd_be.exports b/src/providers/sssd_be.exports new file mode 100644 index 0000000..9afa106 --- /dev/null +++ b/src/providers/sssd_be.exports @@ -0,0 +1,4 @@ +{ + global: + *; +}; -- cgit v1.2.3