1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.IO;
7 using System.Text;
8 using System.Windows.Forms;
9 
10 using KeePass;
11 using KeePass.UI;
12 using KeePass.Plugins;
13 using KeePass.Resources;
14 using KeePassRPC;
15 using System.Reflection;
16 
17 namespace KeePassRPC.Forms
18 {
19     public partial class OptionsForm : Form
20     {
21         private IPluginHost _host;
22         private KeePassRPCExt _plugin;
23 
OptionsForm(IPluginHost host, KeePassRPCExt plugin)24         public OptionsForm(IPluginHost host, KeePassRPCExt plugin)
25         {
26             _host = host;
27             _plugin = plugin;
28 
29             InitializeComponent();
30             Icon = global::KeePassRPC.Properties.Resources.kee;
31             checkBox1.Text = "Automatically save KeePass database when Kee makes changes";
32             if (host.CustomConfig.GetBool("KeePassRPC.KeeFox.autoCommit", true))
33                 checkBox1.Checked = true;
34             else
35                 checkBox1.Checked = false;
36 
37             checkBox2.Text = "Immediately edit entries created by Kee";
38             if (host.CustomConfig.GetBool("KeePassRPC.KeeFox.editNewEntries", false))
39                 checkBox2.Checked = true;
40             else
41                 checkBox2.Checked = false;
42 
43             label13.Text = "You can generate new random passwords from Kee. These are stored in your system clipboard ready for you to paste into \"new user\" or \"change password\" fields. To protect against accidents these new passwords can be automatically stored in your current KeePass database under a special \"Kee Generated Password Backups\" group. You can generate new passwords when not logged in to a KeePass database but they will not receive this extra protection. The KeePass database can NOT be automatically saved after creating these backups so some problems can still result in a lost generated password.";
44 
45             checkBox3.Text = "Store a backup of each password generated by Kee";
46             if (host.CustomConfig.GetBool("KeePassRPC.KeeFox.backupNewPasswords", true))
47                 checkBox3.Checked = true;
48             else
49                 checkBox3.Checked = false;
50 
51             textBoxAuthExpiry.Text = (_host.CustomConfig.GetLong("KeePassRPC.AuthorisationExpiryTime", 8760 * 3600) / 3600).ToString();
52 
53             long secLevel = _host.CustomConfig.GetLong("KeePassRPC.SecurityLevel", 2);
54             long secLevelClientMin = _host.CustomConfig.GetLong("KeePassRPC.SecurityLevelClientMinimum", 2);
55             switch (secLevel)
56             {
57                 case 1: comboBoxSecLevelKeePass.SelectedItem = "Low"; break;
58                 case 2: comboBoxSecLevelKeePass.SelectedItem = "Medium"; break;
59                 default: comboBoxSecLevelKeePass.SelectedItem = "High"; break;
60             }
61             switch (secLevelClientMin)
62             {
63                 case 1: comboBoxSecLevelMinClient.SelectedItem = "Low"; break;
64                 case 2: comboBoxSecLevelMinClient.SelectedItem = "Medium"; break;
65                 default: comboBoxSecLevelMinClient.SelectedItem = "High"; break;
66             }
67 
68             label6.Text = "Listen for connections on this TCP/IP port.";
69             textBoxPort.Text = _host.CustomConfig.GetLong("KeePassRPC.webSocket.port", 12546).ToString();
70 
71             UpdateAuthorisedConnections();
72         }
73 
UpdateAuthorisedConnections()74         private void UpdateAuthorisedConnections()
75         {
76             KeyContainerClass[] kcs = FindAuthorisedConnections();
77 
78             if (kcs == null)
79             {
80                 // Tell the user it's not worked.
81                 dataGridView1.Visible = false;
82                 labelAuthorisedClientsFail.Visible = true;
83                 return;
84             }
85 
86             List<string> connectedClientUsernames = new List<string>();
87 
88             foreach (KeePassRPCClientConnection client in _plugin.GetConnectedRPCClients())
89                 if (!string.IsNullOrEmpty(client.UserName))
90                     connectedClientUsernames.Add(client.UserName);
91 
92             // Update the screen
93             foreach (KeyContainerClass kc in kcs)
94             {
95                 bool connected = false;
96                 if (connectedClientUsernames.Contains(kc.Username))
97                     connected = true;
98 
99                 string[] row = new string[] { kc.ClientName, kc.Username, kc.AuthExpires.ToString() };
100                 int rowid = dataGridView1.Rows.Add(row);
101                 dataGridView1.Rows[rowid].Cells[3].Value = connected;
102             }
103             return;
104 
105         }
106 
FindAuthorisedConnections()107         private KeyContainerClass[] FindAuthorisedConnections()
108         {
109             //This might not work, especially in .NET 2.0 RTM, a shame but more
110             //up to date users might as well use the feature if possible.
111             Dictionary<string, string> configValues;
112             try
113             {
114                 FieldInfo fi = typeof(KeePass.App.Configuration.AceCustomConfig)
115                                .GetField("m_vItems", BindingFlags.NonPublic | BindingFlags.Instance);
116                 configValues = (Dictionary<string, string>)fi.GetValue(_host.CustomConfig);
117 
118 
119             }
120             catch
121             {
122                 return null;
123             }
124 
125             List<KeyContainerClass> keyContainers = new List<KeyContainerClass>();
126 
127             foreach (KeyValuePair<string, string> kvp in configValues)
128             {
129                 if (kvp.Key.StartsWith("KeePassRPC.Key."))
130                 {
131                     string username = kvp.Key.Substring(15);
132                     byte[] serialisedKeyContainer = null;
133 
134                     // Assume config entry is encrypted but fall back to attempting direct deserialisation if something goes wrong
135 
136                     if (string.IsNullOrEmpty(kvp.Value))
137                         return null;
138                     try
139                     {
140                         byte[] keyBytes = System.Security.Cryptography.ProtectedData.Unprotect(
141                         Convert.FromBase64String(kvp.Value),
142                         new byte[] { 172, 218, 37, 36, 15 },
143                         System.Security.Cryptography.DataProtectionScope.CurrentUser);
144                         serialisedKeyContainer = keyBytes;
145                         System.Xml.Serialization.XmlSerializer mySerializer = new System.Xml.Serialization.XmlSerializer(typeof(KeyContainerClass));
146                         using (MemoryStream ms = new System.IO.MemoryStream(serialisedKeyContainer))
147                         {
148                             keyContainers.Add((KeyContainerClass) mySerializer.Deserialize(ms));
149                         }
150                     }
151                     catch (Exception)
152                     {
153                         try
154                         {
155                             serialisedKeyContainer = Convert.FromBase64String(kvp.Value);
156                             System.Xml.Serialization.XmlSerializer mySerializer = new System.Xml.Serialization.XmlSerializer(typeof(KeyContainerClass));
157                             using (MemoryStream ms = new MemoryStream(serialisedKeyContainer))
158                             {
159                                 keyContainers.Add((KeyContainerClass) mySerializer.Deserialize(ms));
160                             }
161                         }
162                         catch (Exception)
163                         {
164                             // It's not a valid entry so ignore it and move on
165                             continue;
166                         }
167                     }
168 
169                 }
170             }
171             return keyContainers.ToArray();
172         }
173 
m_btnOK_Click(object sender, EventArgs e)174         private void m_btnOK_Click(object sender, EventArgs e)
175         {
176             ulong port = 0;
177             try
178             {
179                 if (this.textBoxPort.Text.Length > 0)
180                 {
181                     port = ulong.Parse(this.textBoxPort.Text);
182                     if (port <= 0 || port > 65535)
183                         throw new ArgumentOutOfRangeException();
184                     if (port == _host.CustomConfig.GetULong("KeePassRPC.connection.port", 12536))
185                         throw new ArgumentException("The legacy KeePassRPC connection system is configured to use the port you have selected so please select a different port.");
186                     if (port == 19455)
187                         throw new ArgumentException("Port 19455 is commonly used by the unrelated KeePassHTTP plugin so please select a different port.");
188                 }
189             }
190             catch (ArgumentOutOfRangeException)
191             {
192                 MessageBox.Show("Invalid listening port. Type a number between 1 and 65535 or leave empty to use the default port.");
193                 DialogResult = DialogResult.None;
194                 return;
195             }
196             catch (ArgumentException ex)
197             {
198                 MessageBox.Show(ex.Message);
199                 DialogResult = DialogResult.None;
200                 return;
201             }
202 
203             long expTime = 8760;
204             try
205             {
206                 expTime = long.Parse(this.textBoxAuthExpiry.Text);
207             }
208             catch (Exception)
209             {
210                 MessageBox.Show("Invalid expiry time.");
211                 DialogResult = DialogResult.None;
212                 return;
213             }
214 
215             if (expTime < 1)
216             {
217                 expTime = 1;
218                 MessageBox.Show("Expiry time set to 1 hour. This is the minimum allowed.");
219             }
220 
221             if (expTime > 876000)
222             {
223                 expTime = 876000;
224                 MessageBox.Show("Expiry time set to 100 years. This is the maximum allowed.");
225             }
226 
227             long secLevel = 2;
228             long secLevelClientMin = 2;
229             switch ((string)comboBoxSecLevelKeePass.SelectedItem)
230             {
231                 case "Low": secLevel = 1; break;
232                 case "Medium": secLevel = 2; break;
233                 default: secLevel = 3; break;
234             }
235             switch ((string)comboBoxSecLevelMinClient.SelectedItem)
236             {
237                 case "Low": secLevelClientMin = 1; break;
238                 case "Medium": secLevelClientMin = 2; break;
239                 default: secLevelClientMin = 3; break;
240             }
241 
242             _host.CustomConfig.SetBool("KeePassRPC.KeeFox.autoCommit", checkBox1.Checked);
243             _host.CustomConfig.SetBool("KeePassRPC.KeeFox.editNewEntries", checkBox2.Checked);
244             _host.CustomConfig.SetBool("KeePassRPC.KeeFox.backupNewPasswords", checkBox3.Checked);
245             _host.CustomConfig.SetLong("KeePassRPC.AuthorisationExpiryTime", expTime * 3600);
246             _host.CustomConfig.SetLong("KeePassRPC.SecurityLevel", secLevel);
247             _host.CustomConfig.SetLong("KeePassRPC.SecurityLevelClientMinimum", secLevelClientMin);
248 
249             if (port > 0)
250                 _host.CustomConfig.SetULong("KeePassRPC.webSocket.port", port);
251 
252             _host.MainWindow.Invoke((MethodInvoker)delegate { _host.MainWindow.SaveConfig(); });
253         }
254 
OnFormLoad(object sender, EventArgs e)255         private void OnFormLoad(object sender, EventArgs e)
256         {
257             GlobalWindowManager.AddWindow(this);
258 
259             // Prevent one cell being blue by default to reduce distraction
260             dataGridView1.ClearSelection();
261         }
262 
btnCancel_Click(object sender, EventArgs e)263         private void btnCancel_Click(object sender, EventArgs e)
264         {
265             this.Close();
266         }
267 
OnFormClosed(object sender, FormClosedEventArgs e)268         private void OnFormClosed(object sender, FormClosedEventArgs e)
269         {
270             GlobalWindowManager.RemoveWindow(this);
271         }
272 
comboBoxSecLevelKeePass_SelectedIndexChanged(object sender, EventArgs e)273         private void comboBoxSecLevelKeePass_SelectedIndexChanged(object sender, EventArgs e)
274         {
275             if ((string)comboBoxSecLevelKeePass.SelectedItem == "Low")
276                 labelSecLevelWarning.Text = "A low security setting could increase the chance of your passwords being stolen. Please make sure you read the information in the manual (see link above).";
277             else if ((string)comboBoxSecLevelKeePass.SelectedItem == "High")
278                 labelSecLevelWarning.Text = "A high security setting will require you to enter a randomly generated password every time you start KeePass or its client. A medium setting should suffice in most situations, especially if you set a low authorisation timeout below.";
279             else
280                 labelSecLevelWarning.Text = "";
281         }
282 
dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)283         private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
284         {
285             if (e.ColumnIndex == 4)
286             {
287                 string username = (string)dataGridView1.Rows[e.RowIndex].Cells[1].Value;
288 
289                 // Revoke authorisation by deleting stored key data
290                 _host.CustomConfig.SetString("KeePassRPC.Key." + username, null);
291 
292                 // If this connection is active, destroy it now
293                 foreach (KeePassRPCClientConnection client in _plugin.GetConnectedRPCClients())
294                     if (!string.IsNullOrEmpty(client.UserName) && client.UserName == username)
295                     {
296                         client.WebSocketConnection.Close();
297                         break;
298                     }
299 
300                 // Refresh the view
301                 dataGridView1.Rows.RemoveAt(e.RowIndex);
302                 dataGridView1.Refresh();
303                 //UpdateAuthorisedConnections();
304             }
305         }
306 
307     }
308 }
309