1/**
2 * efs.c - Limited processing of encrypted files
3 *
4 *	This module is part of ntfs-3g library
5 *
6 * Copyright (c)      2009 Martin Bene
7 * Copyright (c)      2009-2010 Jean-Pierre Andre
8 *
9 * This program/include file is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as published
11 * by the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program/include file is distributed in the hope that it will be
15 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
16 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program (in the main directory of the NTFS-3G
21 * distribution in the file COPYING); if not, write to the Free Software
22 * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 */
24
25#ifdef HAVE_CONFIG_H
26#include "config.h"
27#endif
28
29#ifdef HAVE_STDLIB_H
30#include <stdlib.h>
31#endif
32#ifdef HAVE_ERRNO_H
33#include <errno.h>
34#endif
35#ifdef HAVE_STRING_H
36#include <string.h>
37#endif
38#ifdef HAVE_SYS_STAT_H
39#include <sys/stat.h>
40#endif
41
42#ifdef HAVE_SYS_SYSMACROS_H
43#include <sys/sysmacros.h>
44#endif
45
46#include "types.h"
47#include "debug.h"
48#include "attrib.h"
49#include "inode.h"
50#include "dir.h"
51#include "efs.h"
52#include "index.h"
53#include "logging.h"
54#include "misc.h"
55#include "efs.h"
56#include "xattrs.h"
57
58static ntfschar logged_utility_stream_name[] = {
59	const_cpu_to_le16('$'),
60	const_cpu_to_le16('E'),
61	const_cpu_to_le16('F'),
62	const_cpu_to_le16('S'),
63	const_cpu_to_le16(0)
64} ;
65
66
67/*
68 *		Get the ntfs EFS info into an extended attribute
69 */
70
71int ntfs_get_efs_info(ntfs_inode *ni, char *value, size_t size)
72{
73	EFS_ATTR_HEADER *efs_info;
74	s64 attr_size = 0;
75
76	if (ni) {
77		if (ni->flags & FILE_ATTR_ENCRYPTED) {
78			efs_info = (EFS_ATTR_HEADER*)ntfs_attr_readall(ni,
79				AT_LOGGED_UTILITY_STREAM,(ntfschar*)NULL, 0,
80				&attr_size);
81			if (efs_info
82			    && (le32_to_cpu(efs_info->length) == attr_size)) {
83				if (attr_size <= (s64)size) {
84					if (value)
85						memcpy(value,efs_info,attr_size);
86					else {
87						errno = EFAULT;
88						attr_size = 0;
89					}
90				} else
91					if (size) {
92						errno = ERANGE;
93						attr_size = 0;
94					}
95				free (efs_info);
96			} else {
97				if (efs_info) {
98					free(efs_info);
99					ntfs_log_error("Bad efs_info for inode %lld\n",
100						(long long)ni->mft_no);
101				} else {
102					ntfs_log_error("Could not get efsinfo"
103						" for inode %lld\n",
104						(long long)ni->mft_no);
105				}
106				errno = EIO;
107				attr_size = 0;
108			}
109		} else {
110			errno = ENODATA;
111			ntfs_log_trace("Inode %lld is not encrypted\n",
112				(long long)ni->mft_no);
113		}
114	}
115	return (attr_size ? (int)attr_size : -errno);
116}
117
118/*
119 *		Fix all encrypted AT_DATA attributes of an inode
120 *
121 *	The fix may require making an attribute non resident, which
122 *	requires more space in the MFT record, and may cause some
123 *	attribute to be expelled and the full record to be reorganized.
124 *	When this happens, the search for data attributes has to be
125 *	reinitialized.
126 *
127 *	Returns zero if successful.
128 *		-1 if there is a problem.
129 */
130
131static int fixup_loop(ntfs_inode *ni)
132{
133	ntfs_attr_search_ctx *ctx;
134	ntfs_attr *na;
135	ATTR_RECORD *a;
136	BOOL restart;
137	int cnt;
138	int maxcnt;
139	int res = 0;
140
141	maxcnt = 0;
142	do {
143		restart = FALSE;
144		ctx = ntfs_attr_get_search_ctx(ni, NULL);
145		if (!ctx) {
146			ntfs_log_error("Failed to get ctx for efs\n");
147			res = -1;
148		}
149		cnt = 0;
150		while (!restart && !res
151			&& !ntfs_attr_lookup(AT_DATA, NULL, 0,
152				   CASE_SENSITIVE, 0, NULL, 0, ctx)) {
153			cnt++;
154			a = ctx->attr;
155			na = ntfs_attr_open(ctx->ntfs_ino, AT_DATA,
156				(ntfschar*)((u8*)a + le16_to_cpu(a->name_offset)),
157				a->name_length);
158			if (!na) {
159				ntfs_log_error("can't open DATA Attribute\n");
160				res = -1;
161			}
162			if (na && !(ctx->attr->flags & ATTR_IS_ENCRYPTED)) {
163				if (!NAttrNonResident(na)
164				   && ntfs_attr_make_non_resident(na, ctx)) {
165				/*
166				 * ntfs_attr_make_non_resident fails if there
167				 * is not enough space in the MFT record.
168				 * When this happens, force making non-resident
169				 * so that some other attribute is expelled.
170				 */
171					if (ntfs_attr_force_non_resident(na)) {
172						res = -1;
173					} else {
174					/* make sure there is some progress */
175						if (cnt <= maxcnt) {
176							errno = EIO;
177							ntfs_log_error("Multiple failure"
178								" making non resident\n");
179							res = -1;
180						} else {
181							ntfs_attr_put_search_ctx(ctx);
182							ctx = (ntfs_attr_search_ctx*)NULL;
183							restart = TRUE;
184							maxcnt = cnt;
185						}
186					}
187				}
188				if (!restart && !res
189				    && ntfs_efs_fixup_attribute(ctx, na)) {
190					ntfs_log_error("Error in efs fixup of AT_DATA Attribute\n");
191					res = -1;
192				}
193			}
194		if (na)
195			ntfs_attr_close(na);
196		}
197	} while (restart && !res);
198	if (ctx)
199		ntfs_attr_put_search_ctx(ctx);
200	return (res);
201}
202
203/*
204 *		Set the efs data from an extended attribute
205 *	Warning : the new data is not checked
206 *	Returns 0, or -1 if there is a problem
207 */
208
209int ntfs_set_efs_info(ntfs_inode *ni, const char *value, size_t size,
210			int flags)
211
212{
213	int res;
214	int written;
215	ntfs_attr *na;
216	const EFS_ATTR_HEADER *info_header;
217
218	res = 0;
219	if (ni && value && size) {
220		if (ni->flags & (FILE_ATTR_ENCRYPTED | FILE_ATTR_COMPRESSED)) {
221			if (ni->flags & FILE_ATTR_ENCRYPTED) {
222				ntfs_log_trace("Inode %lld already encrypted\n",
223						(long long)ni->mft_no);
224				errno = EEXIST;
225			} else {
226				/*
227				 * Possible problem : if encrypted file was
228				 * restored in a compressed directory, it was
229				 * restored as compressed.
230				 * TODO : decompress first.
231				 */
232				ntfs_log_error("Inode %lld cannot be encrypted and compressed\n",
233					(long long)ni->mft_no);
234				errno = EIO;
235			}
236			return -1;
237		}
238		info_header = (const EFS_ATTR_HEADER*)value;
239			/* make sure we get a likely efsinfo */
240		if (le32_to_cpu(info_header->length) != size) {
241			errno = EINVAL;
242			return (-1);
243		}
244		if (!ntfs_attr_exist(ni,AT_LOGGED_UTILITY_STREAM,
245				(ntfschar*)NULL,0)) {
246			if (!(flags & XATTR_REPLACE)) {
247			/*
248			 * no logged_utility_stream attribute : add one,
249			 * apparently, this does not feed the new value in
250			 */
251				res = ntfs_attr_add(ni,AT_LOGGED_UTILITY_STREAM,
252					logged_utility_stream_name,4,
253					(u8*)NULL,(s64)size);
254			} else {
255				errno = ENODATA;
256				res = -1;
257			}
258		} else {
259			errno = EEXIST;
260			res = -1;
261		}
262		if (!res) {
263			/*
264			 * open and update the existing efs data
265			 */
266			na = ntfs_attr_open(ni, AT_LOGGED_UTILITY_STREAM,
267				logged_utility_stream_name, 4);
268			if (na) {
269				/* resize attribute */
270				res = ntfs_attr_truncate(na, (s64)size);
271				/* overwrite value if any */
272				if (!res && value) {
273					written = (int)ntfs_attr_pwrite(na,
274						 (s64)0, (s64)size, value);
275					if (written != (s64)size) {
276						ntfs_log_error("Failed to "
277							"update efs data\n");
278						errno = EIO;
279						res = -1;
280					}
281				}
282				ntfs_attr_close(na);
283			} else
284				res = -1;
285		}
286		if (!res) {
287			/* Don't handle AT_DATA Attribute(s) if inode is a directory */
288			if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) {
289				/* iterate over AT_DATA attributes */
290                        	/* set encrypted flag, truncate attribute to match padding bytes */
291
292			if (fixup_loop(ni))
293				return -1;
294			}
295			ni->flags |= FILE_ATTR_ENCRYPTED;
296			NInoSetDirty(ni);
297			NInoFileNameSetDirty(ni);
298		}
299	} else {
300		errno = EINVAL;
301		res = -1;
302	}
303	return (res ? -1 : 0);
304}
305
306/*
307 *              Fixup raw encrypted AT_DATA Attribute
308 *     read padding length from last two bytes
309 *     truncate attribute, make non-resident,
310 *     set data size to match padding length
311 *     set ATTR_IS_ENCRYPTED flag on attribute
312 *
313 *	Return 0 if successful
314 *		-1 if failed (errno tells why)
315 */
316
317int ntfs_efs_fixup_attribute(ntfs_attr_search_ctx *ctx, ntfs_attr *na)
318{
319	s64 newsize;
320	s64 oldsize;
321	le16 appended_bytes;
322	u16 padding_length;
323	ntfs_inode *ni;
324	BOOL close_ctx = FALSE;
325
326	if (!na) {
327		ntfs_log_error("no na specified for efs_fixup_attribute\n");
328		goto err_out;
329	}
330	if (!ctx) {
331		ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
332		if (!ctx) {
333			ntfs_log_error("Failed to get ctx for efs\n");
334			goto err_out;
335		}
336		close_ctx = TRUE;
337		if (ntfs_attr_lookup(AT_DATA, na->name, na->name_len,
338				CASE_SENSITIVE, 0, NULL, 0, ctx)) {
339			ntfs_log_error("attr lookup for AT_DATA attribute failed in efs fixup\n");
340			goto err_out;
341		}
342	} else {
343		if (!NAttrNonResident(na)) {
344			ntfs_log_error("Cannot make non resident"
345				" when a context has been allocated\n");
346			goto err_out;
347		}
348	}
349
350		/* no extra bytes are added to void attributes */
351	oldsize = na->data_size;
352	if (oldsize) {
353		/* make sure size is valid for a raw encrypted stream */
354		if ((oldsize & 511) != 2) {
355			ntfs_log_error("Bad raw encrypted stream\n");
356			goto err_out;
357		}
358		/* read padding length from last two bytes of attribute */
359		if (ntfs_attr_pread(na, oldsize - 2, 2, &appended_bytes) != 2) {
360			ntfs_log_error("Error reading padding length\n");
361			goto err_out;
362		}
363		padding_length = le16_to_cpu(appended_bytes);
364		if (padding_length > 511 || padding_length > na->data_size-2) {
365			errno = EINVAL;
366			ntfs_log_error("invalid padding length %d for data_size %lld\n",
367				 padding_length, (long long)oldsize);
368			goto err_out;
369		}
370		newsize = oldsize - padding_length - 2;
371		/*
372		 * truncate attribute to possibly free clusters allocated
373		 * for the last two bytes, but do not truncate to new size
374		 * to avoid losing useful data
375		 */
376		if (ntfs_attr_truncate(na, oldsize - 2)) {
377			ntfs_log_error("Error truncating attribute\n");
378			goto err_out;
379		}
380	} else
381		newsize = 0;
382
383	/*
384	 * Encrypted AT_DATA Attributes MUST be non-resident
385	 * This has to be done after the attribute is resized, as
386	 * resizing down to zero may cause the attribute to be made
387	 * resident.
388	 */
389	if (!NAttrNonResident(na)
390	    && ntfs_attr_make_non_resident(na, ctx)) {
391		if (!close_ctx
392		    || ntfs_attr_force_non_resident(na)) {
393			ntfs_log_error("Error making DATA attribute non-resident\n");
394			goto err_out;
395		} else {
396			/*
397			 * must reinitialize context after forcing
398			 * non-resident. We need a context for updating
399			 * the state, and at this point, we are sure
400			 * the context is not used elsewhere.
401			 */
402			ntfs_attr_reinit_search_ctx(ctx);
403			if (ntfs_attr_lookup(AT_DATA, na->name, na->name_len,
404					CASE_SENSITIVE, 0, NULL, 0, ctx)) {
405				ntfs_log_error("attr lookup for AT_DATA attribute failed in efs fixup\n");
406				goto err_out;
407			}
408		}
409	}
410	ni = na->ni;
411	if (!na->name_len) {
412		ni->data_size = newsize;
413		ni->allocated_size = na->allocated_size;
414	}
415	NInoSetDirty(ni);
416	NInoFileNameSetDirty(ni);
417
418	ctx->attr->data_size = cpu_to_sle64(newsize);
419	if (sle64_to_cpu(ctx->attr->initialized_size) > newsize)
420		ctx->attr->initialized_size = ctx->attr->data_size;
421	ctx->attr->flags |= ATTR_IS_ENCRYPTED;
422	if (close_ctx)
423		ntfs_attr_put_search_ctx(ctx);
424
425	return (0);
426err_out:
427	if (close_ctx && ctx)
428		ntfs_attr_put_search_ctx(ctx);
429	return (-1);
430}
431