packages feed

mvclient-0.2: src/Network/Metaverse/Login.hs

{-# LANGUAGE TupleSections       #-}
{-# LANGUAGE ScopedTypeVariables #-}

{-|
    This module implements the XML-RPC based login protocol provided
    by Linden Labs' Second Life system.  Since Linden Labs' login requires
    SSL which is not widely implemented in Haskell, this requires an stunnel
    proxy listening on port 8001 of localhost.
-}
module Network.Metaverse.Login (
    MVToken(..),
    loginXml
    ) where

import Control.Monad
import Data.Char
import Data.Digest.MD5
import Data.Word
import Data.UUID
import Network.XmlRpc.Client
import Network.XmlRpc.Internals
import System.Info.MAC

{-
    Convert a list of bytes to a hexadecimal string; needed for building
    the login packet
-}

toHexString :: [Word8] -> String
toHexString = concatMap toHex
    where toHex n    = [ hexDigit (n `div` 16), hexDigit (n `mod` 16) ]
          hexDigit d | d < 10    = chr (ord '0' + fromIntegral d)
                     | otherwise = chr (ord 'a' + fromIntegral d - 10)

{-
    Login implementation.  This implements the pure XML-RPC login sequence
    for Second Life (but not the avatar presence piece, which is left until
    later.)
-}

hashPassword :: String -> String
hashPassword = ("$1$" ++) . toHexString . hash . map (fromIntegral . ord)

loginPacket :: String -> String -> String -> String -> Value
loginPacket firstName lastName password ethmac = ValueStruct [
    ("first",           ValueString firstName),
    ("last",            ValueString lastName),
    ("passwd",          ValueString (hashPassword password)),
    ("start",           ValueString "last"),
    ("channel",         ValueString "WillowdaleBot"),
    ("version",         ValueString "0.9"),
    ("platform",        ValueString "Lin"),
    ("mac",             ValueString ethmac),
    ("agree_to_tos",    ValueBool   True),
    ("read_critical",   ValueBool   True),
    ("id0",             ValueString ethmac),
    ("last_exec_event", ValueInt 0),
    ("options",         ValueArray [
                          ValueString "inventory-root",
                          ValueString "adult_compliant"
                        ])
    ]

data MVToken = MVToken {
    tokenAgentID     :: UUID,
    tokenSimIP       :: String,
    tokenSimPort     :: Word16,
    tokenSessionID   :: UUID,
    tokenCircuitCode :: Word32,
    tokenInvHost     :: String,
    tokenInvRoot     :: UUID
    }
    deriving Show

loginXml :: String -> String -> String -> IO MVToken
loginXml firstName lastName password = do
    maybemac <- mac
    case maybemac of
        Nothing     -> error "Unable to fetch Ethernet MAC"
        Just ethmac -> do
            let msg = loginPacket firstName lastName password (show ethmac)
            ValueStruct rsp <- handleError error
                               (call loginURL loginMethod [ msg ])
            let Just (ValueString loginAns)    = lookup "login"               rsp
            when (loginAns /= "true") $ error $ "Login did not succeed: " ++ loginAns
            let Just (ValueString agentID)     = lookup "agent_id"            rsp
            let Just (ValueString simIP)       = lookup "sim_ip"              rsp
            let Just (ValueInt    simPort)     = lookup "sim_port"            rsp
            let Just (ValueString sessionID)   = lookup "session_id"          rsp
            let Just (ValueInt    circuitCode) = lookup "circuit_code"        rsp
            let Just (ValueString invHost)     = lookup "inventory_host"      rsp
            let Just (ValueArray [ ValueStruct [ (_, ValueString invRoot) ] ])
                                               = lookup "inventory-root"      rsp
            return $ MVToken { tokenAgentID     = read (agentID),
                               tokenSimIP       = simIP,
                               tokenSimPort     = fromIntegral simPort,
                               tokenSessionID   = read (sessionID),
                               tokenCircuitCode = fromIntegral circuitCode,
                               tokenInvHost     = invHost,
                               tokenInvRoot     = read (invRoot)
                             }

  where loginURL    = "http://localhost:8001/cgi-bin/login.cgi" -- stunnel endpoint
        loginMethod = "login_to_simulator"