Do you ever need to copy one file from one server to another? If you have ssh access to the remote server then you can do an scp command like so:
1 | $ scp remoteUsername@remoteServername:remoteFile localFile |
If you’ve ever pasted an LDAP query into a config file without really understanding what each piece means, this post is for you. We’ll cover the two concepts that trip people up most — Base DN and Search Filter — then spin up a real LDAP server in Docker and run a few queries against it. By the end you’ll be able to read (and write) most LDAP queries you encounter in the wild. 🗂️
An LDAP directory is a tree. Every entry has a unique Distinguished Name (DN) — its full path from the root, written right-to-left, comma-separated. For example:
1 | uid=alice,ou=people,dc=example,dc=org |
This says: an entry with uid=alice, inside the people Organisational Unit, inside the example.org domain. Each piece is a Relative Distinguished Name (RDN); the whole chain is the DN. Common pieces:
The Base DN is the node in the tree where your search starts. Think of it as cd-ing into a directory before running find. Pick it well and you skip work; pick it badly and you scan the whole tree (or worse, you miss what you wanted).
Combined with the Base DN, the scope tells the server how deep to go: base (just that one node), one (immediate children only), or sub (the whole subtree). sub is the default in most clients.
Search Filters are RFC 4515 expressions wrapped in parentheses. The basic shape is (attribute=value). Some examples:
Combine filters with the prefix-notation operators & (AND), | (OR), ! (NOT). The operator goes first, then the sub-filters, all wrapped in their own parens:
It looks alien at first because of the prefix notation, but it’s actually consistent — once you spot that the operator always comes before its operands, the rest is just nesting.
Reading about LDAP is a slog. Running queries against a real directory is faster. The osixia/openldap image ships an OpenLDAP server with sensible defaults and a one-line spin-up:
1 | docker run -p 389:389 -p 636:636 --name my-openldap --detach osixia/openldap:1.5.0 |
That’s it. The container boots with these defaults:
A quick note on the image: osixia/openldap is largely unmaintained as of 2021 — last tagged release is 1.5.0 — but it’s still excellent for a five-minute tutorial because it does the right thing out of the box. For long-running production use, look at bitnami/openldap or run OpenLDAP directly on a host you control.
Now load a few sample entries. Save the following as seed.ldif:
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 | dn: ou=people,dc=example,dc=org objectClass: organizationalUnit ou: people dn: ou=groups,dc=example,dc=org objectClass: organizationalUnit ou: groups dn: uid=alice,ou=people,dc=example,dc=org objectClass: inetOrgPerson cn: Alice Anderson sn: Anderson uid: alice mail: alice@example.org userPassword: alicepass dn: uid=bob,ou=people,dc=example,dc=org objectClass: inetOrgPerson cn: Bob Brown sn: Brown uid: bob mail: bob@example.org userPassword: bobpass dn: uid=carol,ou=people,dc=example,dc=org objectClass: inetOrgPerson cn: Carol Carter sn: Carter uid: carol mail: carol@example.org userPassword: carolpass |
Then load it with ldapadd (install ldap-utils on Debian/Ubuntu, openldap-clients on RHEL, or brew install openldap on macOS):
1 | ldapadd -x -H ldap://localhost -D "cn=admin,dc=example,dc=org" -w admin -f seed.ldif |
The flags: -x simple bind (no SASL), -H the server URL, -D the bind DN, -w the password, -f the LDIF file.
List everyone in the directory:
1 | ldapsearch -x -H ldap://localhost -b "dc=example,dc=org" "(objectClass=person)" |
The -b flag is the Base DN; the quoted (objectClass=person) is the Search Filter. Notice we didn’t bind as anyone — anonymous bind is allowed by default for read.
Just the people branch, with a narrower base DN:
1 2 3 | ldapsearch -x -H ldap://localhost \ -b "ou=people,dc=example,dc=org" \ "(objectClass=inetOrgPerson)" |
Find one user, returning only their email and full name:
1 2 3 | ldapsearch -x -H ldap://localhost \ -b "dc=example,dc=org" \ "(uid=alice)" cn mail |
The trailing cn mail is the attribute list — without it, the server returns every attribute on the entry. Always specify the attributes you actually need; it’s faster and easier to read.
Wildcard prefix match — everyone whose cn starts with “A” or “B”:
1 2 3 | ldapsearch -x -H ldap://localhost \ -b "ou=people,dc=example,dc=org" \ "(|(cn=A*)(cn=B*))" cn |
Combined AND filter — every person who has a mail attribute and isn’t admin:
1 2 3 | ldapsearch -x -H ldap://localhost \ -b "dc=example,dc=org" \ "(&(objectClass=person)(mail=*)(!(cn=admin)))" cn mail |
Microsoft Active Directory speaks LDAP, but adds its own conventions. The dsquery command is the Windows-side equivalent of ldapsearch:
1 | dsquery * "CN=Users,DC=myadserver,DC=com" -scope onelevel -attr objectguid proxyaddresses -limit 2000 >C:\myadserver.user.list.txt |
Unpack that the same way: “CN=Users,DC=myadserver,DC=com” is the Base DN, -scope onelevel is the scope (immediate children only), -attr objectguid proxyaddresses is the attribute list. There’s no explicit filter, so * means “all entries” — equivalent to (objectClass=*).
A few AD-specific gotchas worth knowing:
Once Base DN, Search Filter, and scope click, every LDAP query you’ll see in the wild — from a Jenkins auth config, a SSSD setup, a Keycloak federation, an old Java InitialDirContext — is just those three concepts plus a credential. Have fun. 🌳
There are times when you want to call a mysql command from the bash or dos script then call this script in a scheduler (cron job).
The example below will show you how to do so.
1 | mysql -uYOURUSERNAME -pYOURPASSWORD -DYOURDATABASE -e"CALL YOURSTOREDPROCEDURE('YOUR_SP_PARAMETER')" |
Explanation:
-u is where you put your username, you can either put a space or no space at all after the -u (both works). As you can see I prefer not to put any space.
-p is where you put your password.
-D is your database name. That’s a capital D.
-e this is where your query will go. It needs to be quoted (“). If it’s calling a stored procedure or stored function, you’ll need to use the CALL keyword, otherwise if it’s just a simple query don use the CALL keyword.
Subversion (SVN) sometimes clutter our source directory by creating .svn folder in each folder or sub folder that we version control.
The easiest way to erase them is by using a bash command:
1 | find . -name ".svn" -type d -exec rm -rf {} \; |
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?php error_reporting(E_ALL); $memcache = new Memcache; // Connect to memcached server $memcache->connect('127.0.0.1', 11211) or die ("Could not connect"); // Add it to memcached server // The parameters are: KEY, VALUE, USE COMPRESSION, EXPIRY IN SECONDS $memcache->set('MyKey1', 'The value of My Key1 is me', false, 100); echo $memcache->get('MyKey1'); // It will show you: The value of My Key1 is me ?> |
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 | <?php require "../predis/autoload.php"; // Update with your location to predis Predis\Autoloader::register(); try { $redis = new Predis\Client(); // Uncomment the following lines and adjust them accordingly if you have a non-default redis configuration /* $redis = new Predis\Client(array( "scheme" => "tcp", "host" => "127.0.0.1", "port" => 6379)); */ echo "Connected to Redis"; } catch (Exception $e) { echo "Unable to connect to Redis"; echo $e->getMessage(); } $redis->set("var1", "The value for var1 is me"); echo $redis->get("var1"); // You can alos use exits() function to test if a variable exists or not echo ($redis->exists("var2")) ? "true" : "false"; |
If you still tracing variable in php using print_r() or var_dump() then you’ll see them directly in the screen or the web page that you’re working on. This is easy to see, but also prone to problems:
Best way to do code tracing in PHP is to write the following code into your file – one of the early-executed file, such as config/header file, where you don’t have any output yet:
1 2 3 | ini_set('log_errors','On'); ini_set('error_log', '/var/wwwtmp/php.log'); error_reporting(E_ALL ^ (E_NOTICE | E_WARNING)); |
And at the part where you want to trace a certain variable
1 |
By default apache2 and php will output their error in /var/log/apache2/error.log ,but after executing the ini_set above, the error log will be produced in /var/wwwtmp/php.log. And you need to make sure you that directory exists, if not use other directory that www-data has write access to. Why do we need to setup in a different location? Because the default file output doesn’t support showing new lines, you’ll see lot’s of \n. Thus make tracing hard to read.