Source

SeclavePlugin / SeclavePlugin / SeclavePlugin.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using KeePass.Plugins;
using KeePassLib;
using System.Windows.Forms;
using System.IO;
using KeePassLib.Security;

namespace SeclavePlugin
{
    public sealed class SeclavePluginExt : Plugin
    {
        private const int m_labelMaxLength = 16;
        private const int m_usernameMaxLength = 34;
        private const int m_passwordMaxLength = 48;
        private const int m_notesMaxLength = 13;
        private const int m_maxEntries = 500;

        private const string m_toolTitle = "Seclave Plugin";

        private List<string> m_truncatedStrings; 

        private IPluginHost m_Host;
        private ToolStripSeparator m_Separator;
        private ToolStripMenuItem m_MenuItem;
        private ToolStripMenuItem m_ContextMenuItem;

        private int m_entriesWritten;

        public override bool Initialize(IPluginHost host)
        {
            m_Host = host;

            // Add option to keepass menu
            ToolStripItemCollection menu = host.MainWindow.ToolsMenu.DropDownItems;
            m_Separator = new ToolStripSeparator();
            menu.Add(m_Separator);

            m_MenuItem = new ToolStripMenuItem();
            m_MenuItem.Text = "Export all groups to Seclave...";
            m_MenuItem.Click += OnSeclaveExport;
            menu.Add(m_MenuItem);

            // Add option to entry context menus
            var contextMenu = host.MainWindow.GroupContextMenu.Items;
            m_ContextMenuItem = new ToolStripMenuItem("Export this group to Seclave...");
            m_ContextMenuItem.Click += OnSeclaveGroupExport;
            contextMenu.Add(m_ContextMenuItem);

            m_truncatedStrings = new List<string>();

            return true;
        }

        public override void Terminate()
        {
            ToolStripItemCollection menu = m_Host.MainWindow.ToolsMenu.DropDownItems;
            menu.Remove(m_Separator);
            menu.Remove(m_MenuItem);

            var contextMenu = m_Host.MainWindow.GroupContextMenu.Items;
            contextMenu.Remove(m_ContextMenuItem);
        }

        private static bool writeTruncatedOrPadded(BinaryWriter writer, string s, int length)
        {
            // Return value is wether this entry was truncated or not
            int l = Math.Min(length, s.Length);
            string s2 = s.Substring(0, l);
            writer.Write(s2.ToCharArray());
            for (int i = l; i < length; i++)
            {
                writer.Write((byte)0);
            }
            return s.Length > length;
        }

        private static string getProtectedString(ProtectedString ps)
        {
            return ps == null ? "" : ps.ReadString();
        }

        private static void writeNullEntry(BinaryWriter writer)
        {
            /* Null Entry Cookie */
            writer.Write((byte)0x08);
            writer.Write((byte)0xf2);
            writer.Write((byte)0x25);
            writer.Write((byte)0x00);

            /* All ignored */
            for (int i = 0; i < 124; i++)
            {
                writer.Write((byte)0);
            }
        }

        private static void writeEntry(BinaryWriter writer, PwEntry entry, List<string> truncatatedStrings)
        {
            /* Entry Cookie */
            writer.Write((byte)0x08);
            writer.Write((byte)0xf2);
            writer.Write((byte)0x24);
            writer.Write((byte)0x00);

            bool entryTruncated = false;

            string label = getProtectedString(entry.Strings.Get("Title")).ToLower();
            StringBuilder labelBuilder = new StringBuilder();
            foreach (char c in label) {
                if (isValidLabelChar(c)) labelBuilder.Append(c);
                else labelBuilder.Append('_');
            }

            if (writeTruncatedOrPadded(writer, labelBuilder.ToString(), m_labelMaxLength))
                entryTruncated = true;

            /* Status */
            writer.Write((byte)0);

            if (writeTruncatedOrPadded(writer, getProtectedString(entry.Strings.Get("UserName")), m_usernameMaxLength))
                entryTruncated = true;
            if (writeTruncatedOrPadded(writer, getProtectedString(entry.Strings.Get("Password")), m_passwordMaxLength))
                entryTruncated = true;
            if (writeTruncatedOrPadded(writer, getProtectedString(entry.Strings.Get("Notes")), m_notesMaxLength))
                entryTruncated = true;

            if (entryTruncated)
                truncatatedStrings.Add(entry.ParentGroup.Name + Environment.NewLine + getProtectedString(entry.Strings.Get("Title")));

            /* Zero Padding */
            for (int i = 0; i < 12; i++)
            {
                writer.Write((byte)0);
            }
        }

        private bool exportHandler(ref int entriesWritten, BinaryWriter writer, PwEntry entry, List<string> truncatedStrings)
        {
            // Check if entry parent group is Recycle bin, if so ignore it
            if (entry.ParentGroup.Uuid == m_Host.Database.RecycleBinUuid) return false;

            writeEntry(writer, entry, truncatedStrings);
            entriesWritten++;           
            return true;
        }

        private void OnSeclaveGroupExport(Object sender, EventArgs e)
        {
            ExportDatabase(m_Host.MainWindow.GetSelectedGroup());
        }

        private void OnSeclaveExport(Object sender, EventArgs e)
        {
            ExportDatabase(m_Host.Database.RootGroup);
        }

        private string GetSeclaveDevicePath()
        {
            var devices = DriveInfo.GetDrives();
            foreach (var driveInfo in devices)
            {
                if (driveInfo.DriveType == DriveType.Removable)
                {
                    var path = driveInfo.RootDirectory.ToString();
                    if (File.Exists(Path.Combine(path, "scimport.ste")))
                        return path;
                }
            }
            return null;
        }

        private void ExportDatabase(PwGroup selectedGroup)
        {
            // Let user know if there are any prerequisites missing for export
            if (!m_Host.Database.IsOpen)
            {
                MessageBox.Show("Password database needs to be unlocked for export");
                return;
            }
            
            //MessageBox.Show("Please connect SECLAVE to computer and select Import on the SECLAVE");
            //FolderBrowserDialog dialog = new FolderBrowserDialog();
            //dialog.Description = "Select SECLAVE device";
            //dialog.ShowDialog();
            //if (dialog.SelectedPath.Equals(""))
            //{
            //    return;
            //}

            //// Open up filestream
            //FileStream fs;
            //try
            //{
            //    fs = new FileStream(Path.Combine(dialog.SelectedPath, "seclave.imp"), FileMode.Create);
            //}
            //catch (UnauthorizedAccessException ex)
            //{
            //    MessageBox.Show("Could not write export file: Access Denied");
            //    return;
            //}

            MessageBox.Show("Make sure your SECLAVE device is connected and ready for import", m_toolTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
            var path = GetSeclaveDevicePath();
            if (path == null)
            {
                MessageBox.Show("No device found!", m_toolTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            FileStream fs;
            try
            {
                fs = new FileStream(Path.Combine(path, "seclave.imp"), FileMode.Create);
            }
            catch (Exception)
            {
                MessageBox.Show("Could not write export file: Access Denied");
                return;
            }

            // Set encoding
            Encoding latin1 = Encoding.GetEncoding("ISO-8859-1");

            // Let the BinaryWriter work
            var writer = EntryWriter(fs, latin1, selectedGroup);

            // Close writer and filestream
            //writer.Flush();
            writer.Close();
            fs.Close();

            // Let user know if there was any entries truncated during export
            if (m_truncatedStrings.Any())
            {
                var message = "During export, " + m_truncatedStrings.Count + " out of " + m_entriesWritten + " entries was truncated: " + Environment.NewLine;
                message += string.Join(Environment.NewLine, m_truncatedStrings.ToArray());
                MessageBox.Show(message, "Export complete", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            else
            {
                // Let user know that export was completed
                MessageBox.Show(m_entriesWritten + " entries was successfully exported!", "Export complete", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        private BinaryWriter EntryWriter(FileStream fileStream, Encoding encoding, PwGroup selectedGroup)
        {
            m_entriesWritten = 0;
            m_truncatedStrings = new List<string>();

            // Create writer and traverse the tree, being root or selected group
            var writer = new BinaryWriter(fileStream, encoding);
            selectedGroup.TraverseTree(TraversalMethod.PreOrder, null, entry => exportHandler(ref m_entriesWritten, writer, entry, m_truncatedStrings));

            for (int i = m_entriesWritten; i < m_maxEntries; i++)
            {
                writeNullEntry(writer);
            }

            return writer;
        }

        private static char latin1ToLower(char c)
        {
            if (c >= 'a' && c <= 'z') return c;
            if (c >= '0' && c <= '9') return c;
            if (c >= 'A' && c <= 'Z') return (char)(c + ('a' - 'A'));

            if (c == 0xc6) return (char)0xe6; // KS_AE
            if (c == 0xc5) return (char)0xe5; // KS_Aring
            if (c == 0xc4) return (char)0xe4; // KS_Adiaeresis
            if (c == 0xd6) return (char)0xf6; // KS_Odiaeresis
            if (c == 0xd8) return (char)0xf8; // KS_Ooblique
            if (c == 0xdc) return (char)0xfc; // KS_Udiaeresis

            return c;
        }

        private static bool isValidLabelChar(char sc)
        {
            char c = latin1ToLower(sc);
            return (c >= 'a' && c <= 'z') ||
                   (c >= '0' && c <= '9') ||
                   (c == '_') ||
                   (c == 0xe6) || // KS_ae
                   (c == 0xe5) || // KS_aring
                   (c == 0xe4) || // KS_adiaeresis
                   (c == 0xf6) || // KS_odiaeresis
                   (c == 0xf8) || // KS_oslash
                   (c == 0xfc) || // KS_udiaeresis
                   (c == 0xdf); // KS_sslash
        }
        
        // Set this link to where the version update file is located
        public override string UpdateUrl
        {
            get
            {
                return "http://www.seclave.se/seclaveplugin_version.txt";
            }
        }
    }
}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.