#!/bin/sh
# maclookup.sh 0.2 by paraxor (Evan Teitelman)
# Looks up MAC addresses in the IEEE MA-L/OUI public listing.

# override with -f
oui_file=/usr/share/maclookup/oui.txt
# override with -a
additional_info=false
# override with -v
verbose=/dev/null
# override with -d
debug=/dev/null
# override with -u and -r
job=search
oui_url=http://standards-oui.ieee.org/oui.txt
version=0.2

err() {
	echo >&2 "$*"
}

die() {
	err "FATAL: $*"
	exit 1
}

usage() {
	echo "usage: `basename "$0"` [OPTIONS] <ADDRESSES|SEARCH>

OPTIONS
 -h  display this message
 -u  update the database
 -r  perform a reverse search (search by organization name)
 -R  generate a random MAC address based on a reverse search
 -a  display additional info (organization contact info)
 -v  be verbose
 -d  show debug info
 -f  set OUI listing file (default: $oui_file)

ADDRESSES
At least the first three bytes of an address must be present for a lookup. The
rest is ignored. All :, -, and . characters are ignored.

SEARCH
By passing the -r flag, you can perform a reverse search (search by
organization name). This search is case-insensitive.

AUTHOR
This was written by paraxor (Evan Teitelman).
Find me here: https://github.com/paraxor"
}

last_update_date() {
	head -n1 "$oui_file" | sed 's/Generated: //'
}

check_oui_file() {
	if [ ! -f "$oui_file" ] ; then
		die "'$oui_file' does not exist. try running maclookup -u"
	fi
	if [ ! -r "$oui_file" ] ; then
		die "cannot read '$oui_file'"
	fi
}

perform_search() {
	check_oui_file

	first=true
	for mac ; do
		clean_mac=`echo "$mac" | sed 's/[-:.]//g' | tr 'a-z' 'A-Z'`

		# validate address
		if ! echo "$clean_mac" | grep -Eq '^[0-9A-F]+$' ; then
			die "invalid MAC address: $mac"
		fi
		if [ `echo -n $clean_mac | wc -c` -lt 6 ] ; then
			die "we need at least the first three bytes to perform a lookup. '$mac' is too short."
		fi
		if [ `echo -n $clean_mac | wc -c` -gt 12 ] ; then
			die "'$mac' is too long to be a MAC address"
		fi

		# trim
		clean_mac=`echo "$clean_mac" | head -c6`

		if $additional_info ; then
			info=`sed -n "/^  $clean_mac/,/^$/p" "$oui_file" | sed 's/^.*\t//g'`
			if [ ! -z "$info" ] ; then
				$first || echo
				echo "$mac:"
				echo "$info"
			else
				err 'no addresses found'
			fi
		else
			info=`grep "^  $clean_mac" "$oui_file" | cut -d'	' -f3`
			if [ ! -z "$info" ] ; then
				echo "$mac $info"
			else
				err 'no addresses found'
			fi
		fi

		$first && first=false
	done
}

perform_reverse_search() {
	search=$*

	check_oui_file

	# fix front anchors
	search=`echo "$search" | sed 's/\^/		/'`

	if $additional_info ; then
		# this is gross
		info=`sed -n "/(hex).*$search/I,/^$/p" "$oui_file" |
		      sed '/(base 16)/d; s/   (hex)		/:\n/; s/	//g; s/^  //'`
		if [ ! -z "$info" ] ; then
			echo "$info"
		fi
	else
		info=`grep -i "(hex).*$search" "$oui_file" |
		      sed 's/   (hex)		/ /; s/^  //'`
		if [ ! -z "$info" ] ; then
			echo "$info"
		else
			err 'no organizations found'
		fi
	fi
}

perform_reverse_search_gen() {
	search=$*

	prefix=`perform_reverse_search "$search" | cut -d' ' -f1 |
	        sort -R | head -n1 | sed 's/-/:/g'`
	tail=`hexdump -Cn3 < /dev/urandom | head -n1 |
	      sed 's/[^ ]*  \([^ ]*\) \([^ ]*\) \([^ ]*\).*/\1:\2:\3/' |
	      tr 'a-z' 'A-Z'`
	echo "$prefix:$tail"
}

update_oui() {
	dir=`dirname "$oui_file"`
	mkdir -p $dir 2> $debug || die "failed to make '$dir'"

	if [ -f "$oui_file" ] ; then
		if [ ! -w "$oui_file" ] ; then
			die "cannot write to '$oui_file'"
		fi
	else
		touch "$oui_file" 2> $debug || die "cannot create '$oui_file'"
	fi

	curl -L "$oui_url" -o "$oui_file" \
	  2> $verbose
}

main() {
	if [ $# -eq 0 ] ; then
		usage
	fi

	while getopts hurRavdf: flag ; do
		case "$flag" in
			h) usage ;;
			u) job=update ;;
			r) job=reverse_search ;;
			R) job=reverse_search_gen ;;
			a) additional_info=true ;;
			v) verbose=/dev/stdout ;;
			d) debug=/dev/stdout; verbose=/dev/stdout ;;
			f) oui_file=$OPTARG ;;
			*) die "invalid option: $OPTARG" ;;
		esac
	done

	shift `expr $OPTIND - 1`

	if [ -z "$job" ] ; then
		usage
	fi

	case "$job" in
		search)             perform_search "$@" ;;
		reverse_search)     perform_reverse_search "$@" ;;
		reverse_search_gen) perform_reverse_search_gen "$@" ;;
		update)             update_oui ;;
	esac
}

main "$@"
