summaryrefslogtreecommitdiffstats
path: root/python/samba/netcmd/common.py
blob: 2aa50c754aa4b3c075f54e1244509e18ff1dfacb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# common functions for samba-tool python commands
#
# Copyright Andrew Tridgell 2010
# Copyright Giampaolo Lauria 2011 <lauria2@yahoo.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 re
from samba.dcerpc import nbt
from samba.net import Net
from samba.netcmd import CommandError
import ldb


# In MS AD, setting a timeout to '(never)' corresponds to this value
NEVER_TIMESTAMP = int(-0x8000000000000000)


def _get_user_realm_domain(user, sam=None):
    r""" get the realm or the domain and the base user
        from user like:
        * username
        * DOMAIN\username
        * username@REALM

         A SamDB object can also be passed in to check
        our domain or realm against the obtained ones.
    """
    baseuser = user
    m = re.match(r"(\w+)\\(\w+$)", user)
    if m:
        domain = m.group(1)
        baseuser = m.group(2)

        if sam is not None:
            our_domain = sam.domain_netbios_name()
            if domain.lower() != our_domain.lower():
                raise CommandError(f"Given domain '{domain}' does not match "
                                   f"our domain '{our_domain}'!")

        return (baseuser.lower(), "", domain.upper())

    realm = ""
    m = re.match(r"(\w+)@(\w+)", user)
    if m:
        baseuser = m.group(1)
        realm = m.group(2)

        if sam is not None:
            our_realm = sam.domain_dns_name()
            our_realm_initial = our_realm.split('.', 1)[0]
            if realm.lower() != our_realm_initial.lower():
                raise CommandError(f"Given realm '{realm}' does not match our "
                                   f"realm '{our_realm}'!")

    return (baseuser.lower(), realm.upper(), "")


def netcmd_dnsname(lp):
    """return the full DNS name of our own host. Used as a default
       for hostname when running status queries"""
    return lp.get('netbios name').lower() + "." + lp.get('realm').lower()


def netcmd_finddc(lp, creds, realm=None):
    """Return domain-name of a writable/ldap-capable DC for the default
       domain (parameter "realm" in smb.conf) unless another realm has been
       specified as argument"""
    net = Net(creds=creds, lp=lp)
    if realm is None:
        realm = lp.get('realm')
    cldap_ret = net.finddc(domain=realm,
                           flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
    return cldap_ret.pdc_dns_name


def netcmd_get_domain_infos_via_cldap(lp, creds, address=None):
    """Return domain information (CLDAP record) of the ldap-capable
       DC with the specified address"""
    net = Net(creds=creds, lp=lp)
    cldap_ret = net.finddc(address=address,
                           flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
    return cldap_ret

def is_printable_attr_val(val):
    import unicodedata

    # The value must be convertible to a string value.
    try:
        str_val = str(val)
    except:
        return False

    # Characters of the Unicode Character Category "C" ("Other") are
    # supposed to be not printable. The category "C" includes control
    # characters, format specifier and others.
    for c in str_val:
        if unicodedata.category(c)[0] == 'C':
            return False

    return True

def get_ldif_for_editor(samdb, msg):

    # Copy the given message, because we do not
    # want to modify the original message.
    m = ldb.Message()
    m.dn = msg.dn

    for k in msg.keys():
        if k == "dn":
            continue
        vals = msg[k]
        m[k] = vals
        need_base64 = False
        for v in vals:
            if is_printable_attr_val(v):
                continue
            need_base64 = True
            break
        if not need_base64:
            m[k].set_flags(ldb.FLAG_FORCE_NO_BASE64_LDIF)

    result_ldif = samdb.write_ldif(m, ldb.CHANGETYPE_NONE)

    return result_ldif


def timestamp_to_mins(timestamp_str):
    """Converts a timestamp in -100 nanosecond units to minutes"""
    # treat a timestamp of 'never' the same as zero (this should work OK for
    # most settings, and it displays better than trying to convert
    # -0x8000000000000000 to minutes)
    if int(timestamp_str) == NEVER_TIMESTAMP:
        return 0
    else:
        return abs(int(timestamp_str)) / (1e7 * 60)


def timestamp_to_days(timestamp_str):
    """Converts a timestamp in -100 nanosecond units to days"""
    return timestamp_to_mins(timestamp_str) / (60 * 24)


def attr_default(msg, attrname, default):
    """get an attribute from a ldap msg with a default"""
    if attrname in msg:
        return msg[attrname][0]
    return default