1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-08 21:18:16 +03:00
samba-mirror/python/samba/netcmd/computer.py
Noel Power 9410cea310 samba_tool: make exception handling statements py2/py3 compatible
Fix some missed conversions of
        except Exception, e:
to
        except Exception as e:

Signed-off-by: Noel Power <noel.power@suse.com>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2018-05-12 21:38:16 +02:00

587 lines
20 KiB
Python

# machine account (computer) management
#
# Copyright Bjoern Baumbch <bb@sernet.de> 2018
#
# based on user management
# Copyright Jelmer Vernooij 2010 <jelmer@samba.org>
# Copyright Theresa Halloran 2011 <theresahalloran@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import samba.getopt as options
import ldb
import socket
import samba
import re
from samba import sd_utils
from samba.dcerpc import dnsserver, dnsp, security
from samba.dnsserver import ARecord, AAAARecord
from samba.ndr import ndr_unpack, ndr_pack, ndr_print
from samba.remove_dc import remove_dns_references
from samba.auth import system_session
from samba.samdb import SamDB
from samba import (
credentials,
dsdb,
Ldb,
werror,
WERRORError
)
from samba.netcmd import (
Command,
CommandError,
SuperCommand,
Option,
)
def _is_valid_ip(ip_string, address_families=None):
"""Check ip string is valid address"""
# by default, check both ipv4 and ipv6
if not address_families:
address_families = [socket.AF_INET, socket.AF_INET6]
for address_family in address_families:
try:
socket.inet_pton(address_family, ip_string)
return True # if no error, return directly
except socket.error:
continue # Otherwise, check next family
return False
def _is_valid_ipv4(ip_string):
"""Check ip string is valid ipv4 address"""
return _is_valid_ip(ip_string, address_families=[socket.AF_INET])
def _is_valid_ipv6(ip_string):
"""Check ip string is valid ipv6 address"""
return _is_valid_ip(ip_string, address_families=[socket.AF_INET6])
def add_dns_records(
samdb, name, dns_conn, change_owner_sd,
server, ip_address_list, logger):
"""Add DNS A or AAAA records while creating computer. """
name = name.rstrip('$')
client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA | dnsserver.DNS_RPC_VIEW_NO_CHILDREN
zone = samdb.domain_dns_name()
name_found = True
sd_helper = sd_utils.SDUtils(samdb)
try:
buflen, res = dns_conn.DnssrvEnumRecords2(
client_version,
0,
server,
zone,
name,
None,
dnsp.DNS_TYPE_ALL,
select_flags,
None,
None,
)
except WERRORError as e:
if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
name_found = False
pass
if name_found:
for rec in res.rec:
for record in rec.records:
if record.wType == dnsp.DNS_TYPE_A or record.wType == dnsp.DNS_TYPE_AAAA:
# delete record
del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
del_rec_buf.rec = record
try:
dns_conn.DnssrvUpdateRecord2(
client_version,
0,
server,
zone,
name,
None,
del_rec_buf,
)
except WERRORError as e:
if e.args[0] != werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
raise
for ip_address in ip_address_list:
if _is_valid_ipv6(ip_address):
logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s" % (
name, zone, ip_address))
rec = AAAARecord(ip_address)
elif _is_valid_ipv4(ip_address):
logger.info("Adding DNS A record %s.%s for IPv4 IP: %s" % (
name, zone, ip_address))
rec = ARecord(ip_address)
else:
raise ValueError('Invalid IP: {}'.format(ip_address))
# Add record
add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
add_rec_buf.rec = rec
dns_conn.DnssrvUpdateRecord2(
client_version,
0,
server,
zone,
name,
add_rec_buf,
None,
)
if (len(ip_address_list) > 0):
domaindns_zone_dn = ldb.Dn(
samdb,
'DC=DomainDnsZones,%s' % samdb.get_default_basedn(),
)
dns_a_dn, ldap_record = samdb.dns_lookup(
"%s.%s" % (name, zone),
dns_partition=domaindns_zone_dn,
)
# Make the DC own the DNS record, not the administrator
sd_helper.modify_sd_on_dn(
dns_a_dn,
change_owner_sd,
controls=["sd_flags:1:%d" % (security.SECINFO_OWNER | security.SECINFO_GROUP)],
)
class cmd_computer_create(Command):
"""Create a new computer.
This command creates a new computer account in the Active Directory domain.
The computername specified on the command is the sAMaccountName without the
trailing $ (dollar sign).
User accounts may represent physical entities, such as workstations. Computer
accounts are also referred to as security principals and are assigned a
security identifier (SID).
Example1:
samba-tool computer create Computer1 -H ldap://samba.samdom.example.com \
-Uadministrator%passw1rd
Example1 shows how to create a new computer in the domain against a remote LDAP
server. The -H parameter is used to specify the remote target server. The -U
option is used to pass the userid and password authorized to issue the command
remotely.
Example2:
sudo samba-tool computer create Computer2
Example2 shows how to create a new computer in the domain against the local
server. sudo is used so a user may run the command as root.
Example3:
samba-tool computer create Computer3 --computerou='OU=OrgUnit'
Example3 shows how to create a new computer in the OrgUnit organizational unit.
"""
synopsis = "%prog <computername> [options]"
takes_options = [
Option("-H", "--URL", help="LDB URL for database or target server",
type=str, metavar="URL", dest="H"),
Option("--computerou",
help=("DN of alternative location (with or without domainDN "
"counterpart) to default CN=Users in which new computer "
"object will be created. E. g. 'OU=<OU name>'"),
type=str),
Option("--description", help="Computers's description", type=str),
Option("--prepare-oldjoin",
help="Prepare enabled machine account for oldjoin mechanism",
action="store_true"),
Option("--ip-address",
dest='ip_address_list',
help=("IPv4 address for the computer's A record, or IPv6 "
"address for AAAA record, can be provided multiple "
"times"),
action='append'),
Option("--service-principal-name",
dest='service_principal_name_list',
help=("Computer's Service Principal Name, can be provided "
"multiple times"),
action='append')
]
takes_args = ["computername"]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self, computername, credopts=None, sambaopts=None, versionopts=None,
H=None, computerou=None, description=None, prepare_oldjoin=False,
ip_address_list=None, service_principal_name_list=None):
if ip_address_list is None:
ip_address_list = []
if service_principal_name_list is None:
service_principal_name_list = []
# check each IP address if provided
for ip_address in ip_address_list:
if not _is_valid_ip(ip_address):
raise CommandError('Invalid IP address {}'.format(ip_address))
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp)
try:
samdb = SamDB(url=H, session_info=system_session(),
credentials=creds, lp=lp)
samdb.newcomputer(computername, computerou=computerou,
description=description,
prepare_oldjoin=prepare_oldjoin,
ip_address_list=ip_address_list,
service_principal_name_list=service_principal_name_list,
)
if ip_address_list:
# if ip_address_list provided, then we need to create DNS
# records for this computer.
hostname = re.sub(r"\$$", "", computername)
if hostname.count('$'):
raise CommandError('Illegal computername "%s"' % computername)
filters = '(&(sAMAccountName={}$)(objectclass=computer))'.format(
ldb.binary_encode(hostname))
recs = samdb.search(
base=samdb.domain_dn(),
scope=ldb.SCOPE_SUBTREE,
expression=filters,
attrs=['primaryGroupID', 'objectSid'])
group = recs[0]['primaryGroupID'][0]
owner = ndr_unpack(security.dom_sid, recs[0]["objectSid"][0])
dns_conn = dnsserver.dnsserver(
"ncacn_ip_tcp:{}[sign]".format(samdb.host_dns_name()),
lp, creds)
change_owner_sd = security.descriptor()
change_owner_sd.owner_sid = owner
change_owner_sd.group_sid = security.dom_sid(
"{}-{}".format(samdb.get_domain_sid(), group),
)
add_dns_records(
samdb, hostname, dns_conn,
change_owner_sd, samdb.host_dns_name(),
ip_address_list, self.get_logger())
except Exception as e:
raise CommandError("Failed to create computer '%s': " %
computername, e)
self.outf.write("Computer '%s' created successfully\n" % computername)
class cmd_computer_delete(Command):
"""Delete a computer.
This command deletes a computer account from the Active Directory domain. The
computername specified on the command is the sAMAccountName without the
trailing $ (dollar sign).
Once the account is deleted, all permissions and memberships associated with
that account are deleted. If a new computer account is added with the same name
as a previously deleted account name, the new computer does not have the
previous permissions. The new account computer will be assigned a new security
identifier (SID) and permissions and memberships will have to be added.
The command may be run from the root userid or another authorized
userid. The -H or --URL= option can be used to execute the command against
a remote server.
Example1:
samba-tool computer delete Computer1 -H ldap://samba.samdom.example.com \
-Uadministrator%passw1rd
Example1 shows how to delete a computer in the domain against a remote LDAP
server. The -H parameter is used to specify the remote target server. The
--computername= and --password= options are used to pass the computername and
password of a computer that exists on the remote server and is authorized to
issue the command on that server.
Example2:
sudo samba-tool computer delete Computer2
Example2 shows how to delete a computer in the domain against the local server.
sudo is used so a computer may run the command as root.
"""
synopsis = "%prog <computername> [options]"
takes_options = [
Option("-H", "--URL", help="LDB URL for database or target server",
type=str, metavar="URL", dest="H"),
]
takes_args = ["computername"]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self, computername, credopts=None, sambaopts=None,
versionopts=None, H=None):
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp, fallback_machine=True)
samdb = SamDB(url=H, session_info=system_session(),
credentials=creds, lp=lp)
samaccountname = computername
if not computername.endswith('$'):
samaccountname = "%s$" % computername
filter = ("(&(sAMAccountName=%s)(sAMAccountType=%u))" %
(ldb.binary_encode(samaccountname),
dsdb.ATYPE_WORKSTATION_TRUST))
try:
res = samdb.search(base=samdb.domain_dn(),
scope=ldb.SCOPE_SUBTREE,
expression=filter,
attrs=["userAccountControl", "dNSHostName"])
computer_dn = res[0].dn
computer_ac = int(res[0]["userAccountControl"][0])
if "dNSHostName" in res[0]:
computer_dns_host_name = res[0]["dNSHostName"][0]
else:
computer_dns_host_name = None
except IndexError:
raise CommandError('Unable to find computer "%s"' % computername)
computer_is_workstation = (
computer_ac & dsdb.UF_WORKSTATION_TRUST_ACCOUNT)
if computer_is_workstation == False:
raise CommandError('Failed to remove computer "%s": '
'Computer is not a workstation - removal denied'
% computername)
try:
samdb.delete(computer_dn)
if computer_dns_host_name:
remove_dns_references(
samdb, self.get_logger(), computer_dns_host_name,
ignore_no_name=True)
except Exception as e:
raise CommandError('Failed to remove computer "%s"' %
samaccountname, e)
self.outf.write("Deleted computer %s\n" % computername)
class cmd_computer_list(Command):
"""List all computers."""
synopsis = "%prog [options]"
takes_options = [
Option("-H", "--URL", help="LDB URL for database or target server",
type=str, metavar="URL", dest="H"),
]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self, sambaopts=None, credopts=None, versionopts=None, H=None):
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp, fallback_machine=True)
samdb = SamDB(url=H, session_info=system_session(),
credentials=creds, lp=lp)
filter = "(sAMAccountType=%u)" % (dsdb.ATYPE_WORKSTATION_TRUST)
domain_dn = samdb.domain_dn()
res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
expression=filter,
attrs=["samaccountname"])
if (len(res) == 0):
return
for msg in res:
self.outf.write("%s\n" % msg.get("samaccountname", idx=0))
class cmd_computer_show(Command):
"""Display a computer AD object.
This command displays a computer account and it's attributes in the Active
Directory domain.
The computername specified on the command is the sAMAccountName.
The command may be run from the root userid or another authorized
userid.
The -H or --URL= option can be used to execute the command against a remote
server.
Example1:
samba-tool computer show Computer1 -H ldap://samba.samdom.example.com \
-U administrator
Example1 shows how display a computers attributes in the domain against a
remote LDAP server.
The -H parameter is used to specify the remote target server.
Example2:
samba-tool computer show Computer2
Example2 shows how to display a computers attributes in the domain against a
local LDAP server.
Example3:
samba-tool computer show Computer2 --attributes=objectSid,operatingSystem
Example3 shows how to display a computers objectSid and operatingSystem
attribute.
"""
synopsis = "%prog <computername> [options]"
takes_options = [
Option("-H", "--URL", help="LDB URL for database or target server",
type=str, metavar="URL", dest="H"),
Option("--attributes",
help=("Comma separated list of attributes, "
"which will be printed."),
type=str, dest="computer_attrs"),
]
takes_args = ["computername"]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self, computername, credopts=None, sambaopts=None, versionopts=None,
H=None, computer_attrs=None):
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp, fallback_machine=True)
samdb = SamDB(url=H, session_info=system_session(),
credentials=creds, lp=lp)
attrs = None
if computer_attrs:
attrs = computer_attrs.split(",")
samaccountname = computername
if not computername.endswith('$'):
samaccountname = "%s$" % computername
filter = ("(&(sAMAccountType=%d)(sAMAccountName=%s))" %
(dsdb.ATYPE_WORKSTATION_TRUST,
ldb.binary_encode(samaccountname)))
domaindn = samdb.domain_dn()
try:
res = samdb.search(base=domaindn, expression=filter,
scope=ldb.SCOPE_SUBTREE, attrs=attrs)
computer_dn = res[0].dn
except IndexError:
raise CommandError('Unable to find computer "%s"' %
samaccountname)
for msg in res:
computer_ldif = samdb.write_ldif(msg, ldb.CHANGETYPE_NONE)
self.outf.write(computer_ldif)
class cmd_computer_move(Command):
"""Move a computer to an organizational unit/container."""
synopsis = "%prog computername <new_ou_dn> [options]"
takes_options = [
Option("-H", "--URL", help="LDB URL for database or target server",
type=str, metavar="URL", dest="H"),
]
takes_args = [ "computername", "new_ou_dn" ]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self, computername, new_ou_dn, credopts=None, sambaopts=None,
versionopts=None, H=None):
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp, fallback_machine=True)
samdb = SamDB(url=H, session_info=system_session(),
credentials=creds, lp=lp)
domain_dn = ldb.Dn(samdb, samdb.domain_dn())
samaccountname = computername
if not computername.endswith('$'):
samaccountname = "%s$" % computername
filter = ("(&(sAMAccountName=%s)(sAMAccountType=%u))" %
(ldb.binary_encode(samaccountname),
dsdb.ATYPE_WORKSTATION_TRUST))
try:
res = samdb.search(base=domain_dn,
expression=filter,
scope=ldb.SCOPE_SUBTREE)
computer_dn = res[0].dn
except IndexError:
raise CommandError('Unable to find computer "%s"' % (computername))
full_new_ou_dn = ldb.Dn(samdb, new_ou_dn)
if not full_new_ou_dn.is_child_of(domain_dn):
full_new_ou_dn.add_base(domain_dn)
new_computer_dn = ldb.Dn(samdb, str(computer_dn))
new_computer_dn.remove_base_components(len(computer_dn)-1)
new_computer_dn.add_base(full_new_ou_dn)
try:
samdb.rename(computer_dn, new_computer_dn)
except Exception as e:
raise CommandError('Failed to move computer "%s"' % computername, e)
self.outf.write('Moved computer "%s" to "%s"\n' %
(computername, new_ou_dn))
class cmd_computer(SuperCommand):
"""Computer management."""
subcommands = {}
subcommands["create"] = cmd_computer_create()
subcommands["delete"] = cmd_computer_delete()
subcommands["list"] = cmd_computer_list()
subcommands["show"] = cmd_computer_show()
subcommands["move"] = cmd_computer_move()