1 /*
2   KeePass Password Safe - The Open-Source Password Manager
3   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 using System;
21 using System.Collections.Generic;
22 using System.Diagnostics;
23 using System.IO;
24 using System.Text;
25 using System.Threading;
26 
27 using KeePassLib.Native;
28 using KeePassLib.Utility;
29 
30 namespace KeePass.Util
31 {
32 	[Flags]
33 	internal enum TempClearFlags
34 	{
35 		None = 0,
36 
37 		RegisteredFiles = 1,
38 		RegisteredDirectories = 2,
39 
40 		ContentTaggedFiles = 4,
41 
42 		All = 0x7FFF
43 	}
44 
45 	public sealed class TempFilesPool
46 	{
47 		private List<string> m_lFiles = new List<string>();
48 		private List<KeyValuePair<string, bool>> m_lDirs =
49 			new List<KeyValuePair<string, bool>>();
50 
51 		private Dictionary<string, bool> m_dContentLoc = new Dictionary<string, bool>();
52 		private readonly object m_oContentLocSync = new object();
53 
54 		private long m_nThreads = 0;
55 
56 		private string m_strContentTag = null;
57 		public string TempContentTag
58 		{
59 			get
60 			{
61 				if(m_strContentTag == null)
62 				{
63 					// The tag should consist only of lower case letters
64 					// and digits (for maximum compatibility); it can
65 					// for instance be used as CSS class name
66 					string strL = "cfbm4v27xyk0dk5lyeq5";
67 					string strR = "qxi7bxozyph6qyexr9kw"; // Together ~207 bit
68 
69 					// Avoid that the content tag is directly visible in
70 					// the source code and binaries, in case the development
71 					// environment creates temporary files containing the
72 					// tag that might then be deleted unintentionally
73 					StringBuilder sb = new StringBuilder();
74 					sb.Append(strL);
75 					for(int i = strR.Length - 1; i >= 0; --i)
76 						sb.Append(strR[i]); // Reverse
77 
78 					m_strContentTag = sb.ToString();
79 				}
80 
81 				return m_strContentTag;
82 			}
83 		}
84 
TempFilesPool()85 		public TempFilesPool()
86 		{
87 		}
88 
89 #if DEBUG
~TempFilesPool()90 		~TempFilesPool()
91 		{
92 			Debug.Assert(Interlocked.Read(ref m_nThreads) == 0);
93 		}
94 #endif
95 
Clear(TempClearFlags f)96 		internal void Clear(TempClearFlags f)
97 		{
98 			if((f & TempClearFlags.RegisteredFiles) != TempClearFlags.None)
99 			{
100 				List<string> lFailed = new List<string>();
101 
102 				foreach(string strFile in m_lFiles)
103 				{
104 					try
105 					{
106 						if(File.Exists(strFile))
107 							File.Delete(strFile);
108 					}
109 					catch(Exception)
110 					{
111 						Debug.Assert(false);
112 						lFailed.Add(strFile);
113 					}
114 				}
115 
116 				m_lFiles = lFailed;
117 			}
118 
119 			if((f & TempClearFlags.RegisteredDirectories) != TempClearFlags.None)
120 			{
121 				List<KeyValuePair<string, bool>> lFailed =
122 					new List<KeyValuePair<string, bool>>();
123 
124 				foreach(KeyValuePair<string, bool> kvp in m_lDirs)
125 				{
126 					try
127 					{
128 						if(Directory.Exists(kvp.Key))
129 							Directory.Delete(kvp.Key, kvp.Value);
130 					}
131 					catch(Exception)
132 					{
133 						Debug.Assert(false);
134 						lFailed.Add(kvp);
135 					}
136 				}
137 
138 				m_lDirs = lFailed;
139 			}
140 
141 			if((f & TempClearFlags.ContentTaggedFiles) != TempClearFlags.None)
142 				ClearContentAsync();
143 		}
144 
WaitForThreads()145 		internal void WaitForThreads()
146 		{
147 			try
148 			{
149 				while(Interlocked.Read(ref m_nThreads) > 0)
150 				{
151 					Thread.Sleep(1);
152 				}
153 			}
154 			catch(Exception) { Debug.Assert(false); }
155 		}
156 
Add(string strTempFile)157 		public void Add(string strTempFile)
158 		{
159 			if(string.IsNullOrEmpty(strTempFile)) { Debug.Assert(false); return; }
160 
161 			m_lFiles.Add(strTempFile);
162 		}
163 
AddDirectory(string strTempDir, bool bRecursive)164 		public void AddDirectory(string strTempDir, bool bRecursive)
165 		{
166 			if(string.IsNullOrEmpty(strTempDir)) { Debug.Assert(false); return; }
167 
168 			m_lDirs.Add(new KeyValuePair<string, bool>(strTempDir, bRecursive));
169 		}
170 
AddContent(string strFilePattern, bool bRecursive)171 		public void AddContent(string strFilePattern, bool bRecursive)
172 		{
173 			if(string.IsNullOrEmpty(strFilePattern)) { Debug.Assert(false); return; }
174 
175 			lock(m_oContentLocSync)
176 			{
177 				if(m_dContentLoc.ContainsKey(strFilePattern) && !bRecursive)
178 					return; // Do not overwrite recursive with non-recursive
179 
180 				m_dContentLoc[strFilePattern] = bRecursive;
181 			}
182 		}
183 
AddWebBrowserPrintContent()184 		public void AddWebBrowserPrintContent()
185 		{
186 			if(!NativeLib.IsUnix())
187 			{
188 				// MSHTML may create and forget temporary files under
189 				// C:\\Users\\USER\\AppData\\Local\\Temp\\*.htm
190 				// (e.g. when printing fails)
191 				AddContent("*.htm", false);
192 			}
193 		}
194 
GetTempFileName()195 		public string GetTempFileName()
196 		{
197 			return GetTempFileName(true);
198 		}
199 
GetTempFileName(bool bCreateEmptyFile)200 		public string GetTempFileName(bool bCreateEmptyFile)
201 		{
202 			string strFile = Path.GetTempFileName();
203 			m_lFiles.Add(strFile);
204 
205 			if(!bCreateEmptyFile)
206 			{
207 				try { File.Delete(strFile); }
208 				catch(Exception) { Debug.Assert(false); }
209 			}
210 
211 			return strFile;
212 		}
213 
GetTempFileName(string strFileExt)214 		public string GetTempFileName(string strFileExt)
215 		{
216 			if(string.IsNullOrEmpty(strFileExt))
217 				return GetTempFileName();
218 
219 			try
220 			{
221 				while(true)
222 				{
223 					string str = UrlUtil.EnsureTerminatingSeparator(
224 						UrlUtil.GetTempPath(), false);
225 					str += "Temp_";
226 
227 					byte[] pbRandom = new byte[9];
228 					Program.GlobalRandom.NextBytes(pbRandom);
229 					str += StrUtil.AlphaNumericOnly(Convert.ToBase64String(
230 						pbRandom, Base64FormattingOptions.None));
231 
232 					str += "." + strFileExt;
233 
234 					if(!File.Exists(str))
235 					{
236 						m_lFiles.Add(str);
237 						return str;
238 					}
239 				}
240 			}
241 			catch(Exception) { Debug.Assert(false); }
242 
243 			return GetTempFileName();
244 		}
245 
Delete(string strTempFile)246 		public bool Delete(string strTempFile)
247 		{
248 			if(string.IsNullOrEmpty(strTempFile)) { Debug.Assert(false); return false; }
249 
250 			int i = m_lFiles.IndexOf(strTempFile);
251 			if(i < 0) { Debug.Assert(false); return false; }
252 
253 			bool bResult = false;
254 			try
255 			{
256 				File.Delete(strTempFile);
257 
258 				m_lFiles.RemoveAt(i);
259 				bResult = true;
260 			}
261 			catch(Exception) { Debug.Assert(false); }
262 
263 			return bResult;
264 		}
265 
ClearContentAsync()266 		private void ClearContentAsync()
267 		{
268 			lock(m_oContentLocSync)
269 			{
270 				if(m_dContentLoc.Count == 0) return;
271 			}
272 
273 			Interlocked.Increment(ref m_nThreads); // Here, not in thread
274 			try { ThreadPool.QueueUserWorkItem(this.ClearContentTh); }
275 			catch(Exception)
276 			{
277 				Debug.Assert(false);
278 				Interlocked.Decrement(ref m_nThreads);
279 			}
280 		}
281 
ClearContentTh(object state)282 		private void ClearContentTh(object state)
283 		{
284 			try
285 			{
286 				Debug.Assert(Interlocked.Read(ref m_nThreads) > 0);
287 
288 				string strTag = m_strContentTag;
289 				if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return; }
290 
291 				UnicodeEncoding ue = new UnicodeEncoding(false, false, false);
292 				byte[] pbTagA = StrUtil.Utf8.GetBytes(strTag);
293 				byte[] pbTagW = ue.GetBytes(strTag);
294 
295 				string strTempPath = UrlUtil.GetTempPath();
296 
297 				Dictionary<string, bool> dToDo;
298 				lock(m_oContentLocSync)
299 				{
300 					dToDo = new Dictionary<string, bool>(m_dContentLoc);
301 					m_dContentLoc.Clear();
302 				}
303 
304 				foreach(KeyValuePair<string, bool> kvp in dToDo)
305 				{
306 					bool bSuccess = false;
307 					try
308 					{
309 						bSuccess = ClearContentPriv(strTempPath, kvp.Key, kvp.Value,
310 							pbTagA, pbTagW);
311 					}
312 					catch(Exception) { Debug.Assert(false); }
313 
314 					if(!bSuccess)
315 					{
316 						lock(m_oContentLocSync)
317 						{
318 							m_dContentLoc[kvp.Key] = kvp.Value; // Try again next time
319 						}
320 					}
321 				}
322 			}
323 			catch(Exception) { Debug.Assert(false); }
324 			finally { Interlocked.Decrement(ref m_nThreads); }
325 		}
326 
ClearContentPriv(string strTempPath, string strFilePattern, bool bRecursive, byte[] pbTagA, byte[] pbTagW)327 		private bool ClearContentPriv(string strTempPath, string strFilePattern,
328 			bool bRecursive, byte[] pbTagA, byte[] pbTagW)
329 		{
330 			List<string> lFiles = UrlUtil.GetFilePaths(strTempPath, strFilePattern,
331 				(bRecursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly));
332 			bool bSuccess = true;
333 
334 			foreach(string strFile in lFiles)
335 			{
336 				if(string.IsNullOrEmpty(strFile)) continue;
337 				if((strFile == ".") || (strFile == "..")) continue;
338 
339 				try
340 				{
341 					byte[] pb = File.ReadAllBytes(strFile);
342 					if(pb == null) { Debug.Assert(false); continue; }
343 
344 					if((MemUtil.IndexOf(pb, pbTagA) >= 0) ||
345 						(MemUtil.IndexOf(pb, pbTagW) >= 0))
346 					{
347 						File.Delete(strFile);
348 					}
349 				}
350 				catch(Exception) { Debug.Assert(false); bSuccess = false; }
351 			}
352 
353 			return bSuccess;
354 		}
355 	}
356 }
357