#!/usr/bin/python

#
# Bonmap, Bonjour Cartographer
#
# Bonmap uses avahi-browse command line tool to find out about services
# running on the local network. Avahi is an Apple bonjour implementation
# which is ran as a background service on some systems to maintain
# up-to-date database about locally available services. As Bonmap gets it's
# data from Avahi, it does not produce any network traffic, other than the
# regular Avahi traffic. Bonmap produces Nmap xml output files that can be
# opened in Zenmap for inspection.
#
# Usage Example:
#
# bonmap > mylan.xml
# zenmap mylan.xml
#
#
# Copyright (c) 2008 the authors.
#
# Authors: Toni Ruottu <toni.ruottu () iki fi>
#

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import commands
import sys
import time

def xmlport(port):
   return '<port portid="%s"><state state="open" /></port>' % port

def xmlhost(findings):
  hhead_t = '''<host><status state="up" reason="bonjour-advertisements"/>
<address addr="%s" addrtype="%s" />
<hostnames />
<ports>
'''
  hfoot = '''
</ports>
</host>
''' 

  address = (findings[0]['address'][0])
  family = 'ipv4'
  if ':' in address:
    family = 'ipv6'
  hhead = hhead_t % (address, family)


  ports = set([f['port'][0] for f in findings])

  return hhead + '\n'.join([xmlport(p) for p in ports]) + hfoot

def range_to_string(r):
  (s, e) = r
  if s == e:
    return str(s)
  return '%s-%s' % r
  

def head(findings, start):
  head_t = '''<?xml version="1.0" ?>
<nmaprun scanner="bonmap" args="bonmap" start="%s" version="%s" xmloutputversion="1.02">
<scaninfo type="bonjour-discovery" numservices="%s" services="%s" />
'''
  ports = sorted(set([int(h['port'][0]) for h in findings]))

  portcount = len(ports)

  ranges = []
  range = []

  previous = None
  for p in ports:
    if previous:
      if p > (previous + 1):
        ranges.append(range)
        range = []
    previous = p
    range.append(p)
  ranges.append(range)

  parts = [ range_to_string((min(r), max(r))) for r in ranges]

  head = head_t % (start, version, portcount, ','.join(parts))

  return head

def xmlify(findings, start, version):

  foot = '''<runstats><hosts up="%s" down="0" total="%s" />
</runstats></nmaprun>''' % (len(findings), len(findings))

  hosts = {}
  for f in findings:
    hosts[f['address'][0]] = []
      
  for f in findings:
    hosts[f['address'][0]] += [f]


  return head(findings, start) + ''.join([xmlhost(h) for h in hosts.values()]) + foot

def getItems(r):
  x = r.split('=')
  if len(x) != 2 or not x[0]:
    return []
  (k, v) = x

  i = (k.strip(), v.strip()[1:-1].split(','))
  return [i]

start = str(int(time.time()))
version = 0.01

ao = commands.getstatusoutput('avahi-browse -a -v -t -r')[1]

rows = ao.split('\n')

findings = []
fields = []

for r in rows:
  if r.startswith('=') and fields:
    findings.append(dict(fields))
    fields = []

  fields += getItems(r)
findings.append(dict(fields))

print xmlify(findings, start, version)

