diff --git a/Copying b/Copying
new file mode 100644
--- /dev/null
+++ b/Copying
@@ -0,0 +1,19 @@
+Copyright (c) 2008 Stephen Cook
+
+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.
diff --git a/GeoIp.cabal b/GeoIp.cabal
new file mode 100644
--- /dev/null
+++ b/GeoIp.cabal
@@ -0,0 +1,25 @@
+Name:		GeoIp
+Version:	0.1
+License:	OtherLicense
+License-File: Copying
+Author:		Stephen Cook
+Maintainer: siti@orcon.net.nz
+category:	Network
+Synopsis:	Pure bindings for the MaxMind IP database.
+Stability:	alpha
+build-type: Simple
+Description:
+  GeoIp is a pure haskell binding to the MaxMind IP database.
+  The IP database contains information such as the approximate
+  longitude and latitude of an IP address.
+Cabal-Version: >= 1.2
+
+Library
+  Exposed-modules:
+    Network.GeoIp.GeoCityIp
+  Other-modules:
+    Network.GeoIp.GeoDB
+  ghc-options: -Wall -fglasgow-exts
+  Build-Depends: base, bytestring, bytestring-mmap
+
+
diff --git a/Network/GeoIp/GeoCityIp.hs b/Network/GeoIp/GeoCityIp.hs
new file mode 100644
--- /dev/null
+++ b/Network/GeoIp/GeoCityIp.hs
@@ -0,0 +1,73 @@
+
+-- | This module loads the MaxMind's GeoIp City database.
+module Network.GeoIp.GeoCityIp (
+	GeoCityDB,
+	load,
+	findRange,
+	findLocation) where
+
+import qualified Data.ByteString as B
+import Data.Int
+import Data.Bits
+
+import Network.GeoIp.GeoDB
+
+data GeoCityDB = GeoCityDB GeoDB
+
+-- | Load the city database.  If an error is triggered then
+--   Left is returned with an error string.
+load :: FilePath -> IO (Either String GeoCityDB)
+load geoFile = do
+	eGeo <- makeGeoDB geoFile
+	case eGeo of
+		Left e -> return $ Left e
+		Right geo ->
+			if dataBaseType geo == geoIPCountryEdition then
+				return $ Right (GeoCityDB geo)
+			else
+				return $ Left ("load: Incorrect database type.  Database type is: " ++ (show $ dataBaseType geo))
+
+extractRecordCity :: GeoDB -> Int -> (Double, Double)
+extractRecordCity geo cursor =
+	(latitude, longitude)
+	where
+		recordCursor = cursor + (2 * (recordLength geo) - 1) * (dataBaseSegments geo)
+		memo = B.drop recordCursor (mem geo)
+		(_, countryMem) = getBytes 1 memo
+		(_, regionMem) = extractNullString countryMem
+		(_, cityMem) = extractNullString regionMem
+		(_, postalMem) = extractNullString cityMem
+		(latitude, latMem) = ((fromIntegral ((getNumber $ B.take 3 postalMem)::Integer)) / 10000 - 180, B.drop 3 postalMem)
+		(longitude, _) = ((fromIntegral ((getNumber $ B.take 3 latMem)::Integer)) / 10000 - 180, B.drop 3 latMem)
+
+
+generateMask :: forall a. (Num a, Bits a) => Int -> Int -> a
+generateMask from to =
+	if from <= to then
+		(bit from) .|. generateMask (from+1) to
+	else
+		(bit from)
+
+-- | Find the IP range that the IP address is in.  The result is monadic.
+--   In most cases you will want to use the Maybe monad.
+findRange :: (Monad m) => GeoCityDB -> Integer -> m (Integer, Integer)
+findRange (GeoCityDB geo) address = do
+	(cursor, netMask) <- seekRecord geo address
+	let
+		bitMask = generateMask (31 - netMask) 31
+		hostMask = generateMask 0 netMask
+	if cursor == dataBaseSegments geo then
+		fail "Could not find IP"
+		else
+			return (address .&. bitMask, address .|. hostMask)			
+	
+-- | Find the location of an IP address. The tuple returned is @(latitude, longitude)@.
+--   The result is monadic, in most cases you will want to use the Maybe monad.
+findLocation :: (Monad m) => GeoCityDB -> Integer -> m (Double, Double)
+findLocation (GeoCityDB geo) address = do
+	(cursor, _) <- seekRecord geo address
+	if cursor == dataBaseSegments geo then
+		fail "Could not find IP"
+		else
+			return (extractRecordCity geo cursor)
+		
diff --git a/Network/GeoIp/GeoDB.hs b/Network/GeoIp/GeoDB.hs
new file mode 100644
--- /dev/null
+++ b/Network/GeoIp/GeoDB.hs
@@ -0,0 +1,122 @@
+module Network.GeoIp.GeoDB where
+
+import System.IO.Posix.MMap
+import qualified Data.ByteString as B
+import Data.Bits
+import Data.Generics
+
+data GeoDB = GeoDB {
+	mem :: B.ByteString,
+	dataBaseType :: Int,
+	recordLength :: Int,
+	dataBaseSegments :: Int
+} deriving (Data, Typeable, Show)
+
+makeGeoDB :: FilePath -> IO (Either String GeoDB)
+makeGeoDB geoFile = do
+	memo <- unsafeMMapFile geoFile
+	let
+		defaultGeo = GeoDB {mem = memo, dataBaseType = geoIPCountryEdition, recordLength = standardRecordLength, dataBaseSegments = countryBegin}
+		eGeo = setupSegments defaultGeo ((B.length memo) - 3) 0
+	return eGeo
+
+setupSegments :: GeoDB -> Int -> Int -> Either String GeoDB
+setupSegments geo cursor sizeRead
+	| sizeRead >= structureMaxSize = Left "SetupSegments: could not find magic bytes. Incorrect file?"
+	| otherwise = 
+		if B.index (mem geo) cursor == 255 && B.index (mem geo) (cursor+1) == 255 && B.index (mem geo) (cursor+2) == 255 then
+			let
+				byteDbType = B.index (mem geo) (cursor+3)
+				databaseType = if byteDbType >= 106 then (byteDbType - 105) else byteDbType
+			in
+				if databaseType == geoIPRegionEditionRev0 then
+					Right geo { dataBaseSegments = stateBeginRev0 }
+				else
+					if databaseType == geoIPRegionEditionRev1 then
+						Right geo { dataBaseSegments = stateBeginRev1 }
+					else
+						if (databaseType == geoIPCityEditionRev0 || databaseType == geoIPCityEditionRev1 || databaseType == geoIPOrgEdition || databaseType == geoIPIspEdition || databaseType == geoIPAsNumEdition) then
+							let newgeo = geo {dataBaseSegments = getNumber $ B.take segmentRecordLength $ B.drop (cursor+4) (mem geo)}
+							in
+								if (databaseType == geoIPOrgEdition || databaseType == geoIPIspEdition) then
+									Right newgeo {recordLength = orgRecordLength}
+								else
+									Right newgeo				
+						else
+							Right geo
+		else
+			setupSegments geo (cursor - 4) (sizeRead + 4)
+
+getNumber :: forall b. (Bits b) => B.ByteString -> b
+getNumber bytes = fst $ B.foldl combine (0, 0) bytes
+	where
+		combine (num, pos) b = (num + ((fromIntegral b) `shiftL` (pos*8)), pos + 1)
+
+extractNullString :: B.ByteString -> (B.ByteString, B.ByteString)
+extractNullString memo =
+	(memTaken, memLeft)
+	where
+		memTaken = B.takeWhile (/=0) memo
+		memLeft = B.drop ((B.length memTaken) + 1) memo
+
+getBytes :: Int -> B.ByteString -> (B.ByteString, B.ByteString)
+getBytes num memo = (B.take num memo, B.drop num memo)
+
+seekRecord :: forall a m. (Bits a, Monad m) => GeoDB -> a -> m (Int, Int)
+seekRecord geo ipNum = seekRecord_ geo 31 0 ipNum
+
+seekRecord_ :: forall a m. (Bits a, Monad m) => GeoDB -> Int -> Int -> a -> m (Int, Int)
+seekRecord_ geo depth offset ipNum
+	| depth < 0 = fail "Could not find record.  Database is possibly corrupt"
+	| otherwise =
+		if newOffset >= (dataBaseSegments geo) then
+			return (newOffset, 32 - depth)
+		else
+			seekRecord_ geo (depth - 1) newOffset ipNum
+		where
+			cursor = (recordLength geo) * 2 * offset
+			newOffset =
+				if (ipNum .&. (1 `shiftL` depth) /= 0) then
+					offsetFunction 1
+				else
+					offsetFunction 0
+			offsetFunction pos = getNumber (B.take (recordLength geo) $ B.drop (cursor + (pos * (recordLength geo))) (mem geo))
+
+geoIPCountryEdition :: forall n. (Num n) => n
+geoIPCountryEdition = 1
+geoIPRegionEditionRev0 :: forall n. (Num n) => n
+geoIPRegionEditionRev0 = 7
+geoIPCityEditionRev0 :: forall n. (Num n) => n
+geoIPCityEditionRev0 = 6
+geoIPOrgEdition :: forall n. (Num n) => n
+geoIPOrgEdition = 5
+geoIPIspEdition :: forall n. (Num n) => n
+geoIPIspEdition = 4
+geoIPCityEditionRev1 :: forall n. (Num n) => n
+geoIPCityEditionRev1 = 2
+geoIPRegionEditionRev1 :: forall n. (Num n) => n
+geoIPRegionEditionRev1 = 3
+--geoIPProxyEdition :: forall n. (Num n) => n
+--geoIPProxyEdition = 8
+geoIPAsNumEdition :: forall n. (Num n) => n
+geoIPAsNumEdition = 9
+--geoIPNetspeedEdition :: forall n. (Num n) => n
+--geoIPNetspeedEdition = 10
+--geoIPDomainEdition :: forall n. (Num n) => n
+--geoIPDomainEdition = 11
+structureMaxSize :: forall n. (Num n) => n
+structureMaxSize = 20
+segmentRecordLength :: forall n. (Num n) => n
+segmentRecordLength = 3
+standardRecordLength :: forall n. (Num n) => n
+standardRecordLength = 3
+orgRecordLength :: forall n. (Num n) => n
+orgRecordLength = 4
+
+stateBeginRev0 :: forall n. (Num n) => n
+stateBeginRev0 = 16700000
+stateBeginRev1 :: forall n. (Num n) => n
+stateBeginRev1 = 16000000
+countryBegin :: forall n. (Num n) => n
+countryBegin = 16776960
+
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMainWithHooks defaultUserHooks
diff --git a/examples/TestGeoIp.hs b/examples/TestGeoIp.hs
new file mode 100644
--- /dev/null
+++ b/examples/TestGeoIp.hs
@@ -0,0 +1,17 @@
+import Network.GeoIp.GeoCityIp
+
+main :: IO ()
+main = do
+	eGeo <- load "GeoLiteCity.dat"
+	case eGeo of
+		Left e -> do
+			putStrLn e
+		Right geo -> do
+			let
+				mRange :: Maybe (Integer, Integer)
+				mRange = findRange geo 3413704712
+				mLocation :: Maybe (Double, Double)
+				mLocation = findLocation geo 3413704712
+			putStrLn $ show mRange
+			putStrLn $ show mLocation
+			putStrLn "Data from c library in February 2008 is: 203.121.0.8 -> (3.166700, 101.699997)"
