Viewing articles in programming

<-- Back to main

TinyUrl clone

Yesterday, while listening to lugradio, I heard Aq mention someone creating a tinyurl clone that allowed you to specify your own link "handle" to reference the url (tinyurl uses a random alphanumeric id). That got me thinking about the usefulness of having my own personal url aliasing service. I use del.icio.us to manage my bookmarks, but I can definitely see the use-case for named, shortcut bookmarks for things of particular interest.

My second thought was how trivial this would be to implement in django. Result: 44 lines of code and 75 lines of html later, and I have my own tinyurl service. You can see the working implementation here (disclaimer: don't assume it's remotely bug-free, secure, or well implemented.)

the model

from django.db import models

class Link(models.Model):
    handle = models.CharField(maxlength=255,unique=True)
    url = models.CharField(maxlength=255)

    def __str__(self):
        return self.handle

    class Admin:
        pass

The views

from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponseNotFound
from django.shortcuts import render_to_response, get_object_or_404
from django.newforms import form_for_model
from django.utils import simplejson
from jonallie.tinylink.models import Link

def index(request):   
 LinkForm = form_for_model(Link)
    message = ""
    if request.method == 'POST':
        form = LinkForm(request.POST)
        if form.is_valid():
            form.save()
            message = "Form saved: access your new link at http://jonallie.com/tinylink/go/%s" % form.data['handle']
            form = LinkForm()
        else:
            message = "Form NOT saved. Check errors below"
    else:
        form = LinkForm()
    return render_to_response('tinylink/index.html',{'form':form,'message':message})
            
def redirect(request,link_handle):   
    link = get_object_or_404(Link, handle=link_handle)
    return HttpResponseRedirect(link.url)
    
def check_handle(request,link_handle):  
    link = get_object_or_404(Link,handle=link_handle)
    return HttpResponse(simplejson.dumps({'handle':link.handle}), mimetype='application/javascript')

Permanent Link (0 comments)

Password expiration notification in python

I've seen several scripts to notify users of impending AD password expiration, but nearly all of them are vbs. Here's a script to accomplish the same goal using python-ldap.

#!/usr/bin/env python
"""
    password_notify.py:
        provides notification of active directory password expiration via email

"""

import sys
import time
import smtplib
import ldap
import syslog

ldaphost = 'HOST'
ldapbase = 'SEARCHBASE'
ldapfilter = '(&(objectCategory=person)(objectClass=user)(!(pwdLastSet=0)))'
ldapuser = 'USER'
ldappass = 'PASS'
mailhost = 'MAILHOST'
from_address = 'donotreply@YOURDOMAIN.COM'
expiry_days = 90
notify_days = 5 # days before expiration to start notification



msg_template = """\
To: %s
From: %s
Subject: Password Expiration Notification

This message is to notify you that your directory password will expire
%s . Please change it before that time using one of the following methods: 

1.) Reboot or logoff/logon your PC and follow the password change prompts.

2.) If you're a Webmail user, you can change your password from within webmail by choosing 
'Options' from the menu on the left, then clicking the 'Change Password' button 
near the bottom of the page. In the popup window that appears, type 'DOMAIN' (no quotes) 
into the field labled 'Domain', and populate the rest of the fields with your username,
old password, and new password twice.

3.) If you're currently logged into your PC _as yourself_, you can change your password
by pressing Ctrl-Alt-Del, and clicking the 'Change Password...' button

[ password guidelines here ]

You'll continue to receive this message each day until your password expires,
or until you reset it using one of the procedures above.

Thanks,

The Helpdesk
"""
def exit_with_error(errorstr, errno=2):
    """Print error to screen and exit"""
    print errorstr
    sys.exit(2)

def wintime_to_epoch(wintime):
    """Convert windows time stamps to epoch seconds"""
    return (int(wintime)-116444736000000000)/10000000 

def epoch_to_wintime(epoch_secs):
    """Convert unix epoc seconds to windows time units"""
    return (int(epoch_secs)*10000000)+116444736000000000
    
cur_win_time = epoch_to_wintime(time.time())

# explicitly open a syslog connection
syslog.openlog("password_notify.py")

# open a connection to the mail server
smail = smtplib.SMTP(mailhost)

# attempt to establish a connection to the ldap server
try:
    conn = ldap.open(ldaphost)
    conn.simple_bind_s(ldapuser, ldappass)
except:
    syslog.syslog("Error opening connection to %s" % ldaphost)
    exit_with_error("Ldap connection error") 

# grab the list of "person" objects from AD
try:
    users = conn.search_st(ldapbase, ldap.SCOPE_SUBTREE, ldapfilter)
except: 
    syslog.syslog("Error retreiving userlist using filter: %s" % ldapfilter)
    exit_with_error("LDAP search error")
    

for user in users:
    try:
        name = user[1]['cn'][0]
        mail = user[1]['mail'][0]
        last_set = long(user[1]['pwdLastSet'][0])
        expires_win = last_set + expiry_days*24*60*60*10000000
        notify_win = expires_win - notify_days*24*60*60*10000000
    except:
        continue 
    if cur_win_time > notify_win and cur_win_time < expires_win:
        syslog.syslog("Sending notification to: %s" % mail)
        smail.sendmail(from_address,mail,msg_template % (mail, from_address,time.ctime(wintime_to_epoch(expires_win))))

sys.exit(0)

Permanent Link (0 comments)

Better net-bridge script

I recently set up an openvpn server on Ubuntu LTS using bridged networking and was really dissatisfied with the sample scripts for bringing the bridge adapter up/down. The script below is what I came up with instead:

    # startup script to establish bridged network
    # based on sambple scripts from openvpn
    
    function exit_with_error {
        echo $*
        exit 2
    }
    
    # bridged interface
    br="br0"
    
    # tap interface
    tap="tap0"
    
    # physical address
    eth="eth0"
    eth_addr="172.16.1.28"
    eth_mask="255.255.0.0"
    eth_cast="172.16.255.255"
    eth_gw="172.16.32.2"
    
    case "$1" in
        "start")
            echo "Starting bridged networking"
            
            # create tunnel
            /usr/sbin/openvpn --mktun --dev $tap
            [ "$?" != "0" ] && exit_with_error "Failed to create tunnel"
            
            /usr/sbin/brctl addbr $br
            /usr/sbin/brctl addif $br $eth
            /usr/sbin/brctl addif $br $tap
            [ "$?" != "0" ] && exit_with_error "Failed to create bridge"
            
            /sbin/ifconfig $tap 0.0.0.0 promisc up || exit_with_error "Failed to configure $tap"
            /sbin/ifconfig $eth 0.0.0.0 promisc up || exit_with_error "Falied to configure $eth"
            /sbin/ifconfig $br $eth_addr netmask $eth_mask broadcast $eth_cast || exit_with_error "Failed to configure $br"
            #/sbin/ifconfig $br up
            
            # add default route
            /sbin/route add -net default gw $eth_gw
    
        # create iptables rules
        /sbin/iptables -A INPUT -i $br -j ACCEPT
        /sbin/iptables -A INPUT -i $tap -j ACCEPT
        /sbin/iptables -A FORWARD -i $br -j ACCEPT
        /sbin/iptables -A FORWARD -i $tap -j ACCEPT
            ;;
            
        "stop")
            echo "Stopping bridged networking"
            
            # bring down bridge
            /sbin/ifconfig $br down
            
            # tear down bridge
            /usr/sbin/brctl delbr $br
            /usr/sbin/openvpn --rmtun --dev $tap
            
            # reset address of eth0
            /sbin/ifconfig $eth $eth_addr netmask $eth_mask broadcast $eth_cast
            #/sbin/ifconfig $eth up
            /sbin/route add -net default gw $eth_gw
    
        # flush iptables rules
        /sbin/iptables -F INPUT
        /sbin/iptables -F FORWARD
            ;;
            
        *)
            echo "Usage: $0 {start|stop}" >&2
            exit 1
            ;;
    esac
    
    exit 0

Permanent Link (0 comments)

MySQL Backup script with encryption

I recently rewrote the script that handles our mysql backups, and thought someone else might find it useful. It includes support for optional bzip compression of the dump files, and optional encryption using openssl. It's available for download here or you can see it inline after the jump

        #!/bin/bash
        ##################################################################
        # sqlbackup.sh:
        #   rolling sqlbackups using mysqldump
        #
        #
        #   This script will dump mysql databases to a specified directory
        #   with optional encrytion.
        #
        #
        ###################################################################
    
        # program options
        backup_dir=/backup
        backup_age=28
        use_encryption=0
        use_bzip=0
        nice_level=19
        mysql_user=root
        mysql_passfile=/etc/keys/.mysqlpass
        openssl_passfile=/etc/keys/.opensslpass
        date=`date +%Y%m%d`
        exten=".sql"
        logfile=/var/log/mysqlbackup.log
        mysqldump_options=" --all-databases --single-transaction --quote-names "
    
        # program paths
        mysqldump=/usr/bin/mysqldump
        openssl=/usr/bin/openssl
        bzip2=/usr/bin/bzip2
    
    
        # usage summary
        function usage {
        echo "
        sqlbackup.sh:
            creates backups of sql databases using the mysqldump command,
            including optional encryption, bzip2 compression, and 
            variable backup aging
    
        Usage: sqlbackup.sh [OPTIONS]
    
            Options:
    
            -h              : display this help
    
            -b <dir>        : select directory to store backups. This directory 
                            will not be created.
    
            -e              : enable encryption using openssl's des3 encryption. 
    
            -k <file>       : specify file containing password to be used for encryption
                            This defaults to /etc/keys/.opensslpass
                    
            -u <user>       : username to use for connecting to MySQL
    
            -p </user></file><file>       : specifies a file containing a password to be used for
                            connecting to MySQL
        
            -z              : enable bzip2 compression of backups
    
            -a <days>       : backup age in days. Backups older than this will be deleted
    
            -n <level>      : Specifies a nice level. This level will apply to all operations of the 
                            backup. Defaults to 19 (very nice)
                    
            -l </level></days></file><file>       : logfile path. Alternate path to a file for logging. Defaults
                            to '/var/log/mysqlbackup.log'
                    
        "
        }      
    
        # echo a string to screen and log
        function echo_and_log {
            logstring="[ `date` ] : $*"
            echo $logstring
            echo $logstring >> $logfile
        }
    
    
        # print error to screen and exit
        function exit_with_error {
            echo_and_log "Error: $*"
            echo_and_log "Exiting with code 1"
            exit 1
        }
    
        # funtion to delete old backups
        function clean_backups {
        find $backup_dir -type f -mtime +${backup_age} -exec rm -f {} \;
        [ "$?" == "0" ] || echo_and_log "Error encountered when cleaning old backups from $backup_dir"
        }
    
    
        # process command line options
        while getopts ":hb:ek:u:p:za:n:l:" opt 
        do
            case $opt in
                b) backup_dir=$OPTARG ;;
                e) use_encryption=1 ;;
                k) openssl_passfile=$OPTARG ;;
                u) mysql_user=$OPTARG ;;
                p) mysql_passfile=$OPTARG ;;
                z) use_bzip=1 ;;
                a) backup_age=$OPTARG ;;
                n) nice_level=$OPTARG ;;
                l) logfile=$OPTARG ;;
                h) usage 
                    exit 0 ;;
                \?) echo "unknown option"
                    usage 
                    exit 1 ;;
                *)  usage
                    exit 1 ;;
            esac
        done
    
        # set umask
        umask 0077
    
        # start processing
        echo_and_log "Starting MySQL backup: encryption = $use_encryption, bzip = $use_bzip, backup directory = $backup_dir"
        
        # error checking and validation
        [ -d $backup_dir ] || exit_with_error "Backup directory '$backup_dir' does not exist"
        [ -n "$logfile" ] || exit_with_error "No log file specified."
        [ -f $mysql_passfile ] || exit_with_error "MySQL password file '$mysql_passfile' does not exist"
        [ -x $mysqldump ] || exit_with_error "MySQLDump program at '$mysqldump' does not exist or is not executable"
        # validate compression options
        if [ "$use_bzip" == "1" ]
        then
            [ -x $bzip2 ] || exit_with_error "Bzip2 program at '$bzip2' does not exist or is not executable"
            bzip2_command=" | nice -n $nice_level $bzip2 "
            exten="${exten}.bz2"
        fi
    
        # validate encryption options
        if [ "$use_encryption" == "1" ]
        then 
            [ -x $openssl ] || exit_with_error "OpenSSL program at '$openssl' does not exist or is not executable"
            [ -f $openssl_passfile ] || exit_with_error "OpenSSL password file '$openssl_passfile' does not exist"
            openssl_command=" | nice -n $nice_level $openssl des3 -salt -pass file:${openssl_passfile} "
            exten="${exten}.cryp"
        fi
    
        # call funtion to clean backup directory
        clean_backups
    
        # execute dump command
        command="$mysqldump $mysqldump_options --user=${mysql_user} --password=$(<$mysql_passfile) $bzip2_command $openssl_command > $backup_dir/mysqlbackup_${date}${exten}"
        eval $command
        [ "$?" == "0" ] || exit_with_error "MySQLDump command returned non-zero error code. Backup not completed successfully"
    
        # finish
        echo_and_log "Backup complete"
        exit 0
    
</file></dir>

Permanent Link (0 comments)

Joel Spolsky on What Design is All About

Brilliant article from Joel Spolsky (of "Joel on Software" fame) about what 'design' actually is; what makes one design better than another; and why design is the most important thing to get right in a product.

Great Design

Permanent Link (0 comments)