Monthly Archives: January 2017

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