packages feed

ginsu-0.8.2: Gale/Gale.hs

{-# LANGUAGE OverlappingInstances, FlexibleInstances, PatternGuards, ScopedTypeVariables #-}
module Gale.Gale(
    GaleContext,
    galeNextPuff,
    reconnectGaleContext,
    connectionStatus,
    galeSendPuff,
    hostStrings,
    galeWillPuff,
    withGale,
    galeSetProxys,
    galeAddCategories,
    verifyDestinations,
    gCategory,
    keyCache,
    getGaleDir) where


import Data.Char(chr,ord)
import System.IO
import Data.List
import Data.Maybe
import System.Time
import System.Timeout (timeout)

import Control.Concurrent
import Control.Concurrent.Async (async, wait)
import Control.Exception as E
import Data.Bits
import Network.BSD
import Network.Socket
import PackedString
import Data.Binary
import Data.Binary.Get
import Data.Binary.Put
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString as BS
import Control.Applicative ((<|>))

import Atom
import Control.Monad.Error (when, replicateM)
import Data.Array.IO
import Data.Monoid
import EIO
import ErrorLog
import Gale.Proto
import Gale.KeyCache
import Gale.Puff
import GenUtil hiding(replicateM)
import qualified System.Posix as Posix
import RSA
import SimpleParser

-- TODO - prove concurrent-correctness, make sure all network errors are accounted for.

-------------------
-- Gale Constants
-------------------

galePort :: PortNumber
galePort = 11512
hostStrings s = [s, "gale." ++ s, s ++ ".gale.org."]


type PuffStatus = ()

data GaleContext = GaleContext {
    connectionStatus :: !(MVar (Either String String)),
    channel :: !(Chan Puff),
    proxy :: !(MVar [String]),
    gThread :: ThreadId,
    gHandle :: !(MVar Handle),
    gCategory :: !(MVar [Category]),
    keyCache :: !KeyCache
    }

void a = a >> return ()




-----------------
-- Implementation
-----------------

withGale :: [String] -> (GaleContext -> IO a) -> IO a
withGale ps io = withSocketsDo $ do
    Posix.installHandler Posix.sigPIPE Posix.Ignore Nothing
    bracket (newGaleContext ps []) destroyGaleContext io
    --gc <- newGaleContext ps []
    --r- <- io gc
    --destroyGaleContext gc
    --return r

newGaleContext ps cs = do
    let ncs = map catParseNew cs
    cats <- newMVar $ ncs
    c <- newChan
    ps <- return (case ps of [] -> snub (concatMap (hostStrings . categoryCell) ncs); _ -> ps)
    status <- newMVar $ Left $ "Attempting to connect to: " ++ unwords ps
    pmv <- newMVar ps
    hv <- newEmptyMVar
    --keycachev <- newMVar []
    --pkcache <- newMVar emptyFM
    galeDir <- getGaleDir
    keyCache <- newKeyCache galeDir
    let gc = GaleContext { connectionStatus = status, gThread = undefined, gHandle = hv, gCategory = cats, channel = c, proxy = pmv, keyCache = keyCache {- keyCache = keycachev, publicKeyCache = pkcache -} }
    thd <- forkIO (connectThread gc ps hv)
    sendGimme gc
    return gc { gThread = thd }

galeAddCategories :: GaleContext -> [Category] -> IO ()
galeAddCategories gc cs = do
    action <- modifyMVar (gCategory gc) $ \cs' ->
        let ncs = snub (cs ++ cs') in
         if ncs == cs' then return (ncs,return ()) else return (ncs,sendGimme gc)
    action
    --sendGimme gc

galeSetProxys :: GaleContext -> [String] -> IO ()
galeSetProxys gc ps = do
    modifyMVar_ (proxy gc) $ \_ -> return (snub ps)
    sendGimme gc

sendGimme :: GaleContext -> IO ()
sendGimme gc = void $ forkIO $ do
    withMVar (gHandle gc) $ \h -> do
        ncs <- readMVar $ gCategory gc
        putLog LogDebug $ "sendGimme:" ++ (show $ ncs)
        let gs = concatInter ":" (map catShowOld ncs)
        putWord32 h 2
        putWord32 h (fromIntegral $ length gs * 2)
        putRaw h $ galeEncodeString gs
        hFlush h

destroyGaleContext gc = killThread $ gThread gc

connectTo hostname port = do
    --proto <- getProtocolNumber "tcp"
    bracketOnError
        (socket AF_INET Stream 6)
        (sClose)  -- only done if there's an error
        (\sock -> do
                he <- getHostByName hostname
                connect sock (SockAddrInet port (hostAddress he))
                socketToHandle sock ReadWriteMode
        )



attemptConnect s = do
    h <- connectTo s galePort
    --hSetBuffering h NoBuffering
    return (h,s)

spc [] = []
spc s = v : spc (drop 1 r) where
    (v,r) = span (/= ':') s

emptyPuffer :: MVar Handle -> IO ()
emptyPuffer hv = repeatM_ (threadDelay 30000000 >> sendEmptyPuff) where
        sendEmptyPuff = withMVar hv $ \h -> do
                putWord32 h 0
                putWord32 h 8
                putWord32 h 0
                putWord32 h 0
                hFlush h

connectThread :: GaleContext ->  [String] -> MVar Handle -> IO ()
connectThread gc _ hv = retryIO 5.0 ("ConnectionError") doit where
    openHandle = do
        ds <- readMVar $ proxy gc
        swapMVar (connectionStatus gc)  $ Left $ "Attempting to connect to: " ++ unwords ds
        trySeveral (map attemptConnect ds)
    doit = bracket openHandle (hClose . fst) $ \(h,hn) -> do
        putWord32 h 1
        _ <- readWord32 h  -- version
        sendGimme gc
        swapMVar (connectionStatus gc) $ Right hn
        bracket_ (putMVar hv h) (takeMVar hv) $
          bracket (forkIO (emptyPuffer hv)) killThread $ \_ -> repeatM_ $ do
            w <- readWord32 h
            l <- readWord32 h
            bs <- LBS.hGet h (fromIntegral l)
            when (w == 0) $ do
                let hash = sha1 bs
                    (catl,puff) = runGet decodePuff bs
                    cat = mapMaybe parseCategoryOld (spc catl)
                ct <- getClockTime
                let ef = \xs -> ((fromString "_ginsu.timestamp",FragmentTime ct):(fromString "_ginsu.spumbuster", FragmentText (packString (bsToHex hash))):xs)
                p' <- galeDecryptPuff gc Puff { signature = [], cats = cat, fragments = ef puff}
                np <- case [(kh, k, data_, sig)
                           | RequestingKey kh k data_ sig <- signature p'] of
                      [] -> return p'
                      (kh, k, data_, sig):_ -> do
                        res <- timeout 4000000 $ wait kh
                        let unver = p' { signature = [Unverifyable k] }
                        case res of
                          Just x -> case x of
                            DestEncrypted (k':_) -> do
                              mkey <- verifySignature k' data_ sig
                              return $ p' { signature = [mkey] }
                            _ -> return unver
                          Nothing -> return unver
                writeChan (channel gc) np
                case getFragmentData np f_answerKey' of
                    Just d -> putKey (keyCache gc) d
                    Nothing -> return ()
                case (cats np,getFragmentString np f_answerKeyError') of
                    ([Category (n,d)],Just _) | "_gale.key." `isPrefixOf` n -> noKey (keyCache gc) (catShowNew $ Category (drop 10 n,d))
                    (_,_) -> return ()
                maybeReplyToKeyQuery gc np

maybeReplyToKeyQuery :: GaleContext -> Puff -> IO ()
maybeReplyToKeyQuery gc p | Just kn <- getFragmentString p f_questionKey = do
  let kn' = unpackPS kn
  mkb <- getPubKeyBytes (keyCache gc) kn'
  d <- createPuff gc False $ keyResponsePuff mkb kn'
  putLog LogDebug $ "sending reply for: " ++ kn'
  retryIO 3.0 "error sending puff" $ withMVar (gHandle gc) $ \h -> LBS.hPut h d >> hFlush h
maybeReplyToKeyQuery _ _ = return ()

decodePuff :: Get (String,FragmentList)
decodePuff = do
    clen <- getWord32be
    cs <- replicateM (fromIntegral (clen `div` 2)) getWord16be
    --getWord32be
    skip 4
    fl <- decodeFrags
    return (map (chr . fromIntegral) cs, fl)


galeNextPuff :: GaleContext -> IO Puff
galeNextPuff gc = do
    p <- readChan $ channel gc
    --p' <- galeDecryptPuff gc p
    --case getFragmentData p' f_answerKey' of
    --    Just d -> putKey (keyCache gc) d
    --    Nothing -> return ()
    putLog LogDebug $ "Puff gotten: \n" ++ (indent 4 $ showPuff p)
    return p

reconnectGaleContext gc = do
    -- p <- readMVar $ proxy gc
    void $ forkIO $ attemptIO $ readMVar (gHandle gc) >>= hClose


galeSendPuff :: GaleContext -> Puff -> IO PuffStatus
galeSendPuff gc puff = void $ forkIO $ do
    putLog LogInfo $ "sending puff:\n" ++ (indent 4 $ showPuff puff)
    puff' <- expandEncryptionList gc puff
    writeChan (channel gc) puff'
    d <- createPuff  gc False puff'
    retryIO 3.0 "error sending puff" $ withMVar (gHandle gc) $ \h -> LBS.hPut h d >> hFlush h

galeWillPuff :: GaleContext -> Puff -> IO ()
galeWillPuff gc puff = void $ forkIO $ do
    putLog LogDebug $ "willing puff:\n" ++ (indent 4 $ showPuff puff)
    d <- createPuff gc True puff
    retryIO 3.0 "error sending puff" $ withMVar (gHandle gc) $ \h -> LBS.hPut h d >> hFlush h


getPrivateKey kc kn = getPKey kc kn >>= \n -> case n of
    Just (k,_) | not $ keyIsPrivKey k -> return Nothing
    o -> return o

collectSigs :: [Signature] -> ([String],[String])
collectSigs ss = liftT2 (snub, snub) $ cs ss ([],[]) where
    cs sig x@(ks,es) = case sig of
      (RequestingKey _ _ _ _):_ -> lose
      (Unverifyable _):_ -> lose
      (Signed (Key k _)):ss -> cs ss (k:ks,es)
      (Encrypted es':ss) -> cs ss (ks,es' ++ es)
      [] -> x
    lose = error "attempt to create unverifiable puff"

tagWord :: Int -> Put -> Put
tagWord i p = putWord32be (fromIntegral i) >> p

class RawPut a where
    rawPut :: a -> Put

instance RawPut BS.ByteString where
    rawPut = putByteString

instance RawPut LBS.ByteString where
    rawPut = putLazyByteString

instance RawPut [Word8] where
    rawPut = putByteString . BS.pack

runPutBS = BS.concat . LBS.toChunks . runPut

createPuff :: GaleContext -> Bool -> Puff -> IO LBS.ByteString
createPuff _ will puff | [] <- signature puff = do
    let cn = runPut $ putGaleString (concatInter ":" (map catShowOld $ cats puff))
    let ad = runPut $ tagWord (fromIntegral $ LBS.length cn) $ do putLazyByteString cn; tagWord 0 $ putFragments (fragments puff)
    let pd = tagWord (if will then 1 else 0) (tagWord (fromIntegral $ LBS.length ad) (putLazyByteString ad))
    evaluate $ runPut pd
createPuff gc will p | (kn:_,es) <-  collectSigs (signature p) = do
    getPrivateKey (keyCache gc) kn >>= \v -> case v of
        Nothing -> createPuff gc will $ p {signature = []}
        Just (Key _ kfl,pkey) -> do
            sfl <- case fragmentString f_keyOwner kfl of
                Just o -> return [(f_messageSender, FragmentText o)]
                Nothing -> return []
            let fl = runPutBS $ tagWord 0 (putFragments (fragments p `mergeFrags` sfl))
            sig <- signAll pkey fl
            let sd = runPutBS $ do
                    putByteString bs_signature_magic1
                    tagWord (BS.length sig) (putByteString sig)
                    putByteString (BS.pack pubkey_magic3)
                    tagWord (fromIntegral $ length kn) (putGaleString kn)
                fd = tagWord (BS.length sd) (putByteString sd) >> putByteString fl
                fragments = [(f_securitySignature,FragmentData (runPutBS fd))]
            nfragments <- cryptFragments gc es fragments
            createPuff gc will $ p {signature = [], fragments = nfragments }
createPuff _ _ _ = error "createPuff: invalid arguments"

cryptFragments :: GaleContext -> [String] -> FragmentList -> IO FragmentList
cryptFragments _ [] fl = return fl
cryptFragments gc ss fl = do
    putLog LogDebug $ "cryptFragments " ++ show ss
    ks <-  mapM (getPKey (keyCache gc)) ss
    let ks' = [ (n,x) | Just (Key n _,x) <- ks]
        fl' = putWord32be 0 >> (putFragments fl)
        n = fromIntegral (length ks')
    --putStrLn $ show (ks,ks',fl')
    (d,ks,iv) <- encryptAll (snds ks') (BS.concat (LBS.toChunks $ runPut fl'))
    return [(f_securityEncryption,FragmentData . BS.concat $ [bs_cipher_magic2,iv] ++ LBS.toChunks (runPut $ do putWord32be n;  mapM_ g (zip (fsts ks') ks) ; putByteString d))]
  where
     g (kn,kd) = do
        putWord32be (fromIntegral $ length kn)
        putGaleString kn
        putWord32be (fromIntegral $ BS.length kd)
        putByteString kd



expandEncryptionList :: GaleContext -> Puff -> IO Puff
expandEncryptionList gc p = do
    ks <- fmap (normalizeDest . mconcat) $ mapM (findDest gc ) (cats p)
    case ks of
        DestPublic -> return p
        DestUnknown _ -> return p
        DestEncrypted ks -> return p { signature = Encrypted [ n | k@(Key n _) <- ks, keyIsPubKey k ]: signature p }

--    if any (maybe True (keyIsPublic ) ) (snds ks) then return p else do
--        return p { signature = Encrypted [ n | Just k@(Key n _) <-  snds ks, keyIsPubKey k ]: signature p }

putGaleString :: String -> Put
putGaleString s = putByteString . BS.pack $ galeEncodeString s

putFragments :: FragmentList -> Put
putFragments fl = mapM_ f fl where
    f (s',f) = putWord32be t >> putWord32be (fromIntegral $ LBS.length nxs) >> putLazyByteString nxs  where
        nxs = runPut (n >> xs)
        (t, xs) = g f
        n = putWord32be (fromIntegral $ length s) >> (putGaleString s)
        s = toString s'
    g (FragmentData ws) = (1, putByteString ws)
    g (FragmentText s) = (0, putGaleString (unpackPS s))
    g (FragmentTime (TOD s _)) = (2, putWord64be (fromIntegral s) >> putWord64be 0)
    g (FragmentInt i) = (3, putWord32be (fromIntegral i))
    g (FragmentNest fl) = (4, putFragments fl)



catfixes = [ ("/", ".|"), (".", "/"), (":", "..") ]

parseCategoryOld :: String -> Maybe Category
parseCategoryOld = parser p where
    con cs | [nv] <- [x ++ (con $ drop (length y) cs) |(x,y) <- catfixes, y `isPrefixOf` cs] = nv
    con (c:cs) = c:con cs
    con "" = ""
    bl [] = []
    bl [_] = []
    bl (x:xs) = x:bl xs
    p = do
        char '@'
        d <- many (noneOf "/")
        parseExact "/user/"
        c <- parseRest
        return (Category (con (bl c),d))

catShowOld :: Category -> String
catShowOld (Category (c,d)) = "@" ++ d ++ "/user/" ++ con c ++ "/" where
    con cs | [nv] <- [x ++ (con $ drop (length y) cs) |(y,x) <- catfixes, y `isPrefixOf` cs] = nv
    con (c:cs) = c:con cs
    con "" = ""


--------------------
-- Security routines
--------------------

verifySignature :: Key -> BS.ByteString -> BS.ByteString -> IO Signature
verifySignature k data_ sig = do
  pkey <- keyToPkey k
  rv <- verifyAll pkey data_ sig
  return $ (if rv then Signed else Unverifyable) k

galeDecryptPuff :: GaleContext -> Puff -> IO Puff
galeDecryptPuff gc p = handle (\(_ :: IOException) -> return p) $ galeDecryptPuff' gc p
galeDecryptPuff' gc p | (Just xs) <- getFragmentData p f_securitySignature = do
    let (l,xs') = xdrReadUInt (BS.unpack xs)
        (sb,xs'') = xdrReadUInt (drop 4 xs')
        fl = (decodeFragments $ drop (fromIntegral l + 4) xs') ++ [f | f <- fragments p, fst f /= f_securitySignature]
        sigoff = 12 -- skip length hdr (4), sig magic (4), sig len (4)
        siglen = fromIntegral sb
        keylen = fromIntegral l - (8 + siglen)
        keyoff = sigoff + siglen -- key directly follows sig
        sig = BS.take siglen $ BS.drop sigoff xs
        data_ = BS.drop (keyoff + keylen) xs
    key <- maybe (fail "parseKey") return $ parseKey $ take keylen (drop siglen xs'')
    let (Key kn _) = key
    kgc <- getKey (keyCache gc) kn
    mkey <- case kgc of
      Nothing -> do
        let senderc = catParseNew kn
        h <- async (findDest gc senderc)
        return $ RequestingKey h key data_ sig
      Just k -> verifySignature k data_ sig
    galeDecryptPuff' gc $ p {signature = mkey: signature p, fragments = fl}
galeDecryptPuff' gc p | (Just xs) <- getFragmentData p f_securityEncryption = do
    (cd,ks) <- maybe (fail "parseKey") return $ parser pe (BS.unpack xs)
    dfl <- firstIO (map (td' (BS.pack cd)) [ (BS.pack x,y,BS.pack z) | (x,y,z) <- ks])
    let dfl' = dfl ++ [f | f <- fragments p, fst f /= f_securityEncryption]
    galeDecryptPuff' gc $ p {signature = (Encrypted (map (\(_,n,_) -> n) ks)): signature p, fragments = dfl'}  where
        pe = (parseExact cipher_magic1 >> pr parseNullString) <|> (parseExact cipher_magic2 >> pr parseLenString)
        pk pkname iv = do
            kname <- pkname
            keydata <- parseLenData
            return $ (iv,kname,keydata)
        pr pkname = do
            iv <- parseSome 8
            keycount <-  parseIntegral32
            ks <- replicateM keycount (pk pkname iv)
            xs <- parseRest
            return (xs,ks)
        td' cd (iv,kname,keydata) = do
            Just (_,pkey) <- getPrivateKey (keyCache gc) kname
            dd <- decryptAll keydata iv pkey cd
            --let dfl = decodeFragments (drop 4 (BS.unpack dd))
            return $ runGet decodeFrags (LBS.fromChunks [BS.drop 4 dd])
galeDecryptPuff' _ x = return x


--data DestinationStatus = DSPublic { dsComment :: String } | DSPrivate { dsComment :: String } | DSGroup { dsComment :: String, dsComponents :: [DestinationStatus] } | DSUnknown

--verifyDestinations :: [Category] -> [(Category,DestinationStatus)]
--verifyDestinations cs = [ (c,DSUnknown) | c <- cs ]



verifyDestinations' :: GaleContext -> [Category] -> IO [(Category, String)]
verifyDestinations' gc cs = mapM dc cs where
    dc c | categoryIsSystem c = return (c,"Special location (puff will not be encrypted)")
    dc c = dc' c >>= return . (,) c
    dc' c = do
        ks <- findDest gc c
        case ks of
            DestPublic -> return "Public category (puff will not be encrypted)"
            DestEncrypted _ -> return "Private category"
            -- DestUnknown _ | Just x <- nextTry (fst c) -> dc' (x,snd c)
            DestUnknown _ -> return "Unknown destination (puff will not be encrypted)"
--    nextTry "*" = fail "no more"
--    nextTry ss = return $ reverse (nt (reverse ss)) where
--        nt ('*':'.':ss)  = nt ss
--        nt ss =  '*' : dropWhile (/= '.') ss


{-
        if any isNothing (snds ks) then
            if fst c == "*" then
                return "*UNKNOWN*  (puff will not be encrypted)"
             else
                dc' (nextTry (fst c), snd c)
         else
          pp [ (x,y) | (x,Just y) <- ks]

    pp ks | any isPublic (snds ks) = return "Public Category (puff will not be encrypted)"
    pp _ = return "Private Category"
    isPublic key = any nullPS (getFragmentStrings key f_keyMember)

fetchKeymembers :: GaleContext -> String -> IO [(String,Maybe Key)]
fetchKeymembers gc s = do
    km <- fk [s] []
    putLog LogNotice $ "fetchKeymembers " ++ s ++ "\n" ++ show km
    return km
   where
    fk [] xs = return xs      -- we are done
    fk ("":_) xs = return xs  -- public category
    fk (s:ss) xs | s `elem` fsts xs = fk ss xs
    fk (s:ss) xs = getPublicKey (keyCache gc) s >>= maybe (fk ss ((s,Nothing):xs)) (r . fst) where
        r :: Key -> IO [(String,Maybe Key)]
        r k = fk (map unpackPS (getFragmentStrings k f_keyMember) ++ ss) ((s,Just k):xs)

fetchKeymembers :: GaleContext -> String -> IO [(String,Maybe Key)]
fetchKeymembers _ s | "_gale." `isPrefixOf` s = return [(s,Just $ emptyKey s)]
fetchKeymembers gc s = do
    km <- fk [s] []
    putLog LogNotice $ "fetchKeymembers: " ++ s ++  show km
    return km
   where
    fk [] xs = return xs      -- we are done
    fk ("":_) _ = return [(s,Just $ emptyKey s)]  -- public category
    fk (s:ss) xs | s `elem` fsts xs = fk ss xs
    fk (s:ss) xs = getKey (keyCache gc) s >>= maybe (fk ss ((s,Nothing):xs)) r where
        r :: Key -> IO [(String,Maybe Key)]
        r k = fk (map unpackPS (getFragmentStrings k f_keyMember) ++ ss) ((s,Just k):xs)
-}

normalizeDest DestPublic = DestPublic
normalizeDest (DestUnknown xs) = DestUnknown $ snub xs
normalizeDest (DestEncrypted xs) = DestEncrypted $ snub xs

fetchKeys :: GaleContext -> String -> IO Dest
fetchKeys _ s | "_gale." `isPrefixOf` s = return DestPublic
fetchKeys _ s | "_gale@" `isPrefixOf` s = return DestPublic
fetchKeys gc s = do
    km <- fk [s] []
    putLog LogDebug $ "fetchKeys: " ++ s ++  show km
    return $ normalizeDest (mconcat $ snds km) where
        fk [] xs = return xs      -- we are done
        fk ("":_) _ = return [("",DestPublic)]
        fk (s:ss) xs | s `elem` fsts xs = fk ss xs
        fk (s:ss) xs = getKey (keyCache gc) s >>= maybe (requestKey gc (catParseNew s) >> fk ss ((s,DestUnknown [s]):xs)) r where
            r (Key _ []) = fk ss ((s,DestUnknown [s]):xs)
            r k = fk (map unpackPS (getFragmentStrings k f_keyMember) ++ ss) ((s,DestEncrypted [k]):xs)

categoryIsSystem (Category (n,_)) | "_gale." `isPrefixOf` n = True
categoryIsSystem (Category (n,_)) | "_gale" == n = True
categoryIsSystem _ = False


requestKey _ c | categoryIsSystem c = return ()
requestKey gc c = do
    let c' = catShowNew c
    v <- getKey (keyCache gc) c'
    when (isNothing v) $ do
        galeAddCategories gc [Category ("_gale.key", categoryCell c)]
        d <- createPuff  gc False $ keyRequestPuff c'
        putLog LogDebug $ "sending request for: " ++ c'
        retryIO 3.0 "error sending puff" $ withMVar (gHandle gc) $ \h -> LBS.hPut h d >> hFlush h


findDest gc c = fd c >>= res where
    -- cn = catShowNew c
    fd c = do
        ks <- fetchKeys gc (catShowNew c)
        case ks of
                DestUnknown _ | Category (a,b) <- c, Just x <- nextTry a -> fd (Category (x,b))
                k -> return k
    res x = case x of
        DestUnknown _ -> do
--            let cs = map catParseNew ss
--            galeAddCategories gc (("_gale.key", snd c):[("_gale.key", x) | x <- snds cs])
--            let f Nothing = []
--                f (Just x) = x:f (nextTry x)
--                ac = snub $ concat $ map (flip (,) (snd c)) (f $ Just $ fst c) : [ map (flip (,) d)  (f $ Just n) | (n,d) <- cs]
--                g x = requestKey gc x
--            putLog LogDebug $ "attempting to lookup: " ++ show ac
--            --mapM_ g (f $ Just $ fst c)
--            mapM_ g ac
            threadDelay 1000000  -- try again after one second
            fd c
        k -> return k






nextTry "*" = fail "no more"
nextTry ss = return $ reverse (nt (reverse ss)) where
    nt ('*':'.':ss)  = nt ss
    nt ss =  '*' : dropWhile (/= '.') ss


verifyDestinations :: GaleContext -> [Category] -> IO String

verifyDestinations _ [] = return "** No Destinations **"
verifyDestinations gc cs = do
    (ds) <- verifyDestinations' gc cs
    let --x = "DestinationStatus: " ++ d
        xs = map f ds
        f (c,x) =  (catShowNew c) ++ ": " ++ x
    return (unlines (xs))





----------------------
-- Gale stream Parsing
----------------------


putWord32 :: Handle -> Word32 -> IO ()
putWord32 h x = do
    hPutChar h $ chr $ fromIntegral $ (x `shiftR` 24)
    hPutChar h $ chr $ fromIntegral $ (x `shiftR` 16) .&. 0xFF
    hPutChar h $ chr $ fromIntegral $ (x `shiftR` 8) .&. 0xFF
    hPutChar h $ chr $ fromIntegral $ x .&. 0xFF

readWord32 :: Handle -> IO Word32
readWord32 h = do
    a <- newArray_ (0,3)
    n <- hGetArray h a 4
    when (n /= 4) $ fail "short read."
    [b1,b2,b3,b4] <- getElems a
    return $ (fromIntegral b4) .|. (fromIntegral b3 `shiftL` 8) .|.
             (fromIntegral b2 `shiftL` 16) .|. (fromIntegral b1 `shiftL` 24)

galeEncodeString :: String -> [Word8]
galeEncodeString cs = concatMap (f . ord) (concat $ map (\c -> if c == '\n' then "\r\n" else [c]) cs) where
    f x = (b1:b2:[]) where
        b1 = fromIntegral $ (x `shiftR` 8) .&. 0xFF
        b2 = fromIntegral $ x .&. 0xFF


--------------
-- Key Parsing
--------------

stons :: [Char] -> [Word8]
stons = map (fromIntegral . ord)

cipher_magic1, cipher_magic2 :: [Word8]
bs_signature_magic1 :: BS.ByteString

cipher_magic1 = stons "h\DC3\002\000"
cipher_magic2 = stons "h\DC3\002\001"

bs_cipher_magic1 = BS.pack cipher_magic1
bs_cipher_magic2 = BS.pack cipher_magic2

bs_signature_magic1 = BS.pack $ stons "h\DC3\001\000"