1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-28 07:21:54 +03:00
samba-mirror/python/samba/netcmd/contact.py
David Mulder a3cd315321 python2 reduction: Merge remaining compat code into common
The remaining compat code (get_string, get_bytes,
cmp) are useful helper routines which we should
simply merge into common (especially since there
is some duplication here).

Signed-off-by: David Mulder <dmulder@suse.com>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>

Autobuild-User(master): David Mulder <dmulder@samba.org>
Autobuild-Date(master): Fri Oct  2 14:49:36 UTC 2020 on sn-devel-184
2020-10-02 14:49:36 +00:00

862 lines
30 KiB
Python

# samba-tool contact management
#
# Copyright Bjoern Baumbach 2019 <bbaumbach@samba.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General 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 os
import tempfile
from subprocess import check_call, CalledProcessError
from operator import attrgetter
from samba.auth import system_session
from samba.samdb import SamDB
from samba import (
credentials,
dsdb,
)
from samba.net import Net
from samba.netcmd import (
Command,
CommandError,
SuperCommand,
Option,
)
from samba.common import get_bytes
from . import common
class cmd_add(Command):
"""Add a new contact.
This command adds a new contact to the Active Directory domain.
The name of the new contact can be specified by the first argument
'contactname' or the --given-name, --initial and --surname arguments.
If no 'contactname' is given, contact's name will be made up of the given
arguments by combining the given-name, initials and surname. Each argument
is optional. A dot ('.') will be appended to the initials automatically.
Example1:
samba-tool contact add "James T. Kirk" --job-title=Captain \\
-H ldap://samba.samdom.example.com -UAdministrator%Passw1rd
The example shows how to add a new contact to the domain against a remote
LDAP server.
Example2:
samba-tool contact add --given-name=James --initials=T --surname=Kirk
The example shows how to add a new contact to the domain against a local
server. The resulting name is "James T. Kirk".
"""
synopsis = "%prog [contactname] [options]"
takes_options = [
Option("-H", "--URL", help="LDB URL for database or target server",
type=str, metavar="URL", dest="H"),
Option("--ou",
help=("DN of alternative location (with or without domainDN "
"counterpart) in which the new contact will be created. "
"E.g. 'OU=<OU name>'. "
"Default is the domain base."),
type=str),
Option("--surname", help="Contact's surname", type=str),
Option("--given-name", help="Contact's given name", type=str),
Option("--initials", help="Contact's initials", type=str),
Option("--display-name", help="Contact's display name", type=str),
Option("--job-title", help="Contact's job title", type=str),
Option("--department", help="Contact's department", type=str),
Option("--company", help="Contact's company", type=str),
Option("--description", help="Contact's description", type=str),
Option("--mail-address", help="Contact's email address", type=str),
Option("--internet-address", help="Contact's home page", type=str),
Option("--telephone-number", help="Contact's phone number", type=str),
Option("--mobile-number",
help="Contact's mobile phone number",
type=str),
Option("--physical-delivery-office",
help="Contact's office location",
type=str),
]
takes_args = ["fullcontactname?"]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self,
fullcontactname=None,
sambaopts=None,
credopts=None,
versionopts=None,
H=None,
ou=None,
surname=None,
given_name=None,
initials=None,
display_name=None,
job_title=None,
department=None,
company=None,
description=None,
mail_address=None,
internet_address=None,
telephone_number=None,
mobile_number=None,
physical_delivery_office=None):
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp)
try:
samdb = SamDB(url=H,
session_info=system_session(),
credentials=creds,
lp=lp)
ret_name = samdb.newcontact(
fullcontactname=fullcontactname,
ou=ou,
surname=surname,
givenname=given_name,
initials=initials,
displayname=display_name,
jobtitle=job_title,
department=department,
company=company,
description=description,
mailaddress=mail_address,
internetaddress=internet_address,
telephonenumber=telephone_number,
mobilenumber=mobile_number,
physicaldeliveryoffice=physical_delivery_office)
except Exception as e:
raise CommandError("Failed to add contact", e)
self.outf.write("Contact '%s' added successfully\n" % ret_name)
class cmd_delete(Command):
"""Delete a contact.
This command deletes a contact object from the Active Directory domain.
The contactname specified on the command is the common name or the
distinguished name of the contact object. The distinguished name of the
contact can be specified with or without the domainDN component.
Example:
samba-tool contact delete Contact1 \\
-H ldap://samba.samdom.example.com \\
--username=Administrator --password=Passw1rd
The example shows how to delete a contact in the domain against a remote
LDAP server.
"""
synopsis = "%prog <contactname> [options]"
takes_options = [
Option("-H",
"--URL",
help="LDB URL for database or target server",
type=str,
metavar="URL",
dest="H"),
]
takes_args = ["contactname"]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self,
contactname,
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)
base_dn = samdb.domain_dn()
scope = ldb.SCOPE_SUBTREE
filter = ("(&(objectClass=contact)(name=%s))" %
ldb.binary_encode(contactname))
if contactname.upper().startswith("CN="):
# contact is specified by DN
filter = "(objectClass=contact)"
scope = ldb.SCOPE_BASE
try:
base_dn = samdb.normalize_dn_in_domain(contactname)
except Exception as e:
raise CommandError('Invalid dn "%s": %s' %
(contactname, e))
try:
res = samdb.search(base=base_dn,
scope=scope,
expression=filter,
attrs=["dn"])
contact_dn = res[0].dn
except IndexError:
raise CommandError('Unable to find contact "%s"' % (contactname))
if len(res) > 1:
for msg in sorted(res, key=attrgetter('dn')):
self.outf.write("found: %s\n" % msg.dn)
raise CommandError("Multiple results for contact '%s'\n"
"Please specify the contact's full DN" %
contactname)
try:
samdb.delete(contact_dn)
except Exception as e:
raise CommandError('Failed to remove contact "%s"' % contactname, e)
self.outf.write("Deleted contact %s\n" % contactname)
class cmd_list(Command):
"""List all contacts.
"""
synopsis = "%prog [options]"
takes_options = [
Option("-H",
"--URL",
help="LDB URL for database or target server",
type=str,
metavar="URL",
dest="H"),
Option("-b", "--base-dn",
help="Specify base DN to use.",
type=str),
Option("--full-dn",
dest="full_dn",
default=False,
action='store_true',
help="Display contact's full DN instead of the name."),
]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self,
sambaopts=None,
credopts=None,
versionopts=None,
H=None,
base_dn=None,
full_dn=False):
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp, fallback_machine=True)
samdb = SamDB(url=H,
session_info=system_session(),
credentials=creds,
lp=lp)
search_dn = samdb.domain_dn()
if base_dn:
search_dn = samdb.normalize_dn_in_domain(base_dn)
res = samdb.search(search_dn,
scope=ldb.SCOPE_SUBTREE,
expression="(objectClass=contact)",
attrs=["name"])
if (len(res) == 0):
return
if full_dn:
for msg in sorted(res, key=attrgetter('dn')):
self.outf.write("%s\n" % msg.dn)
return
for msg in res:
contact_name = msg.get("name", idx=0)
self.outf.write("%s\n" % contact_name)
class cmd_edit(Command):
"""Modify a contact.
This command will allow editing of a contact object in the Active Directory
domain. You will then be able to add or change attributes and their values.
The contactname specified on the command is the common name or the
distinguished name of the contact object. The distinguished name of the
contact can be specified with or without the domainDN component.
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 contact edit Contact1 -H ldap://samba.samdom.example.com \\
-U Administrator --password=Passw1rd
Example1 shows how to edit a contact's attributes in the domain against a
remote LDAP server.
The -H parameter is used to specify the remote target server.
Example2:
samba-tool contact edit CN=Contact2,OU=people,DC=samdom,DC=example,DC=com
Example2 shows how to edit a contact's attributes in the domain against a
local server. The contact, which is located in the 'people' OU,
is specified by the full distinguished name.
Example3:
samba-tool contact edit Contact3 --editor=nano
Example3 shows how to edit a contact's attributes in the domain against a
local server using the 'nano' editor.
"""
synopsis = "%prog <contactname> [options]"
takes_options = [
Option("-H",
"--URL",
help="LDB URL for database or target server",
type=str,
metavar="URL",
dest="H"),
Option("--editor",
help="Editor to use instead of the system default, "
"or 'vi' if no system default is set.",
type=str),
]
takes_args = ["contactname"]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self,
contactname,
sambaopts=None,
credopts=None,
versionopts=None,
H=None,
editor=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)
base_dn = samdb.domain_dn()
scope = ldb.SCOPE_SUBTREE
filter = ("(&(objectClass=contact)(name=%s))" %
ldb.binary_encode(contactname))
if contactname.upper().startswith("CN="):
# contact is specified by DN
filter = "(objectClass=contact)"
scope = ldb.SCOPE_BASE
try:
base_dn = samdb.normalize_dn_in_domain(contactname)
except Exception as e:
raise CommandError('Invalid dn "%s": %s' %
(contactname, e))
try:
res = samdb.search(base=base_dn,
scope=scope,
expression=filter)
contact_dn = res[0].dn
except IndexError:
raise CommandError('Unable to find contact "%s"' % (contactname))
if len(res) > 1:
for msg in sorted(res, key=attrgetter('dn')):
self.outf.write("found: %s\n" % msg.dn)
raise CommandError("Multiple results for contact '%s'\n"
"Please specify the contact's full DN" %
contactname)
for msg in res:
result_ldif = common.get_ldif_for_editor(samdb, msg)
if editor is None:
editor = os.environ.get('EDITOR')
if editor is None:
editor = 'vi'
with tempfile.NamedTemporaryFile(suffix=".tmp") as t_file:
t_file.write(get_bytes(result_ldif))
t_file.flush()
try:
check_call([editor, t_file.name])
except CalledProcessError as e:
raise CalledProcessError("ERROR: ", e)
with open(t_file.name) as edited_file:
edited_message = edited_file.read()
msgs_edited = samdb.parse_ldif(edited_message)
msg_edited = next(msgs_edited)[1]
res_msg_diff = samdb.msg_diff(msg, msg_edited)
if len(res_msg_diff) == 0:
self.outf.write("Nothing to do\n")
return
try:
samdb.modify(res_msg_diff)
except Exception as e:
raise CommandError("Failed to modify contact '%s': " % contactname,
e)
self.outf.write("Modified contact '%s' successfully\n" % contactname)
class cmd_show(Command):
"""Display a contact.
This command displays a contact object with it's attributes in the Active
Directory domain.
The contactname specified on the command is the common name or the
distinguished name of the contact object. The distinguished name of the
contact can be specified with or without the domainDN component.
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 contact show Contact1 -H ldap://samba.samdom.example.com \\
-U Administrator --password=Passw1rd
Example1 shows how to display a contact's attributes in the domain against
a remote LDAP server.
The -H parameter is used to specify the remote target server.
Example2:
samba-tool contact show CN=Contact2,OU=people,DC=samdom,DC=example,DC=com
Example2 shows how to display a contact's attributes in the domain against
a local server. The contact, which is located in the 'people' OU, is
specified by the full distinguished name.
Example3:
samba-tool contact show Contact3 --attributes=mail,mobile
Example3 shows how to display a contact's mail and mobile attributes.
"""
synopsis = "%prog <contactname> [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="contact_attrs"),
]
takes_args = ["contactname"]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self,
contactname,
sambaopts=None,
credopts=None,
versionopts=None,
H=None,
contact_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)
base_dn = samdb.domain_dn()
scope = ldb.SCOPE_SUBTREE
attrs = None
if contact_attrs:
attrs = contact_attrs.split(",")
filter = ("(&(objectClass=contact)(name=%s))" %
ldb.binary_encode(contactname))
if contactname.upper().startswith("CN="):
# contact is specified by DN
filter = "(objectClass=contact)"
scope = ldb.SCOPE_BASE
try:
base_dn = samdb.normalize_dn_in_domain(contactname)
except Exception as e:
raise CommandError('Invalid dn "%s": %s' %
(contactname, e))
try:
res = samdb.search(base=base_dn,
expression=filter,
scope=scope,
attrs=attrs)
contact_dn = res[0].dn
except IndexError:
raise CommandError('Unable to find contact "%s"' % (contactname))
if len(res) > 1:
for msg in sorted(res, key=attrgetter('dn')):
self.outf.write("found: %s\n" % msg.dn)
raise CommandError("Multiple results for contact '%s'\n"
"Please specify the contact's DN" %
contactname)
for msg in res:
contact_ldif = common.get_ldif_for_editor(samdb, msg)
self.outf.write(contact_ldif)
class cmd_move(Command):
"""Move a contact object to an organizational unit or container.
The contactname specified on the command is the common name or the
distinguished name of the contact object. The distinguished name of the
contact can be specified with or without the domainDN component.
The name of the organizational unit or container can be specified as the
distinguished name, with or without the domainDN component.
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 contact move Contact1 'OU=people' \\
-H ldap://samba.samdom.example.com -U Administrator
Example1 shows how to move a contact Contact1 into the 'people'
organizational unit on a remote LDAP server.
The -H parameter is used to specify the remote target server.
Example2:
samba-tool contact move Contact1 OU=Contacts,DC=samdom,DC=example,DC=com
Example2 shows how to move a contact Contact1 into the OU=Contacts
organizational unit on the local server.
"""
synopsis = "%prog <contactname> <new_parent_dn> [options]"
takes_options = [
Option("-H",
"--URL",
help="LDB URL for database or target server",
type=str,
metavar="URL",
dest="H"),
]
takes_args = ["contactname", "new_parent_dn"]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self,
contactname,
new_parent_dn,
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)
base_dn = samdb.domain_dn()
scope = ldb.SCOPE_SUBTREE
filter = ("(&(objectClass=contact)(name=%s))" %
ldb.binary_encode(contactname))
if contactname.upper().startswith("CN="):
# contact is specified by DN
filter = "(objectClass=contact)"
scope = ldb.SCOPE_BASE
try:
base_dn = samdb.normalize_dn_in_domain(contactname)
except Exception as e:
raise CommandError('Invalid dn "%s": %s' %
(contactname, e))
try:
res = samdb.search(base=base_dn,
scope=scope,
expression=filter,
attrs=["dn"])
contact_dn = res[0].dn
except IndexError:
raise CommandError('Unable to find contact "%s"' % (contactname))
if len(res) > 1:
for msg in sorted(res, key=attrgetter('dn')):
self.outf.write("found: %s\n" % msg.dn)
raise CommandError("Multiple results for contact '%s'\n"
"Please specify the contact's full DN" %
contactname)
try:
full_new_parent_dn = samdb.normalize_dn_in_domain(new_parent_dn)
except Exception as e:
raise CommandError('Invalid new_parent_dn "%s": %s' %
(new_parent_dn, e))
full_new_contact_dn = ldb.Dn(samdb, str(contact_dn))
full_new_contact_dn.remove_base_components(len(contact_dn) - 1)
full_new_contact_dn.add_base(full_new_parent_dn)
try:
samdb.rename(contact_dn, full_new_contact_dn)
except Exception as e:
raise CommandError('Failed to move contact "%s"' % contactname, e)
self.outf.write('Moved contact "%s" into "%s"\n' %
(contactname, full_new_parent_dn))
class cmd_rename(Command):
"""Rename a contact and related attributes.
This command allows to set the contact's name related attributes.
The contact's new CN will be made up by combining the given-name, initials
and surname. A dot ('.') will be appended to the initials automatically, if
required.
Use the --force-new-cn option to specify the new CN manually and the
--reset-cn option to reset this changes.
Use an empty attribute value to remove the specified attribute.
The contactname specified on the command is the CN.
The command may be run locally 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 contact rename "John Doe" --surname=Bloggs \\
--force-new-cn=John
Example1 shows how to change the surname ('sn' attribute) of a contact
'John Doe' to 'Bloggs' and change the CN to 'John' on the local server.
Example2:
samba-tool contact rename "J Doe" --given-name=John
-H ldap://samba.samdom.example.com -U administrator
Example2 shows how to rename the given name of a contact 'J Doe' to
'John'. The contact's cn will be renamed automatically, based on
the given name, initials and surname, if the previous CN is the
standard combination of the previous name attributes.
The -H parameter is used to specify the remote target server.
"""
synopsis = "%prog <contactname> [options]"
takes_options = [
Option("-H", "--URL",
help="LDB URL for database or target server",
type=str, metavar="URL", dest="H"),
Option("--surname",
help="New surname",
type=str),
Option("--given-name",
help="New given name",
type=str),
Option("--initials",
help="New initials",
type=str),
Option("--force-new-cn",
help="Specify a new CN (RDN) instead of using a combination "
"of the given name, initials and surname.",
type=str, metavar="NEW_CN"),
Option("--reset-cn",
help="Set the CN (RDN) to the combination of the given name, "
"initials and surname. Use this option to reset "
"the changes made with the --force-new-cn option.",
action="store_true"),
Option("--display-name",
help="New display name",
type=str),
Option("--mail-address",
help="New email address",
type=str),
]
takes_args = ["contactname"]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"credopts": options.CredentialsOptions,
"versionopts": options.VersionOptions,
}
def run(self, contactname, credopts=None, sambaopts=None, versionopts=None,
H=None, surname=None, given_name=None, initials=None, force_new_cn=None,
display_name=None, mail_address=None, reset_cn=None):
# illegal options
if force_new_cn and reset_cn:
raise CommandError("It is not allowed to specify --force-new-cn "
"together with --reset-cn.")
if force_new_cn == "":
raise CommandError("Failed to rename contact - delete protected "
"attribute 'CN'")
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())
filter = ("(&(objectClass=contact)(name=%s))" %
ldb.binary_encode(contactname))
try:
res = samdb.search(base=domain_dn,
scope=ldb.SCOPE_SUBTREE,
expression=filter,
attrs=["name",
"sn",
"givenName",
"cn",
"initials",
"displayName",
"mail"]
)
old_contact = res[0]
contact_dn = old_contact.dn
except IndexError:
raise CommandError('Unable to find contact "%s"' % (contactname))
contact_parent_dn = contact_dn.parent()
old_cn = old_contact["cn"][0]
if force_new_cn is not None:
new_cn = force_new_cn
else:
new_cn = samdb.fullname_from_names(old_attrs=old_contact,
given_name=given_name,
initials=initials,
surname=surname)
# change CN, if the new CN is different and the old CN is the
# standard CN or the change is forced with force-new-cn or reset-cn
excepted_cn = samdb.fullname_from_names(old_attrs=old_contact)
must_change_cn = str(old_cn) != str(new_cn) and \
(str(old_cn) == str(excepted_cn) or \
reset_cn or bool(force_new_cn))
new_contact_dn = ldb.Dn(samdb, "CN=%s" % new_cn)
new_contact_dn.add_base(contact_parent_dn)
if new_cn == "" and must_change_cn:
raise CommandError("Failed to rename contact '%s' - "
"can not set an empty CN "
"(please use --force-new-cn to specify a "
"different CN or --given-name, --initials or "
"--surname to set name attributes)" % old_cn)
# format given attributes
contact_attrs = ldb.Message()
contact_attrs.dn = contact_dn
samdb.prepare_attr_replace(contact_attrs, old_contact, "givenName", given_name)
samdb.prepare_attr_replace(contact_attrs, old_contact, "sn", surname)
samdb.prepare_attr_replace(contact_attrs, old_contact, "initials", initials)
samdb.prepare_attr_replace(contact_attrs, old_contact, "displayName", display_name)
samdb.prepare_attr_replace(contact_attrs, old_contact, "mail", mail_address)
contact_attributes_changed = len(contact_attrs) > 0
# update the contact with formatted attributes
samdb.transaction_start()
try:
if contact_attributes_changed == True:
samdb.modify(contact_attrs)
if must_change_cn:
samdb.rename(contact_dn, new_contact_dn)
except Exception as e:
samdb.transaction_cancel()
raise CommandError('Failed to rename contact "%s"' % contactname, e)
samdb.transaction_commit()
if must_change_cn:
self.outf.write('Renamed CN of contact "%s" from "%s" to "%s" '
'successfully\n' % (contactname, old_cn, new_cn))
if contact_attributes_changed:
self.outf.write('Following attributes of contact "%s" have been '
'changed successfully:\n' % (contactname))
for attr in contact_attrs.keys():
if attr == "dn":
continue
self.outf.write('%s: %s\n' % (attr, contact_attrs[attr]
if contact_attrs[attr] else '[removed]'))
class cmd_contact(SuperCommand):
"""Contact management."""
subcommands = {}
subcommands["add"] = cmd_add()
subcommands["create"] = cmd_add()
subcommands["delete"] = cmd_delete()
subcommands["edit"] = cmd_edit()
subcommands["list"] = cmd_list()
subcommands["move"] = cmd_move()
subcommands["show"] = cmd_show()
subcommands["rename"] = cmd_rename()