[Put this one-off script here for safekeeping Dino Morelli **20140407172055 Ignore-this: 167f643684bbf581ca7989b3ecd154d5 It includes good code for producing the KeePass 1.x XML format, so is worth keeping. ] addfile ./pwconvert.hs hunk ./pwconvert.hs 1 +#! /usr/bin/env runhaskell + +{- Script to convert my old password file (plain text encrypted +with GPG) into KeePass 1.x XML format. + +This was for one-time import. +-} + +import Control.Monad +import Data.Time +import Data.Time.Format +import System.Locale +import System.Process +import System.Time +import Text.ParserCombinators.Parsec +import Text.Printf + + +main :: IO () +main = do + -- Call old password database tool to decrypt my password db, + -- capturing the results + oldPws <- readProcess "pd" ["-v"] "" + + -- Parse that data into a list of PwEntry data structures + let result = parsePwEntries "pwfile" oldPws + + -- Print out any error or make and print the XML + either print outputXml result + + +{- My existing password file format + +---- file start ---- +Desc: foo user1 +Username: user1 +Password: blah +Url: +Date: 2009-07-28 +Notes: credentials for user1 on foo + This server does something special +-------------- +Desc: bar.com +Username: someuser +Password: somepw +Url: http://bar.com +Date: 2010-03-24 +Notes: blah blah +-------------- +-------------- +---- file end ---- +-} + + +data PwEntry = PwEntry -- KeePass XML elements + { desc :: String -- title + , username :: String -- username + , password :: String -- password + , url :: String -- url + , date :: String -- creation, lastmod + , notes :: String -- comment + } + deriving Show + + +parsePwEntries :: SourceName -> String -> Either ParseError [PwEntry] +parsePwEntries name = runParser pPwEntries () name + + +pPwEntries :: Parser [PwEntry] +pPwEntries = do + bs <- many pPwEntry + string "--------------" + return bs + + +pPwEntry :: Parser PwEntry +pPwEntry = do + d <- pField "Desc" + n <- pField "Username" + p <- pField "Password" + u <- pField "Url" + m <- pField "Date" + c <- pNotes + return $ PwEntry d n p u (formatDate m) c + + +pField :: String -> Parser String +pField label = do + string label + string ": " + manyTill anyChar eol + + +pNotes :: Parser String +pNotes = do + string "Notes:" + optional $ char ' ' + manyTill anyChar (try (string "--------------\n")) + + +many1Till :: Parser a -> Parser end -> Parser [a] +many1Till p end = do + h <- p + t <- manyTill p end + return $ h : t + + +eol :: Parser Char +eol = newline <|> (eof >> return '\n') + + +formatDate :: String -> String +formatDate rawDate = maybe "" formatDate' parseDate + where + formatDate' = formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" + parseDate = foldl mplus Nothing $ map ($ rawDate) + [ parseDateString "%-m/%-d/%y" + , parseDateString "%Y-%m-%d" + ] + + +parseDateString :: String -> String -> Maybe UTCTime +parseDateString = parseTime defaultTimeLocale + + +{- KeePass 1.x XML format: + + + + + + Internet + 1 + + + this is the title + username here + passwordhere + url here + comment here + 0 + 2009-09-28T17:00:10 + 2009-09-28T17:00:38 + 2009-09-28T17:00:38 + Never + + + + eMail + 19 + + +-} + +outputXml :: [PwEntry] -> IO () +outputXml pes = do + putStrLn "\n" + putStrLn "" + putStrLn " " + putStrLn " Old" + putStrLn " 1" + mapM_ outputPwEntry pes + putStrLn " " + putStrLn "" + + +outputPwEntry :: PwEntry -> IO () +outputPwEntry pe = do + putStrLn " " + printf " <![CDATA[%s]]>\n" (desc pe) + printf " \n" (username pe) + printf " \n" (password pe) + printf " \n" (url pe) + printf " %s\n" (date pe) + printf " %s\n" (date pe) + printf " \n" (notes pe) + putStrLn " Never" + putStrLn " " + + +-- These are for debugging + +printDescPw :: PwEntry -> IO () +printDescPw pe = printf "%-37s %s\n" (desc pe) (password pe) + +printDateDesc :: PwEntry -> IO () +printDateDesc pe = printf "%s %s\n" (date pe) (desc pe)