The Samba 4 Python Scripting Interface - Implementing Samba 4 (2014)

Implementing Samba 4 (2014)

Chapter 9. The Samba 4 Python Scripting Interface

This chapter deals with new features of the Samba 4 Server and is focused on the coding of the Samba 4 software. This chapter is the most challenging one in the book as it involves programming and some explorative work as part of our quest and we learn about the Samba 4 code and some existing Python bindings. We will show and explain some excerpts of code as well as a full example of how to use this powerful API on a practical, day-to-day administration script. The following is a list of the topics we will cover in this chapter:

· Open source development and collaborative work

· Exploring and using the Python interface of the Samba 4 Server

· Introducing Samba 4 Python bindings

· Understanding the power of Python and the Samba 4 Server

Open source development and collaborative work

The Samba software and the newest and much-awaited, fully featured Version 4 is the result of a huge project that hides an effortless community and the hard work of hackers from all around the world [71]. No matter how many years I've been involved in open source software initiatives and all the collaborative work and amazing solutions resulting from many different open source projects, I'm still amazed at the dedication and passion these communities employ in building their code and support. Since the beginning of my career [70], I have always been involved in open source projects such as GNU/Linux, FreeBSD, and OpenSolaris. With respect to the last one, I could actually become a contributor for Open Highly Availability Clusters, with my code actually accepted in the mainstream and a book [57] about ZFS [58] published in Brazilian Portuguese. However, I have sent some patches/requests for other open source application software, and even for operating system features (such as the one for FreeBSD [56]).

Open source projects need the collaboration and participation of users, system administrators, developers, and many other individuals who want to be part of it and help with different projects—only that will ensure continuity. It will also help in delivering quality and essential software that today we have running and supporting countless solutions all around the world in different businesses and industries. The Samba project is no exception, and with its scripting interface binding (for example, Python), is a very productive and intelligent move on the part of the project's leaders as the ease-of-use and thus, short learning curve for using a scripting interface such as Python will surely help the project attract more developers quickly.

No software is created by itself; thus, there is a need for collaboration and to have a pulsing community surrounding it that is passionate, always growing, and interacting with users and other developers. The Samba project's leaders know this, and Python bindings as well as some components of the project that are actually written in Python is a clear move towards that objective. I hope this chapter helps the reader embrace this goal and open source ideal, facilitating the adoption, understanding, and participation of new contributors in this amazing project! If we can get at least one new Samba developer or an individual interested in participating in the development of a new feature or helping fix a bug that was incentivized and stimulated, then this book was worth writing!

Exploring and using the Python interface of the Samba 4 Server

Python [59] is a programming language that runs on various platforms and operating systems (for example, Microsoft Windows, GNU/Linux, Illumos [60], FreeBSD [69], Mac OS, and others). Python has some similarities with other interpreted programming languages such as Tcl, Perl, and Ruby, but includes some distinguishing features, such as [61] strong introspection capabilities, intuitive object orientation, full modularity (supporting hierarchical packages), and so on. Based on Wheezy 7.2, a Debian GNU/Linux distribution, we can start playing with Python and the bindings from Samba 4 very quickly. Let's just start looking at the package that already has the python modules ready for installation on Debian Wheezy:

root@addc:~# apt-cache show python-samba

Description-en: Python bindings for Samba

Samba is an implementation of the SMB/CIFS protocol for Unix systems,

providing support for cross-platform file sharing with Microsoft Windows, OS X,

and other Unix systems. Samba can also function as a domain controller

or member server in both NT4-style and Active Directory domains.

.

These packages contain snapshot versions of Samba 4, the next-generation

version of Samba.

.

This package contains Python bindings for most Samba 4 libraries.

...

root@addc:~#

To search for packages on Debian GNU/Linux environments, we can use the apt-cache search command (for example, apt-cache search python-samba). In the command's output in the preceding code, we can see that the apt-cache show command is used to describe the python-samba package as this is the package that contains the Python bindings for most Samba 4 libraries. The Python programming language should already be installed in our operating system as many parts of the Debian GNU/Linux distribution depend on it (and actually, Samba too). We just need to install the Samba Python bindings package; but, before that, let's take a look at the Python interpreter without installing this extra package. We just need to issue the following command:

leal@addc:~$ python

Python 2.7.3 (default, Jan 2 2013, 13:56:14)

[GCC 4.7.2] on linux2

Type "help", "copyright", "credits" or "license" for more information.

The last line in the preceding code is the Python interpreter prompt, which tells us that the programming tool is ready. Now let's do some exploration and understand some important commands that will help us with Python programming in a more general sense. By issuing the following dir() function, we can find out the modules available on the system:

>>> dir()

['__builtins__', '__doc__', '__name__', '__package__']

>>>

This is an important command to know when working with Python. As you can see in the preceding output, when we have just the built-in and default modules loaded, the output for the dir() command should be similar to the previous output. As we have not installed the python-samba package, we do not have any samba package available to load in the Python interpreter. So, let's quit the Python interpreter and install the python-samba package as follows:

>>> quit()

leal@addc:~$ sudo apt-get install python-samba

[sudo] password for leal:

...

Setting up python-samba (4.0.0~beta2+dfsg1-3.2) ...

leal@addc:~$

Tip

Modules in Python implement the different functions that are available. For example, the __builtin__ module implements the dir() function that we are using.

Now we can start the interpreter again and look at the modules available one more time:

leal@addc:~$ python

Python 2.7.3 (default, Jan 2 2013, 13:56:14)

[GCC 4.7.2] on linux2

Type "help", "copyright", "credits" or "license" for more information.

>>> dir()

['__builtins__', '__doc__', '__name__', '__package__']

>>>

Not exactly what we were expecting, as we still do not have anything about Samba on the modules listing. However, that is another important nugget of knowledge about how Python works; as we have just now installed the python-samba module, we have only made it available in our operating system environment, but have not loaded it yet. In order to use it, we need to load it on Python and for that, we use the import function:

>>> import samba

>>> dir()

['__builtins__', '__doc__', '__name__', '__package__', 'samba']

We can see that we have used the import command to load the samba package; after this, we can execute the dir() function and finally see our samba module listed at the end just after the default loaded modules. We have the module installed and loaded in Python, but now what? How do I know what functions are available? How will I use them? We will answer these questions in the following sections.

Introducing Samba 4 Python bindings

Another use of the dir()command is to list the functions and variables available on each module we have loaded on Python. We will issue this command in the Python interpreter as follows:

>>> dir(samba)

['Ldb', 'MAX_NETBIOS_NAME_LEN', '_Ldb', '__builtins__', '__doc__', '__docformat__', '__file__', '__name__', '__package__', '__path__', '_glue', '_ldb', 'check_all_substituted', 'dn_from_dns_name', 'ensure_external_module', 'generate_random_password', 'get_debug_level', 'import_bundled_package', 'in_source_tree', 'interface_ips', 'is_valid_netbios_char', 'ldb', 'nttime2string', 'nttime2unix', 'os', 'param', 'read_and_sub_file', 'samba', 'set_debug_level', 'setup_file', 'source_tree_topdir', 'strcasecmp_m', 'strstr_m', 'substitute_var', 'sys', 'unix2nttime', 'valid_netbios_name', 'version']

So, we used the dir() function by passing an argument with it (for example, samba), and so the output was different. If we call it without passing arguments, it returns the names in the current scope (best effort [67]); but, with arguments (and depending on the argument), it may return the module's attributes or its class attributes. Thus we can dig deeper and get more information using the dir() function on one of the preceding attributes and look deeper into the Samba module. We are making progress, and now let's goone step further. We will continue to use the dir() function, but now we will pass one of the names we received in the previous output as an extra argument:

>>> dir(samba.MAX_NETBIOS_NAME_LEN)

['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__float__', '__floordiv__', '__format__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__', '__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real']

We recognize the methods from the preceding output, and so MAX_NETBIOS_NAME_LEN should be an int object. We can confirm this by just issuing the following command:

>>> dir(int)

['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__float__', '__floordiv__', '__format__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__', '__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real']

>>>

Bingo[62]! Any similarity is not just mere coincidence. Before we continue looking deeper, let's just print the integer number to recognize MAX_NETBIOS_NAME_LEN:

>>> print samba.MAX_NETBIOS_NAME_LEN

15

>>>

That is another beautiful feature of the Python language: we just used print and the int object, and we got the result (that is not so simple for some other programming languages). It's really intuitive and as you start to play with the language, you will see that it's really simple, and that is one of the reasons we have so many hackers addicted to it. So, in the preceding code, we have the samba package defining the maximum name length for NetBIOS[63]. Thus, we can use it just as we implement any program to execute an administrative task and validate the machine's NetBIOS name, for example. As a learning example, let's dig even deeper and use another important built-in function called help() on the Python interpreter, following the same utilization pattern of the dir() function. Have a look at the following example:

>>> help(int)

Help on class int in module __builtin__:

class int(object)

| int(x[, base]) -> integer

|

| Convert a string or number to an integer, if possible. A floating point

| argument will be truncated towards zero (this does not include a string

| representation of a floating point number!) When converting a string, use

| the optional base. It is an error to supply a base when converting a

| non-string. If base is zero, the proper base is guessed based on the

| string content. If the argument is outside the integer range a

| long object will be returned instead.

|

| Methods defined here:

|

| __abs__(...)

| x.__abs__() <==> abs(x)

|

...

In the preceding code, we have used the help function to show us the documentation for the Int class. At the end of the code, we can see that we have listed the methods available for any object of that class, and if we want to, we can use the help() method for specific methods to know how we can use it:

>>> help(int.__add__)

Help on wrapper_descriptor:

__add__(...)

x.__add__(y) <==> x+y

(END)

>>>

We can use the methods in a very straightforward way and we can see them in action as follows (for example, print samba.MAX_NETBIOS_NAME_LEN+5):

>>> print samba.MAX_NETBIOS_NAME_LEN.__add__(5)

20

>>>

In our findings, we were expecting a function called generate_random_password that can be handy if the name suggests exactly what it does. Let's take a look at the documentation for it as follows:

>>> help (samba.generate_random_password)

Help on built-in function generate_random_password in module samba._glue:

generate_random_password(...)

generate_random_password(min, max) -> string

Generate random password with a length >= min and <= max.

(END)

>>>

It seems really easy to use—actually too easy to be true—so let's try it:

>>> print(samba.generate_random_password(15, 20))

ccOy$fxyY[T49k-c

That's it! We just call it by passing a minimum and a maximum length for the new generated password, and it's done. The extra aspect of this function is that it will generate a password that satisfies the minimum requirements for the Microsoft Windows OS (for example, special characters). We can create a quick and dirty Python script to use a handy function. First, let's use the quit() method in our Python interpreter as follows:

>>> quit()

leal@addc:~$

Tip

The classes and methods are case sensitive, so we need to pay attention when searching and using specific functions, or we may get the results or information about a subject that is different from the one we are really running a search for.

Now, just type the following command in the shell prompt:

leal@addc:~$ python -c "import samba; \

print(samba.generate_random_password(15, 15))"

7LuApNZi4JWx!f-

leal@addc:~$

Cool, isn't it? If you have some issues with creating random passwords or want to create a batch of users and one specific password for each one, this function can be a good partner for the job. I'm sure you already have many ideas about how to explore these Samba 4 Python bindings, and in the next section we will take this to a new level!

Tip

The generate_random_password function is implemented on the samba._glue function where Python bindings for miscellaneous Samba functions reside (for example, help (samba._glue)).

Understanding the power of Python and the Samba 4 Server

Now that we already know how to explore the Python interpreter and the Samba 4 Python bindings, we will explore another great feature of the open source community: the source code. I think a general rule for learning programming, and it really works for me, is to actually look at the source code and learn from the examples. The Samba 4 source code has a lot of examples and scripts used to run tests on the compiled server, which we can use to learn many things. The example code combined with our technique to explore the modules and documentation should be sufficient for us to create a full piece of code that we can be proud of.

It seems there was a bug on Debian GNU/Linux-based distributions regarding the libauth4.so library [64]. If you face any issues loading some Samba 4 Python bindings, try to install the libdcerpc-server0 package (for example, apt-get install libdcerpc-server0). The idea is to show an approach where we can go from zero to hero. So, the assumption is that we do not know much about Python programming. We just need some understanding of the working of the Samba 4 Server, and some knowledge about operating system and programming languages in general. We want to try our luck with the Samba development for our own utilization and maybe to help the community and make our contribution to the future.

Our objective is to implement a Python script that can be used to query our Samba Server, and for that, we have some idea of the functions we need to use to accomplish our task (for example, connection/authentication and query/search). What I normally do for such tasks is first use the Python interpreter to explore and test the modules and functions and flex the muscles as hard as I can. The point here is to try your best to understand the internals of the language you're learning; because that can give you excellent results if you can actually figure out how it works. Secondly, as it is not possible to fully understand the concepts directly from the documentation or if it is just missing that glue to simply make everything make sense, then it's time to look at some examples and dig into the source code to see how the developers have actually implemented it. We cannot forget that every open source community has discussion mailing lists and IRC channels where many enthusiasts are willing to help everybody [72]. I think that the most important point is that everyone is different, and so I will go through a procedure that has worked for me, but feel free to adapt to your personal preference; the essential point is to be loyal to whatever works for you and makes you feel more comfortable and confident.

Don't be afraid to make mistakes, or being restricted to some programming conventions and all that stuff from the beginning. If you develop something that is useful for others, more experienced programmers will help you with tips on how to conform to any rules or make your code more readable. This comes with practice and time, so take your time and keep going! We already have some idea of the modules and functions available on the samba package, but what we have not discussed yet is some underlying implementations of packages, modules, and functions in Python [66]; this is a little over the top for our discussion here, so we will leave that theory for the reader as an exercise for the reader.

Note

The dir() function tries its best to gather information from the object's __dict__ attribute, if one has been defined, and its type object.[67].

So, we cannot always have a complete picture of what is available just by using dir(), and if we look at the preceding reference about modules and the import() function, we will see that importing one package will not necessarily import all modules. In the references [65], we have a link for the online documentation about the Python API on Samba.

No more talk, let's code! To find our example source codes to learn about specific functions and techniques, we can execute the following script to first find the Python scripts on the Samba 4 source code tree:

leal@addc:~$ find workspace/samba-4.0.9/ -type f -name *.py –print

...

workspace/samba-4.0.9/buildtools/wafadmin/Tools/misc.py

workspace/samba-4.0.9/buildtools/wafadmin/Tools/gdc.py

workspace/samba-4.0.9/buildtools/wafadmin/TaskGen.py

workspace/samba-4.0.9/buildtools/wafadmin/ansiterm.py

workspace/samba-4.0.9/buildtools/wafadmin/Logs.py

workspace/samba-4.0.9/buildtools/wafadmin/__init__.py

workspace/samba-4.0.9/buildtools/wafadmin/3rdparty/go.py

workspace/samba-4.0.9/buildtools/wafadmin/3rdparty/boost.py

...

leal@addc:~$ find workspace/samba-4.0.9/ -type f -name *.py -print | wc –l

478

leal@addc:~$

As we can see in the preceding code, we have 478 Python scripts on the Samba 4 source code tree, and that is a good start. Many of them can be good sources of information about utilizing the Samba 4 Python bindings though some may not be of direct use to us at this stage. So, one tip is to actually try to find examples in the source tree, as they are more likely to have the information we need at the moment. A good guess is trying to find exactly these (for instance, examples):

leal@addc:~$ cd workspace/samba-4.0.9/ && echo OK

OK

leal@addc:~$ find ./ -type f -name *.py -print | grep -i example

./python/examples/winreg.py

./python/examples/netbios.py

./python/examples/dnsserver.py

./python/examples/samr.py

./lib/dnspython/examples/mx.py

./lib/dnspython/examples/reverse_name.py

./lib/dnspython/examples/ddns.py

./lib/dnspython/examples/e164.py

./lib/dnspython/examples/reverse.py

./lib/dnspython/examples/zonediff.py

./lib/dnspython/examples/xfr.py

./lib/dnspython/examples/name.py

./examples/logon/ntlogon/ntlogon.py

./examples/scripts/vfs/media_harmony/trigger_avid_update.py

./examples/scripts/shares/python/SambaParm.py

./examples/scripts/shares/python/SambaConfig.py

./examples/scripts/shares/python/smbparm.py

./examples/scripts/shares/python/generate_parm_table.py

./examples/scripts/shares/python/modify_samba_config.py

./source3/stf/example.py

leal@addc:~$

So, the preceding output has some interesting Python scripts for us to look at when seeking some light to be shed on specific functions. Usually, another good keyword to use when looking for examples is test. In the implementation of tests, we need to use the components, so we can try another filter in the first output as follows:

leal@addc:~$ cd workspace/samba-4.0.9/ && echo OK

OK

leal@addc:~$ find ./ -type f -name *.py -print | grep -i test

...

./python/samba/tests/libsmb_samba_internal.py

./python/samba/tests/upgrade.py

./python/samba/tests/dsdb.py

./python/samba/tests/ntacls.py

./python/samba/tests/gensec.py

./python/samba/tests/getopt.py

./python/samba/tests/policy.py

...

leal@addc:~$

Well, as we find some test folders and our first task before executing queries is to connect/authenticate to the server, let's add another filter:

leal@addc:~$ cd workspace/samba-4.0.9/ && echo OK

OK

leal@addc:~$ find ./ -type f -name *.py -print | grep -i test | grep auth

./python/samba/tests/auth.py

./auth/credentials/tests/bind.py

./source3/torture/test_ntlm_auth.py

leal@addc:~$

The preceding output is really encouraging as we have just two files: one named bind the other named auth. Let's start looking at the bind.py script, and after going through the script's source code, we can quickly see the following excerpt (the file has just 42 lines):

ldb = samba.tests.connect_samdb(host, credentials=creds, lp=lp, ldap_only=True)

The nice part about the preceding line of code is that it is not strange for us as we already know something about the samba.tests.connect_samdb construction. This is sufficient, I would say, to know that we need to focus on the connect_samdb part and not onsamba.tests. So, let's look for that definition in the source tree:

leal@addc:~$ cd workspace/samba-4.0.9/ && echo OK

OK

leal@addc:~$ find ./ -type f -name *.py -exec grep -q "def connect_samdb" '{}' \; -print

./python/samba/tests/__init__.py

leal@addc:~$

Looking inside the Python script file in the preceding code, we can identify the following relevant lines (inside the connect_samdb definition):

leal@addc:~$

...

return SamDB(url=samdb_url,

lp=lp,

session_info=session_info,

credentials=credentials,

flags=flags,

options=ldb_options)

...

leal@addc:~$

That's it! We have found the function definition that the bind.py script was calling and that function is doing the magic. Now that we know the exact method we need to call (for example, SamDB), let's look at the beginning of the same Python script file to find out which modules we need to load:

...

import samba

from samba import param

from samba.samdb import SamDB

Now we have something to start with to write our Python script; for now, just create a file (for example, ch9.py) with the following content:

#!/usr/bin/python

#byLeal

# Chapter 9 Example Script

# Loading...

import samba

from samba import param

from samba.samdb import SamDB

# Binding...

cx = (url='ldap://localhost',

lp=lp,

session_info=session_info,

credentials=credentials,

flags=flags,

options=ldb_options)

As we can see in the preceding code, we have just changed the URL variable as it is the only information we know for now and we do not know yet exactly what the others are and how to properly configure them. So, let's continue looking at the __init__.py python script and look for the initialization of these variables to understand them and finish the first part of our code. At the beginning of the connect_samdb function, we have some interesting information as follows:

:param samdb_url: Url for database to connect to.

:param lp: Optional loadparm object

:param session_info: Optional session information

:param credentials: Optional credentials, defaults to anonymous.

:param flags: Optional LDB flags

:param ldap_only: If set, only remote LDAP connection will be created.

The preceding code gives us information about every variable; most of them are optional, so we will take them out of the running for now, except for the credentials function as we know that our server needs authentication. We can look at the code for initializing them and as authentication should be our next step, when we look for credentials in the code, we find the following:

..

if credentials is None:

credentials = cmdline_credentials

..

When we look for cmdline_credentials, we find the following:

cmdline_credentials = None

This is really not encouraging, so let's take a look at the Python interpreter to see whether or not we can figure out how to set the credentials without command-line parsing at this stage by running the following code:

leal@addc:~$ python

>>> from samba.credentials import Credentials

>>> dir(Credentials)

['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'authentication_requested', 'get_bind_dn', 'get_domain', 'get_gensec_features', 'get_named_ccache', 'get_nt_hash', 'get_password', 'get_realm', 'get_username', 'get_workstation', 'guess', 'is_anonymous', 'parse_string', 'set_anonymous', 'set_bind_dn', 'set_cmdline_callbacks', 'set_domain', 'set_gensec_features', 'set_kerberos_state', 'set_krb_forwardable', 'set_machine_account', 'set_password', 'set_realm', 'set_username', 'set_workstation', 'wrong_password']

>>>quit()

leal@addc:~$

The class has methods to set the username and password, and that should be everything we need. So, with the new things we learned, our updated code would look like the following:

#!/usr/bin/python

# Chapter 9 Example Script

# Loading...

import samba

from samba import param

from samba.samdb import SamDB

from samba.credentials import Credentials

badge = Credentials()

badge.set_username('Administrator')

badge.set_password('w1ndow$$!')

# Binding...

cx = SamDB(url='ldap://localhost',

credentials=badge)

Let us now test the preceding code:

leal@addc:~$ chmod 755 ch9.py && OK

OK

leal@addc:~$./ch9.py

PANIC: assert failed at ../lib/param/loadparm.c(3790): lp_ctx != NULL

PANIC: assert failed: lp_ctx != NULL

Aborted

leal@addc:~$

We are on a mission and the preceding message is pretty interesting because it's related to the loadparm.c code and we had a loadparm argument (for example, object) in SamDB, but it was marked as optional. Based on the PANIC action in the preceding code (assertion failure, lp_ctx != NULL), it looks like one of two options: a bug or a false statement. If the lp object is optional, the Samba code seems to have a bug; on the other hand, if the lp object is really needed, the information about it being optional is a false statement. Well, looking for an answer to this situation, I found this reference [68], where developers are discussing it and the bottom line is that the lp object seems not to be optional at all as it loads some important definitions of the environment needed for many subsystems (credentials included). In the same link, we know the typical construction for using the lp object for the credentials that are mentioned as follows:

creds.guess(lp)

In the file __initi__.py the variable lp is defined at the function env_loadparm() like this:

def env_loadparm():

lp = param.LoadParm()

Going back to our updated code, initializing the lp object (LoadParm()), and adding the lp object to the credentials and the SamDB call, we get the following code:

#!/usr/bin/python

# Chapter 9 Example Script

# Loading...

import samba

from samba import param

from samba.samdb import SamDB

from samba.credentials import Credentials

lp = param.LoadParm()

badge = Credentials()

badge.guess(lp)

badge.set_username('Administrator')

badge.set_password('w1ndow$$!')

# Binding...

cx = SamDB(url='ldap://localhost',

lp=lp,

credentials=badge)

Upon executing the preceding code, we get:

leal@addc:~$ ./ch9.py && echo OK

OK

leal@addc:~$

The preceding code is executed without errors, which is a good signal. Now that we have an authenticated connection, we can go to the next phase: performing a query. So, let's take a look at the SamDB call using our old friend dir() to see whether or not we have asearch method using the following code:

leal@addc:~$ python

>>> from samba.samdb import SamDB

>>> dir(SamDB.search)

['__call__', '__class__', '__delattr__', '__doc__', '__format__', '__get__', '__getattribute__', '__hash__', '__init__', '__name__', '__new__', '__objclass__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

>>>

We have it, so let's take a look at its documentation as follows:

>>> help(SamDB.search)

Help on method_descriptor:

search(...)

S.search(base=None, scope=None, expression=None, attrs=None, controls=None) -> msgs

Search in a database.

:param base: Optional base DN to search

:param scope: Search scope (SCOPE_BASE, SCOPE_ONELEVEL or SCOPE_SUBTREE)

:param expression: Optional search expression

:param attrs: Attributes to return (defaults to all)

:param controls: Optional list of controls

:return: Iterator over Message objects

(END)

>>>

We have everything we needed to know! Now let's execute our search, and in order to do so, we just add the following code to our Python script (leaving some optional parameters out of the equation):

# Search...

search_result = cx.search('DC=poa,DC=msdcbrz,DC=eall,DC=com,DC=br',

scope=2,

expression='(objectClass=user)',

attrs=["samaccountname"])

Let's test it to see if everything still executes without error:

leal@addc:~$ ./ch9.py && echo OK

OK

leal@addc:~$

In this case, we use the dir() function by just passing SamDB as an argument. We will see that it has one method named domain_dn. If we call it, we would get the following result: poa,DC=msdcbrz,DC=eall,DC=com,DC=br. Well, based on the documentation of the function, we need to iterate our search_result function to print all the attribute values we queried (for example, samaccountname/usernames). We can accomplish this in Python using a for loop, calling the get method on our results as follows:

# Results...

for username in search_result:

print("User: %s" % username.get("samaccountname", idx=0))

We need to just add the preceding code to our ch9.py script and execute it like the following example to see the result:

leal@addc:~$ ./ch9.py && echo OK

...

User: djfox

User: joliu

User: kakim

User: lamei

User: root

OK

leal@addc:~$

The preceding output is something we are very familiar with, and so we are sure that our first script using the new Samba 4 Python bindings is fully working! How many lines of code were needed? 18 (without blank lines and comments).With 18 lines of code, we could create a useful script, and that just shows how important these Samba 4 Python bindings are. They leverage the participation of new individuals that are just starting with the programming, without them requiring a lot of knowledge, and this is really important to incentivize more and more people to feel capable of helping.

Summary

In this chapter, we learned the importance of the open source community and were introduced to the Samba 4 Python bindings. We used the Python interpreter to explore the Samba 4 Python bindings in a very intuitive way as well as learning some essential Python functions to get insights about packages, modules, and functions/methods. In the end, we learned how to use the source code to help us understand specific functions and implementation details. We used all these components to implement a step-by-step procedure in creating our first Python script that makes use of some Samba 4 Python bindings (for example, to connect to the Samba 4 Server at the local host, authenticate, and execute a simple query listing usernames from the base). We provided users a full, working example that can be useful for anyone who wants to start participating and collaborating, which is a real need for any open source community.