Can’t send mail with Outlook 2016 for Mac

Published / by Taco Scheltema / Leave a Comment

I recently setup a new email account for a customer who is using Outlook 2016 for Mac OSX. The customer was able to setup the new account in Outlook without any issues but when he tried sending mail he received the following error

Authentication fails with error 17895

After some testing on our end we worked out that our server doesn’t offer a suitable authentication mechanism on SMTP as Outlook doesn’t support the plain mechanism.

The server we use is a Modoboa setup which uses Dovecot. in the file /etc/dovecot/conf.d/10-auth.conf there is an option called auth_mechanisms which lists the authentication mechanisms that the server will offer. Adding ‘login’ as an additional mechanism will allow Outlook to authenticate.

in a standard Modoboa setup (and probably with a standard Dovecot setup as well) the relevant section in /etc/dovecot/conf.d/10-auth.conf looks as follows:

# Space separated list of wanted authentication mechanisms:
# plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi otp skey
# gss-spnego
# NOTE: See also disable_plaintext_auth setting.
auth_mechanisms = plain

Change this to the following

# Space separated list of wanted authentication mechanisms:
# plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi otp skey
# gss-spnego
# NOTE: See also disable_plaintext_auth setting.
auth_mechanisms = plain login

and restart dovecot:

systemctl restart dovecot

After this you should be able to send email through your server with an Outlook for Mac client.

Using ipset with iptables

Published / by Taco Scheltema / Leave a Comment

Some time ago I noticed lots of hacking attempts on some of the servers I manage. Some of them are mail servers where hackers were brute forcing smtp user/password combinations, other servers are web servers with wordpress and magento sites where the logs showed lots of attempts to find vulnerabilities in those sites.

One way of dealing with those is to implement fail2ban which can be efficient if configured right, but I wanted to try and block the majority of those attempts at the firewall. So I started collecting addresses from the logs and started blocking them with normal iptables block rules. This worked for the first 50-60 addresses but soon became unmanageable. Then I found out about publicly available blacklists like blocklist.de and bruteforceblocker so I tried loading block rules based on those lists in the iptables firewalls but that caused iptables to take a few minutes to load(!), it also made the firewalls perform pretty poorly.

So after some investigation I found out about ipset. Ipset allows you to create tables that hold a large amount of ip addresses and or networks (amongst a few other things) that can be queried without a hit on performance.

To set it up you’ll need to install ipset. On Debian this is done as follows

~$ apt-get install ipset

on Yum based systems you’d use

~$ yum install ipset

A simple example of setting up an ipset table with some ip addresses and networks and a matching iptables rule. The list will be called example_list

~$ ipset create example_list hash:net family inet

This creates an empty table. Now we can add addresses and networks to the list, for this example I’ll use addresses from the non-public 10.x.x.x block

~$ ipset add example_list 10.1.1.1
~$ ipset add example_list 10.1.2.0/24
~$ ipset add example_list 10.1.3.2
~$ ipset add example_list 10.1.3.3

To see the contents of the table you use the following command

~$ ipset list example_list
Name: example_list
Type: hash:net
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 16880
References: 0
Members:
10.1.2.0/24
10.1.3.2
10.1.1.1
10.1.3.3

And to check if an address is matched in an ipset table you use this

~$ ipset test example_list 10.1.2.43
10.1.2.43 is in set example_list.

~$ ipset test example_list 10.20.2.100
10.20.2.100 is NOT in set example_list.

You’ll notice that the time for the test command to complete is minimal. Of course there are only 4 entries in the table but this command will perform just as well with 60,000 entries in the table.

For this example, you would use the following iptables rule to block addresses that are contained in the table

iptables -A INPUT -m set --match-set example_list src -j DROP -m comment --comment "ipset: example_list"

Now, adding the iptables rule before the ipset table is created will fail as iptables can’t reference a table that doesn’t exist. On the other hand, the ipset table can’t be removed as long as iptables references it. This also highlights the first issue when implementing this; iptables will fail to start/load when the ipset tables it references haven’t been created and this will cause iptables to not load at boot time. Also, ipset tables are loaded in memory and won’t survive a reboot which means we’ll need to create the ipset tables before iptables starts.

Another potential issue is that when using external blocklists like the one from blocklist.de, they’ll need to be updated regularly. So I’ve written a few scripts to take care of all of this. I’m not claiming that this is the best way of doing things but it’s working well for me.

First script is a script to retrieve ip blocklists, i’ve called it ‘getblocklist.sh’ and resides in /usr/local/sbin. It will work with most blocklists, all it expects is one ip address per line. It will store a local version of the list and only download a new one if the local version is over 24 hours old, this to avoid unnecessary load on the remote server. The script will create an ipset table if it doesn’t exist and flush the table if it does.

/usr/local/sbin/getblocklist.sh
#!/bin/bash
#
# Taco Scheltema Mon 31 Aug 2015 12:04:36 AM CEST
# Modified: Fri 09 Dec 2016 12:31:24 AM CET

# Path to store blocklists
CPATH=/etc/ipset
ipset=/usr/sbin/ipset

if [ ! -d $CPATH ]
then 
    echo "$CPATH does not exist, please create it first"
fi

usage() {
cat<<EOF

 $(basename $0) [-h] -u <url> -l <list>

 -h This help message
 -u blocklist url (txt format, i.e. http://lists.blocklist.de/lists/all.txt)
 -l ipset list name, defaults to ip_blocklist
 -q be quiet

to use the blocklist in your iptables firewall, add a rule like the following:

iptables -A INPUT -m set --match-set <blocklist name> src -j LOG --log-prefix "BLOCKLIST: " -m comment --comment "IP Blocklist match"
iptables -A INPUT -m set --match-set <blocklist name> src -j DROP -m comment --comment "IP Blocklist match"

EOF
}

cleanup(){
 LIST=$1
 URL=$2
 sed -i "s/^\([0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\)\(\/[0-9]\{1,2\}\)\?.*/add $LIST \1\2/" $URL
 cat $URL | grep -v -e ':' -e '^#' -e '^$' | sort -u -o $URL
}

unset QUIET
while getopts "hu:l:q" option
do
 case $option in
 h ) usage; exit ;;
 u ) URL=$OPTARG;;
 l ) LIST=$OPTARG;;
 q ) QUIET=1;;
 esac
done

[ -z $URL ] && {
 usage
 exit 1
}
[ -z $LIST ] && {
 usage
 exit 1
}

$ipset -q flush $LIST
if [ $? -gt 0 ]
then
 $ipset create $LIST hash:net family inet
fi

if [ -e $URL ]
then
 cleanup $LIST $URL
 $ipset restore < $URL
else
 if test ! -e $CPATH/${LIST}
 then
 [ -z $QUIET ] && echo "no local file found, downloading..."
 curl -s $URL | grep -v -e '^;' -e '^#' -e '^$' > $CPATH/${LIST}
 elif test `find $CPATH/${LIST} -mmin +1440`
 then
 [ -z $QUIET ] && echo "local file is older than 24 hrs, downloading..."
 curl -s $URL | grep -v -e '^;' -e '^#' -e '^$' > $CPATH/${LIST}
 else
 [ -z $QUIET ] && echo "local file is less than 24 hrs old, using local file"
 fi
 cleanup $LIST $CPATH/${LIST}
 $ipset restore < $CPATH/${LIST} -q
 EXIT=$?
fi

[ -z $QUIET ] && {
 echo
 echo
 echo "Add the following to your iptables recipe, before any allow rules"
 echo "if you use UFW then add this to /etc/ufw/before.rules"
 echo
 echo "-A INPUT -m set --match-set $LIST src -j LOG --log-prefix \"BLOCKLIST: \" -m comment --comment \"IP Blocklist $URL\""
 echo "-A INPUT -m set --match-set $LIST src -j DROP -m comment --comment \"IP Blocklist $URL\""
}

exit $EXIT
# vim: ts=4 sw=4 et

Next file is the iptables rules file

/etc/ipset/rules
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:TRUSTED - [0:0]
:FILTERBEFORE - [0:0]
:PUBLIC - [0:0]
:LOGGER - [0:0]

-A INPUT -i lo -j ACCEPT
-A INPUT -d 127.0.0.0/8 ! -i lo -j REJECT --reject-with icmp-port-unreachable
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

# Jump to FILTERBEFORE
# Add any service that you want exposed to the internet
# traffic to these ports will first go through the FILTERBEFORE chain before being
# allowed. in FILTERBEFORE the incoming address will be matched against our ipset 
# tables and blocked if a match is found
-A INPUT -p tcp -m tcp -m multiport --dports smtp,smtps,pop3s,imaps -j FILTERBEFORE

# Jump to TRUSTED
# add any ports that you only want to allow from trusted addresses, in the chain TRUSTED
# these are matched against the ipset allow tables that we've created
-A INPUT -p tcp -m tcp -m multiport --dports http,https -j TRUSTED -m comment --comment "Send http & https to chain TRUSTED"
-A INPUT -p tcp -m state --state NEW -m tcp -m multiport --dports ssh -j TRUSTED -m comment --comment "Send ssh to chain TRUSTED"
-A INPUT -p tcp -m state --state NEW -m tcp -m multiport --dports 5666 -j TRUSTED -m comment --comment "Send nrpe to chain TRUSTED"
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

# Stop the noise
-A INPUT -p tcp -m tcp -m multiport --dports telnet,bootps,bootpc -j DROP -m comment --comment "Do not log noisy protocols"

# Anything that gets to here should be blocked, logging is optional but if you do log
# it we'll use rate limiting to avoid our log file filling up
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "[ DENIED ] " --log-level 7
-A INPUT -j REJECT --reject-with icmp-port-unreachable
-A FORWARD -j REJECT --reject-with icmp-port-unreachable
-A OUTPUT -j ACCEPT

###### TRUSTED - only allow known sources ######
## ipset allow rules will be inserted here by /usr/local/sbin/firewall
-A TRUSTED -s 10.1.1.0/24 -j ACCEPT -m comment --comment "Internal network"
-A TRUSTED -s 10.2.2.0/24 -j ACCEPT -m comment --comment "VPN network"
# Add a separate rule for the ip address that you are coming from to avoid being locked out
# replace 10.3.3.122 with the external address of your home connection
-A TRUSTED -s 10.3.3.122 -p tcp -m tcp -m multiport --dports ssh -j ACCEPT -m comment --comment "Allow myself"
# Optionally log anything that still makes it past here (should not happen)
-A TRUSTED -m limit --limit 5/min -j LOG --log-prefix "[ DENIED ] "
# Block anything that makes it past here (should not happen)
-A TRUSTED -j DROP


###### FILTERBEFORE - Block all bad people ######
## ipset rules will be inserted here by /usr/local/sbin/firewall
## anything that isn't matched in the ipset tables jumps to PUBLIC
-A FILTERBEFORE -j PUBLIC

###### PUBLIC - Allow from anywhere ######
-A PUBLIC -j ACCEPT

COMMIT

Next we’ll need a script to load the firewall rules and to fill the ipset tables;

/usr/local/sbin/firewall
#!/bin/bash

# Load iptables rules before interfaces are brought online
# This ensures that we are always protected by the firewall
# On debian, create a symlink as follows:
# ln -s /usr/local/sbin/firewall /etc/network/if-pre-up.d/firewall
# On Centos/RHEL systems, the symlink should be
# ln -s /usr/local/sbin/firewall /sbin/ifup-pre-local
#
# Note: if bad rules are inadvertently (or purposely) saved it could block
# access to the server except via the serial tty interface.
#

RULES=/etc/ipset/rules
CHAIN_ALLOW=TRUSTED
CHAIN_DENY=FILTERBEFORE

## If you use fail2ban on this system you may want to stop it before
## and start it after loading the firewall 
#service fail2ban stop
## load our rules
iptables-restore < $RULES
#service fail2ban start

# Define an array of block- and allow lists
# A list with a name ending on allowed will be added to the
# TRUSTED chain, all others to FILTERBEFORE
# Lists can be a local file with one ip address or cidr per line
# or a URL, the lists are parsed and converted to a format that can be passed
# to ipset 
declare -A ipset_lists=(
 [blocklist_de]="http://lists.blocklist.de/lists/all.txt"
 [bruteforce]="http://danger.rulez.sk/projects/bruteforceblocker/blist.php"
 [spamhaus]="http://www.spamhaus.org/drop/drop.lasso"
 [ci_badguys]="http://cinsscore.com/list/ci-badguys.txt"
 [trusted_allowed]="/etc/ipset/trusted_allowed"
 [localban]="/etc/ipset/localban"
 )

# Iterate over the array above and insert iptables rules accordingly
# for each list, the getblocklist.sh script is called which creates the ipset tables
for elem in ${!ipset_lists[@]}
do
 echo -n "$elem ${ipset_lists[$elem]}: "
 /usr/local/sbin/getblocklist.sh -u ${ipset_lists[$elem]} -l $elem -q
 echo
 if [[ $elem =~ allowed ]]
 then
 iptables -I $CHAIN_ALLOW 1 -m set --match-set $elem src -m state --state NEW -j LOG --log-prefix "[ALLOWED]: $elem: " -m limit -m comment --comment "IP Allowlist ${ipset_lists[$elem]}"
 iptables -I $CHAIN_ALLOW 2 -m set --match-set $elem src -j ACCEPT -m comment --comment "IP Allowlist ${ipset_lists[$elem]}"
 else
 iptables -I $CHAIN_DENY 1 -m set --match-set $elem src -j LOG --log-prefix "[BLOCKED]: $elem: " -m comment --comment "IP Blocklist ${ipset_lists[$elem]}"
 iptables -I $CHAIN_DENY 2 -m set --match-set $elem src -j DROP -m comment --comment "IP Blocklist ${ipset_lists[$elem]}"
 fi
done

# vim: ts=4 sw=4 et

Now create the /etc/ipset directory and for any local list that you have, make sure a file exists. these files can be empty. so for the example above, create /etc/ipset/localban and /etc/ipset/trusted_allowed

mkdir /etc/ipset
touch /etc/ipset/localban
touch /etc/ipset/trusted_allowed

The firewall script should be loaded before the network starts at boot time, to do this, create a symlink. On debian, create a symlink as follows:

ln -s /usr/local/sbin/firewall /etc/network/if-pre-up.d/firewall

On Centos/RHEL systems, the symlink should be

ln -s /usr/local/sbin/firewall /sbin/ifup-pre-local

Now, when you run the firewall script for the first time, it will download the defined blocklists and setup your firewall.

Vim: Automatic Last Updated tag

Published / by Taco Scheltema / Leave a Comment

Ever wanted to know when a script was last updated and don’t necessarily want to implement a version control tool like subversion? Adding a comment in your script with the date of the last change is a good start but relies on manually updating the comment every time a change is made. Adding the code below to your .vimrc file (and/or in the .vimrc file of the root user) will automate this for you, it will also add the username of the editor. Of course this is no proper way of auditing but it has proven quite useful when working in a team of 3 to 5 system administrators. This also works well for apache vhost files files or DNS zone files for instance.

autocmd BufWritePre /usr/local/bin/*,/usr/local/sbin/*,~/bin/*,*.sh,*.html,*.pl,*/check_* ks|call LastMod()|'s
fun LastMod()
 if line("$") > 20
 let l = 20
 else
 let l = line("$")
 endif
 let editor_name=system('logname')
 exe "1," . l . "g/^# Modified:/s/# Modified:.*/# Modified: " .
 \ strftime("%c") . " by: " . editor_name
endfun

In the first line you can add file extensions or patterns of files you want to use this function for. Then when you create a new file, say for instance test.sh, add the following line:

 # Modified: x

The x is arbitrary, it will be replaced with the current date & time once the file is saved.

The expression to find the ‘# Modified:’ tag could be changed to cater for files that use a different symbol for in-line comments

Send Attachment from command line

Published / by Taco Scheltema / Leave a Comment

The script below allows you to send an email with attachment from command line. Use it to automate sending logfiles, images, documents, etc.

Dependencies

  • Getopt::Long
  • Pod::Usage
  • MIME::Lite
  • File::Type
  • File::Basename
  • #!/usr/bin/perl
    #
    #
    #
    # $Author: taco $
    # $Revision: 94 $
    # $Date: 2011-04-19 23:33:46 +0200 (Tue, 19 Apr 2011) $
     
    use Getopt::Long;
    use Pod::Usage;
     
    my $help        = '';
    my $man         = '';
    my $ver         = '';
    my $from        = '';
    my $to          = '';
    my $subject     = '';
    my $text        = '';
    my $filetype    = '';
    my $file        = '';
    my $filename    = '';
    my $smtp        = 'localhost';
     
    GetOptions ( 'help|h'           => \$help,
                 'man'              => \$man,
                 'version|V'        => \$ver,
                 'from|f=s'         => \$from,
                 'to|t=s'           => \$to,
                 'subject|s=s'      => \$subject,
                 'text|body=s'      => \$text,
                 'file=s'           => \$file,
                 'filetype|ft=s'    => \$filetype,
                 'filename|fn=s'    => \$filename,
                 'smtp=s'           => \$smtp
               );
     
    if(!-e "$file") { print "File not found: $file\n"; 
                   $help=1; 
                  }
    else          { $filetype = fType($file); }
    if(!$filename){ $filename = getFilename($file); }
    if(!$from)    { print "No from address set\n";
                    $help=1;
                  }
    if(!$to)      { print "No to address set\n";
                    $help=1;
                  }
    if(!$subject) { print "Subject is empty\n";
                    $help=1;
                  }
     
    if($help)     { pod2usage( -verbose =>  1) && exit;               }
    if($man)      { pod2usage({-verbose =>  2, -output => \*STDOUT}); }
     
    if(!$text)    { print "Enter your message below, ^d to end.\n";
                    while(<STDIN>) { $text.=$_; } }
     
    # for debugging only
    #print "$text";
    #print "mimetype = $filetype\n";
    #print "filename = $filename\n";
     
    use MIME::Lite;
    ### Create a new multipart message:
    $msg = MIME::Lite->new(
                 From    =>$from,
                 To      =>$to,
                 Subject =>$subject,
                 Type    =>'multipart/mixed'
                 );
     
    ### Add parts (each "attach" has same arguments as "new"):
    $msg->attach(Type     =>'TEXT',
                 Data     =>$text
                 );
    $msg->attach(Type     =>$filetype,
                 Path     =>$file,
                 Filename =>$filename,
                 Disposition => 'attachment'
                 );
     
    #$msg->print(\*STDOUT);
    $msg->send("smtp",$smtp);
     
    sub fType{
      my $file=shift;
      use File::Type;
      my $ft = File::Type->new();
      my $filetype = $ft->mime_type($file);
      return $filetype;
    }
     
    sub getFilename{
      my $file=shift;
      use File::Basename;
      return basename($file);
    }
     
    __END__
     
    =pod
     
    =head1 NAME 
     
    send-attachment - send attachments from command line
     
    =head1 SYNOPSIS
     
      send-attachment -from me@example.com \
                      -to you@example.com \
                      -subject "Some message" \
                      [-body "Hey, here is that file you wanted"] \
                      -file ./image.gif \
                      [-filename image.gif] \
                      [-filetype "image/gif"]
     
    =head1 OPTIONS
     
    =over 8
     
    =item B<-help> B<-h>
     
    displays a brief help message
     
    =item B<-man>
     
    displays the full manual
     
    =item B<-version> B<-V>
     
    displays version information
     
    =item B<-f(rom)> <from address>
     
    The 'sender' address of the message
     
    =item B<-t(o)> <to address>
     
    The recipient of the message
     
    =item B<-s(ubject)> "<subject>"
     
    Subject of the message
     
    =item B<-text|-body> "<body text of the message>"
     
    If this option isn't set STDIN will be used, this allows you to pipe text in to the command.
     
    =item B<-file> </path/to/file>
     
    File to attach to the message
     
    =item B<-ft -filetype> "<filetype>"
     
    Use only if the mimetype of the file can't be detected.
     
    =item B<-fn -filename> 
     
    Filename you want the attachment to have if different from it's current name
     
    =item B<-smtp>
     
    smtp server to use, defaults to localhost
     
    =back
     
    =head1 DESCRIPTION
     
    send-attachment is a perl script that uses MIME::Lite to send an email with an attachment from command line
     
    =head1 DEPENDENCIES
     
    This script depends on the following perl modules:
     
     Getopt::Long
     Pod::Usage
     MIME::Lite
     File::Type
     File::Basename
     
    =head1 EXAMPLES
     
      send-attachment -from me@example.com \
                      -to you@example.com \
                      -subject "Some message" \
                      -body "Hey, here is that file you wanted" \
                      -file ./image.gif \
                      -filename image.gif \
                      -filetype "image/gif"
     
    or
     
      (cat<<EOF
      Hey dude,
     
      This is the file you wanted.
     
      Cheers,
      Me
      EOF
      ) | send-attachment -from me@example.com \
                    -to you@example.com \
                    -subject "Some message" \
                    -file ./image.gif \
                    -filename image.gif \
                    -filetype "image/gif"
     
    =head1 VERSION
     
    0.4
     
    =head1 REVISION
     
    $Revision: 94 $
     
    =head1 AUTHOR
     
    Taco Scheltema <taco_at_scheltema.org>
     
    =cut

Redeliver messages from MBOX file

Published / by Taco Scheltema / Leave a Comment

On occasion I encounter a situation where, due to a configuration issue, mail was received on a server and stored in the default MBOX format instead of being delivered to a user account on a real mail server.

the following script will read an mbox file and redeliver them to the original recipient specified in the message or, if specified, a global recipient.

#!/usr/bin/perl
#
# Usage:
#         redeliver_mbox.pl <mboxfile> [<global TO: address>]
#
#         The global TO: address is optional, setting this will cause all
#         messages to be delivered to that address instead of the TO:
#         address definined in the message.
# Examples:
#         To deliver all messages to me@example.com:
#         #> redeliver_mbox.pl mbox.txt me@example.com
#         To deliver all messages to the recipients listed in the message itself
#         #> redeliver_mbox.pl mbox.txt
my $sendmail='/usr/sbin/sendmail';
my $file = shift || die "need file\n";
my $gto  = shift; # global to.  if present, override other per-email decision
my $msg  = '';
my $to   = '';
my $from = '';
 
open(I, "<$file") || die "Can't open $file\n";
while (<I>) {
  if (/^From /) {
    print "From found\n";
    if ($msg) {
      if ($to && $from) {
        do_mail($from, $to, $msg);
      } else {
        print STDERR "have a message but no recipients\n";
      }
    } else {
      print STDERR "saw From: without message\n";
    }
    $msg  = '';
    $from = '';
    $to   = '';
  } elsif (/^Return-[pP]ath:\s*<(.*)>$/) {
    $from = $1;
        #print "from=$from\n";
  } elsif (/^To:\s*(.*)$/i) {
    $to = $1;
        #print "to=$to\n";
  } elsif (/^Date:\s*/) {
    ; # just ignore
  } else {
    $msg .= $_;
  }
}
close(I);
 
if ($msg && $to && $from) {
  do_mail($from, $to, $msg);
}
 
sub do_mail {
  my $f = shift;
  my $t = shift;
  my $m = shift;
  $t = $gto if ($gto);
 
  print "$f -> $t\n";
  print "MAIL FROM:<$f>\nRCPT TO:<$t>\nDATA\n$m\n.\n";
  open(P, "|$sendmail -f $f $t") || warn "can't open sendmail: $!\n";
  print P $m, "\n.\n";
  close(P);
}

LDAPsearch sanitize output

Published / by Taco Scheltema / Leave a Comment

ldapsearch is a handy command line tool to query a ldap server, it does have some annoying quirks though;

  • The output is wrapped at 80 characters making it difficult to work with
  • Results containing utf8 characters are base64 encoded making it hard to read

An example of the output:

 

# extended LDIF
#
# LDAPv3
# base <ou=TestGroup,dc=example,dc=com> with scope subtree
# filter: (objectclass=groupOfNames)
# requesting: cn member
# with dereference control
#

# OC_USER, TestGroup, example.com
dn: cn=OC_USER,ou=TestGroup,dc=example,dc=com
member: cn=admin,dc=users,dc=example,dc=com
member: cn=nagios,dc=SYSTEM,dc=users,dc=example,dc=com
member: cn=Test User 1,ou=Developers ICT,dc=example,dc=com
member: cn=Test User 2,dc=users,dc=example,dc=com
member: cn=Test User 3,ou=Developers ICT,dc=example,dc=com
member: cn=John Jason Doe,ou=Project Management & Project Operations,dc=example
,dc=LOCAL
member:: Y249cmVuw6llLnBvaXLDqSxkYz11c2VycyxkYz1leHRlcm5hbCxkYz1MT0NBTA==
cn: OC_USER

# OC_ADMIN, TestGroup, example.com
dn: cn=OC_ADMIN,ou=TestGroup,dc=example,dc=com
cn: OC_ADMIN
member: cn=admin,dc=users,dc=example,dc=com
member: cn=Test User 1,ou=Developers ICT,dc=example,dc=com

# search result
search: 2
result: 0 Success

# numResponses: 3
# numEntries: 2

As you can see this can be hard to work with if you have to parse this further.

 

The following script will restore the wrapped lines and will decode the base64 encoded fields;

ldapsearch -h localhost -p 389 -D "CN=admin,DC=example,DC=com" -W -b "dc=example,dc=com" \
  -E 'deref=member:cn,uid' "(objectclass=groupOfNames)" cn member \
  | perl -MMIME::Base64 -n -00 -e 's/\n //g;s/(?<=:: )(\S+)/decode_base64($1)/eg;print'

This will produce the following:

# extended LDIF
#
# LDAPv3
# base <ou=TestGroup,dc=example,dc=com> with scope subtree
# filter: (objectclass=groupOfNames)
# requesting: cn member
# with dereference control
#

# OC_USER, TestGroup, example.com
dn: cn=OC_USER,ou=TestGroup,dc=example,dc=com
member: cn=admin,dc=users,dc=example,dc=com
member: cn=nagios,dc=SYSTEM,dc=users,dc=example,dc=com
member: cn=Test User 1,ou=Developers ICT,dc=example,dc=com
member: cn=Test User 2,dc=users,dc=example,dc=com
member: cn=Test User 3,ou=Developers ICT,dc=example,dc=com
member: cn=John Jason Doe,ou=Project Management & Project Operations,dc=example,dc=com
member:: cn=renée.poiré,dc=users,dc=example,dc=com
cn: OC_USER

# OC_ADMIN, TestGroup, example.com
dn: cn=OC_ADMIN,ou=TestGroup,dc=example,dc=com
cn: OC_ADMIN
member: cn=admin,dc=users,dc=example,dc=com
member: cn=Test User 1,ou=Developers ICT,dc=example,dc=com

# search result
search: 2
result: 0 Success

# numResponses: 3
# numEntries: 2