File: filemap.cpp

package info (click to toggle)
rsyncrypto 1.14-1.2
  • links: PTS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 1,552 kB
  • sloc: cpp: 3,459; sh: 1,221; makefile: 29
file content (345 lines) | stat: -rw-r--r-- 11,208 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
/*
 * rsyncrypto - an rsync friendly encryption
 * Copyright (C) 2005-2008 Shachar Shemesh for Lingnu Open Source Consulting ltd.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * In addition, as a special exception, the rsyncrypto authors give permission
 * to link the code of this program with the OpenSSL library (or with modified
 * versions of OpenSSL that use the same license as OpenSSL), and distribute
 * linked combinations including the two. You must obey the GNU General Public
 * License in all respects for all of the code used other than OpenSSL. If you
 * modify this file, you may extend this exception to your version of the file,
 * but you are not obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 *
 * The project's homepage is at http://rsyncrypto.lingnu.com/
 */

/*
 * The file format is ALMOST text-editor friendly. Not quite, though.
 * Each "line" contains a single character indicating the directory seperator
 * (/, \ etc). The encrypted file name, a space, and then the unencrypted file
 * name. Each line is terminated with a NULL.
 */

#include <precomp.h>
#include "rsyncrypto.h"
#include "filemap.h"

#include "file.h"

filemaptype namemap;
revfilemap reversemap; // Cypher->plain mapping for encryption usage

static const size_t CODED_FILE_ENTROPY=128;

static void replace_dir_sep( std::string &path, char dirsep )
{
    // Shortpath if we have nothing to do
    if( dirsep!=DIRSEP_C ) {
        for( std::string::iterator i=path.begin(); i!=path.end(); ++i )
        {
            if( *i==DIRSEP_C )
                throw rscerror("Untranslatable file name");

            if( *i==dirsep )
                *i=DIRSEP_C;
        }
    }
}

void filemap::fill_map( const char *list_filename, bool encrypt )
{
    bool nofile=false;
    autofd listfile_fd;

    try {
        autofd _listfile_fd( list_filename, O_RDONLY );
        listfile_fd=_listfile_fd;
    } catch( const rscerror &err ) {
        if( err.errornum()!=ENOENT )
            throw;
        nofile=true;
    }

    // If the file doesn't exist, an empty map is what "initialization" is for us. Simply get out.
    if( !nofile ) {
        autommap listfile( listfile_fd, PROT_READ );

        size_t offset=0;
        while( offset<listfile.getsize() ) {
            filemap entry;
            char ch=-1;
	    
            entry.dirsep=listfile.get_uc()[offset++];

            if( entry.dirsep==' ' || entry.dirsep=='\0' ) {
                // Probably a filemap corrupted by rsyncrypto 1.07
                throw rscerror("Corrupt filemap - rsyncrypto_recover will, likely, fully restore it");
            }

            int i;
            for( i=0; i+offset<listfile.getsize() && (ch=listfile.get_uc()[offset+i])!=' ' &&
                ch!='\0'; ++i )
                ;

            if( ch!=' ' )
                throw rscerror("Corrupt filemap - no plaintext file");

            entry.ciphername=std::string(reinterpret_cast<const char *>(listfile.get_uc()+offset), i);
            offset+=i+1;

            for( i=0; i+offset<listfile.getsize() && (ch=listfile.get_uc()[offset+i])!='\0'; ++i )
                ;
            if( ch!='\0' )
                throw rscerror("Corrupt filemap - file is not NULL terminated");
            entry.plainname=std::string(reinterpret_cast<const char *>(listfile.get_uc()+offset), i);

            offset+=i+1;

            replace_dir_sep( entry.plainname, entry.dirsep );

            // Hashing direction (encoded->unencoded file names or vice versa) depends on whether we are
            // encrypting or decrypting
            std::string key;
            if( encrypt ) {
                key=entry.plainname;
            } else {
                key=entry.ciphername;
            }

            if( !namemap.insert(filemaptype::value_type(key, entry)).second ) {
                // filemap already had an item with the same key
                throw rscerror("Corrupt filemap - dupliacte key");
            }

            // If we are encrypting, we will also need the other map direction
            if( encrypt && !reversemap.insert(revfilemap::value_type(entry.ciphername, entry.plainname)).second ) {
                // Oops - two files map to the same cipher name
                throw rscerror("Corrupt filemap - dupliace encrypted name");
            }
        }
    }
}

static std::string bin2hex( const uint8_t *data, size_t length )
{
    std::string ret;

    for( unsigned int i=0; i<length; ++i ) {
	static const char convertable[]="0123456789ABCDEF";
	ret+=convertable[data[i]>>4];
	ret+=convertable[data[i]&0x0f];
    }

    return ret;
}

std::string filemap::namecat_encrypt( const char *left, const char *right, mode_t mode )
{
    switch( mode&S_IFMT ) {
    case S_IFREG:
	{
	    std::string c_name; // Crypted name of file

	    // Remove leading dirseps
	    while( *right==DIRSEP_C )
		right++;

	    // Find out whether we already have an encoding for this file
	    filemaptype::const_iterator iter=namemap.find(right);
	    if( iter==namemap.end() ) {
		int i=0;
		std::string encodedfile;

		// Make sure we have no encoded name collisions
		do {
		    // Need to create new encoding
		    uint8_t buffer[CODED_FILE_ENTROPY/8];

		    // Generate an encoded form for the file.
		    if( !RAND_bytes( buffer, CODED_FILE_ENTROPY/8 ) )
		    {
			throw rscerror("No random entropy for file name", 0, left);
		    }

		    encodedfile=bin2hex( buffer, sizeof(buffer) );
		} while( reversemap.find(encodedfile)!=reversemap.end() && // Found a unique encoding
			(++i)<5 ); // Tried too many times.

		if(i==5) {
		    throw rscerror("Failed to locate unique encoding for file");
		}

		filemap newdata;
		newdata.plainname=right;
		newdata.ciphername=c_name=encodedfile;
		newdata.dirsep=DIRSEP_C;

		namemap[right]=newdata;
		reversemap[encodedfile]=right;
	    } else {
		// We already have an encoding

		c_name=iter->second.ciphername;
	    }

	    // Calculate the name as results from the required directory nesting level
	    nest_name(c_name);

	    return autofd::combine_paths(left, c_name.c_str());
	}
	break;
    case S_IFDIR:
	return left;
	break;
    default:
	return autofd::combine_paths(left, right);
    }
}

std::string filemap::namecat_decrypt( const char *left, const char *right, mode_t mode )
{
    if( !S_ISREG(mode) )
	return autofd::combine_paths(left, right);

    while( *right==DIRSEP_C )
	++right;

    // Get just the file part of the path
    for( int skip=0; right[skip]!='\0'; ++skip ) {
	if( right[skip]==DIRSEP_C ) {
	    right+=skip+1;
	    skip=0;
	}
    }

    if( *right=='\0' || strcmp(right, FILEMAPNAME)==0 )
	return "";

    filemaptype::const_iterator iter=namemap.find(right);
    if( iter==namemap.end() )
	// Oops - we don't know how this file was called before we hashed it's name!
	throw rscerror("Filename translation not found", 0, right);

    return autofd::combine_paths(left, iter->second.plainname.c_str());
}

void filemap::nest_name( std::string &name )
{
    int nestlevel=VAL(nenest);
    std::string retval(name);

    while( nestlevel>0 ) {
	std::string partial(name.c_str(), nestlevel);
	retval=autofd::combine_paths(partial.c_str(), retval.c_str() );
	nestlevel--;
    }

    name=retval;
}

// Create the file name mapping file
void filemap::write_map( const char *map_filename )
{
    autofd file(map_filename, O_WRONLY|O_CREAT|O_TRUNC, 0600 );

    for( revfilemap::const_iterator i=reversemap.begin(); i!=reversemap.end(); ++i ) {
	const filemap *data=&namemap[i->second];

	file.write( &(data->dirsep), sizeof( data->dirsep ) );
	file.write( data->ciphername.c_str(), data->ciphername.length() );
	file.write( " ", 1 );
	file.write( data->plainname.c_str(), data->plainname.length() );
	file.write( "", 1 );
    }
}

void virt_recurse_dir_enc( const char *encdir, const char *plaindir, const char *keydir,
	RSA *rsa_key, encopfunc op, const char *dir_sig_part )
{
    // We scan the translation map around the "dir_sig_part" area
    std::string basedirname(dir_sig_part);
    filemaptype::iterator begin, end;

    {
	// First, make sure there is exactly one DIRSEP_C at the end of the string.
	std::string::size_type i;
	for( i=basedirname.length(); i>0 && basedirname[i-1]==DIRSEP_C; --i )
	    ;

	basedirname.resize(i);

	basedirname+=DIRSEP_C;
    }
    
    if( basedirname.length()==1 ) {
	// The significant part is, for all intent and purposes, empty. Scan entire map
	begin=namemap.begin();
	end=namemap.end();
    } else {
	// Find the first file belonging to our work group
	begin=namemap.lower_bound(basedirname);
	// And one past the last one
	basedirname[basedirname.length()-1]=basedirname[basedirname.length()-1]+1;
	end=namemap.lower_bound(basedirname);
    }

    filemaptype::iterator next;
    for( filemaptype::iterator i=begin; i!=end; i=next ) {
	next=i;
	++next;
	op( encdir, plaindir, keydir, i, rsa_key );
    }
}

void filemap::enc_file_delete( const char *source_dir, const char *dst_dir, const char *key_dir,
	filemaptype::iterator &item, RSA *rsa_key )
{
    struct stat status;
    const std::string &plainname=item->second.plainname, &orig_ciphername=item->second.ciphername;
    std::string ciphername=orig_ciphername;
    // Make sure we use the proper nesting on the file name. That's why plain name is a reference, but cipher name
    // is a copy
    nest_name(ciphername);

    const std::string dst_file(autofd::combine_paths( dst_dir, plainname.c_str() ) );
    const std::string src_file(autofd::combine_paths( source_dir, ciphername.c_str() ));
    const std::string key_file(autofd::combine_paths( key_dir, ciphername.c_str() ));

    try {
        status=autofd::lstat(dst_file.c_str());
    } catch( const rscerror &err ) {
        if( err.errornum()==ENOENT || err.errornum()==ENOTDIR ) {
            // Need to erase file
            
            if( VERBOSE(1) )
                std::cout<<"Delete "<<orig_ciphername<<" ("<<plainname<<")"<<std::endl;
            if( changes_log.get()!=NULL )
                (*changes_log.get())<<src_file<<std::endl;
            autofd::unlink( src_file.c_str() );
            if( EXISTS(delkey) ) {
                if( VERBOSE(1) )
                    std::cout<<"Delete key "<<orig_ciphername<<" ("<<plainname<<")"<<std::endl;
                autofd::unlink( key_file.c_str() );
                reversemap.erase( orig_ciphername );
                namemap.erase( item );
            }
        } else {
            throw;
        }
    }
}