# HG changeset patch # User Bram Moolenaar # Date 1277093746 -7200 # Node ID c08f91142c4181e28999aadad4ca21185853ed0a # Parent ae2e615a732051fa0a9ac0a215f459076f22eed6 Crypt the swapfile. diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1332,10 +1332,12 @@ 9. Encryption *encryption* Vim is able to write files encrypted, and read them back. The encrypted text cannot be read without the right key. -Note: The swapfile and text in memory is not encrypted. A system -administrator will be able to see your text while you are editing it. -When filtering text with ":!filter" or using ":w !command" the text is not -encrypted, this may reveal it to others. +The text in the swap file and the undo file is also encrypted. + +Note: The text in memory is not encrypted. A system administrator may be able +to see your text while you are editing it. When filtering text with +":!filter" or using ":w !command" the text is not encrypted, this may reveal +it to others. The 'viminfo' file is not encrypted. WARNING: If you make a typo when entering the key and then write the file and exit, the text will be lost! diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt --- a/runtime/doc/helphelp.txt +++ b/runtime/doc/helphelp.txt @@ -119,9 +119,9 @@ 1. Help commands *online-help* *:lh* *:lhelpgrep* :lh[elpgrep] {pattern}[@xx] Same as ":helpgrep", except the location list is used - instead of the quickfix list. If the help window is + instead of the quickfix list. If the help window is already opened, then the location list for that window - is used. Otherwise, a new help window is opened and + is used. Otherwise, a new help window is opened and the location list for that window is set. The location list for the current window is not changed. @@ -281,9 +281,9 @@ The first line in a help file should hav *helpfile_name.txt* For Vim version 7.3 Last change: 2010 June 4 -The first field is a link to the help file name. The second field describes -the applicable Vim version. The last field specifies the last modification -date of the file. Each field is separated by a tab. +The first field is a link to the help file name. The second field describes +the applicable Vim version. The last field specifies the last modification +date of the file. Each field is separated by a tab. At the bottom of the help file, place a Vim modeline to set the 'textwidth' and 'tabstop' options and the 'filetype' to 'help'. Never set a global option @@ -295,30 +295,30 @@ TAGS To define a help tag, place the name between asterisks (*tag-name*). The tag-name should be different from all the Vim help tag names and ideally -should begin with the name of the Vim plugin. The tag name is usually right +should begin with the name of the Vim plugin. The tag name is usually right aligned on a line. When referring to an existing help tag and to create a hot-link, place the name between two bars (|) eg. |help-writing|. When referring to a Vim option in the help file, place the option name between -two single quotes. eg. 'statusline' +two single quotes, eg. 'statusline' HIGHLIGHTING -To define a column heading, use a tilde character at the end of the line. This -will highlight the column heading in a different color. E.g. +To define a column heading, use a tilde character at the end of the line. +This will highlight the column heading in a different color. E.g. Column heading~ To separate sections in a help file, place a series of '=' characters in a -line starting from the first column. The section separator line is highlighted +line starting from the first column. The section separator line is highlighted differently. To quote a block of ex-commands verbatim, place a greater than (>) character at the end of the line before the block and a less than (<) character as the -first non-blank on a line following the block. Any line starting in column 1 +first non-blank on a line following the block. Any line starting in column 1 also implicitly stops the block of ex-commands before it. E.g. > function Example_Func() echo "Example" diff --git a/runtime/doc/recover.txt b/runtime/doc/recover.txt --- a/runtime/doc/recover.txt +++ b/runtime/doc/recover.txt @@ -188,4 +188,43 @@ will continue to get warning messages th {Vi: recovers in another way and sends mail if there is something to recover} + +ENCRYPTION AND THE SWAP FILE *:recover-crypt* + +When the text file is encrypted the swap file is encrypted as well. This +makes recovery a bit more complicated. When recovering from a swap file and +encryption has been used, you will be asked to enter one or two crypt keys. + +If the text file does not exist you will only be asked to enter the crypt key +for the swap file. + +If the text file does exist, it may be encrypted in a different way than the +swap file. You will be asked for the crypt key twice: + + Need encryption key for "/tmp/tt" ~ + Enter encryption key: ****** ~ + "/tmp/tt" [crypted] 23200L, 522129C ~ + Using swap file "/tmp/.tt.swp" ~ + Original file "/tmp/tt" ~ + Swap file is encrypted: "/tmp/.tt.swp" ~ + If you entered a new crypt key but did not write the text file, ~ + enter the new crypt key. ~ + If you wrote the text file after changing the crypt key press enter ~ + to use the same key for text file and swap file ~ + Enter encryption key: ~ + +You can be in one of these two situations: + +1. The encryption key was not changed, or after changing the key the text file + was written. You will be prompted for the crypt key twice. The second + time you can simply press Enter. That means the same key is used for the + text file and the swap file. +2. You entered a new encryption key, but did not save the text file. Vim will + then use the new key for the swap file, and the text file will still be + encrypted with the old key. At the second prompt enter the new key. + +Note that after recovery the key of the swap file will be used for the text +file. Thus if you write the text file, you need to use that new key. + + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/tags b/runtime/doc/tags --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -2577,6 +2577,7 @@ 90.5 usr_90.txt /*90.5* :read! insert.txt /*:read!* :rec recover.txt /*:rec* :recover recover.txt /*:recover* +:recover-crypt recover.txt /*:recover-crypt* :red undo.txt /*:red* :redi various.txt /*:redi* :redir various.txt /*:redir* @@ -6914,7 +6915,6 @@ os_unix.txt os_unix.txt /*os_unix.txt* os_vms.txt os_vms.txt /*os_vms.txt* os_win32.txt os_win32.txt /*os_win32.txt* other-features vi_diff.txt /*other-features* -ownsyntax eval.txt /*ownsyntax* p change.txt /*p* page-down intro.txt /*page-down* page-up intro.txt /*page-up* @@ -8220,7 +8220,7 @@ vt100-cursor-keys term.txt /*vt100-curso vt100-function-keys term.txt /*vt100-function-keys* w motion.txt /*w* w32-clientserver remote.txt /*w32-clientserver* -w:ownsyntax-variable eval.txt /*w:ownsyntax-variable* +w:current_syntax syntax.txt /*w:current_syntax* w:var eval.txt /*w:var* warningmsg-variable eval.txt /*warningmsg-variable* white-space pattern.txt /*white-space* diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt --- a/runtime/doc/todo.txt +++ b/runtime/doc/todo.txt @@ -1088,18 +1088,16 @@ Vim 7.3: - using NSIS 2.46: install on Windows 7 works, but no "Edit with Vim" menu. Use register_shell_extension()? (George Reilly, 2010 May 26) Ron's version: http://dev.ronware.org/p/vim/finfo?name=gvim.nsi -- Also crypt the swap file, each block separately. Change mf_write() and - mf_read(). - - How to get b_p_key to these functions? -> Store buf_T pointer in mfp. - - Generate a salt and seed for the swapfile, put it in block 0. - - For each block, use password + seed + byte offset to crypt/decrypt. - - When changing the password need to read back with the old password and - write again with the new one. - - Fill the gaps in the block with random bytes, otherwise it's easy to - check for correct password by finding NUL bytes. - - Verify recovery works. +- Also crypt the swap file, each block separately: + - When changing the password or 'cryptmethod' need to read back with the + old password and write again with the new one. + Problem: when the file is not written, key differs between text file and + swap file! +- Patch for :ownsyntax completion (Dominique Pelle, 2010 Jun 20) - Patch for conceal feature and 'foldcolumn'. (Dominique Pelle, 2010 Jun 10, second patch) + Also patch from Vince, 2010 Jun 15. And another June 16. + However: more generic patch on the way. - patch for conceal feature and 'modifiable'. (Dominique Pelle, 2010 Jun 9) - undofile: keep markers where the file was written/read, so that it's easy to go back to a saved version of the file: ":earlier 1f" (f for file)? @@ -1110,6 +1108,7 @@ Vim 7.3: dictionary: {'nr': 2, 'time': 1234, 'saved': 1} - Remove support for GTK 1? Patch by James Vega, Jun 11. Patches to include: +- Patch for X clibboard CurrentTime, (Fries, 2010 Jun 20) - Patch for Lisp support with ECL (Mikael Jansson, 2008 Oct 25) - Minor patches from Dominique Pelle, 2010 May 15 - Gvimext patch to support wide file names. (Szabolcs Horvat 2008 Sep 10) @@ -1117,8 +1116,9 @@ Patches to include: - Patch to support clipboard for Mac terminal. (Jjgod Jiang, 2009 Aug 1) - Patch to support :browse for more commands. (Lech Lorens, 2009 Jul 18) - Patch to improve javascript indenting. (Hari Kumar G, 2010 May 22) +- Patch to use return value of 'formatexpr'. (James Vega, 2010 Jun 16) - Patch to make CTRL-L work better with 'ignorecase' and 'smarcase'. (Martin - Toft, 2010 Jun 8) + Toft, 2010 Jun 8, Jun 16) - Patch to add diff functionality to 2html.vim. (Christian Brabandt, 2009 Dec 15) - Win32: patch for better font scaling. (George Reilly, 2009 Mar 26) diff --git a/runtime/doc/usr_11.txt b/runtime/doc/usr_11.txt --- a/runtime/doc/usr_11.txt +++ b/runtime/doc/usr_11.txt @@ -283,6 +283,8 @@ machines. Therefore, don't rely on Vim If you really don't want to see this message, you can add the 'A' flag to the 'shortmess' option. But it's very unusual that you need this. +For remarks about encryption and the swap file, see |:recover-crypt|. + ============================================================================== *11.4* Further reading diff --git a/runtime/syntax/c.vim b/runtime/syntax/c.vim --- a/runtime/syntax/c.vim +++ b/runtime/syntax/c.vim @@ -270,7 +270,7 @@ if !exists("c_no_c99") " ISO C99 endif " Accept %: for # (C99) -syn region cPreCondit start="^\s*\(%:\|#\)\s*\(if\|ifdef\|ifndef\|elif\)\>" skip="\\$" end="$" contains=cComment,cCommentL,cCppString,cCharacter,cCppParen,cParenError,cNumbers,cCommentError,cSpaceError +syn region cPreCondit start="^\s*\(%:\|#\)\s*\(if\|ifdef\|ifndef\|elif\)\>" skip="\\$" end="$" keepend contains=cComment,cCommentL,cCppString,cCharacter,cCppParen,cParenError,cNumbers,cCommentError,cSpaceError syn match cPreCondit display "^\s*\(%:\|#\)\s*\(else\|endif\)\>" if !exists("c_no_if0") if !exists("c_no_if0_fold") diff --git a/src/blowfish.c b/src/blowfish.c --- a/src/blowfish.c +++ b/src/blowfish.c @@ -436,13 +436,7 @@ bf_key_init(password, salt, salt_len) key[i] = j; } - for (i = 0; i < 256; ++i) - { - sbx[0][i] = sbi[0][i]; - sbx[1][i] = sbi[1][i]; - sbx[2][i] = sbi[2][i]; - sbx[3][i] = sbi[3][i]; - } + mch_memmove(sbx, sbi, 4 * 4 * 256); for (i = 0; i < 18; ++i) { @@ -655,6 +649,40 @@ bf_crypt_init_keys(passwd) } } +static int save_randbyte_offset; +static int save_update_offset; +static char_u save_ofb_buffer[BF_OFB_LEN]; +static UINT32_T save_pax[18]; +static UINT32_T save_sbx[4][256]; + +/* + * Save the current crypt state. Can only be used once before + * bf_crypt_restore(). + */ + void +bf_crypt_save() +{ + save_randbyte_offset = randbyte_offset; + save_update_offset = update_offset; + mch_memmove(save_ofb_buffer, ofb_buffer, BF_OFB_LEN); + mch_memmove(save_pax, pax, 4 * 18); + mch_memmove(save_sbx, sbx, 4 * 4 * 256); +} + +/* + * Restore the current crypt state. Can only be used after + * bf_crypt_save(). + */ + void +bf_crypt_restore() +{ + randbyte_offset = save_randbyte_offset; + update_offset = save_update_offset; + mch_memmove(ofb_buffer, save_ofb_buffer, BF_OFB_LEN); + mch_memmove(pax, save_pax, 4 * 18); + mch_memmove(sbx, save_sbx, 4 * 4 * 256); +} + /* * Run a test to check if the encryption works as expected. * Give an error and return FAIL when not. diff --git a/src/fileio.c b/src/fileio.c --- a/src/fileio.c +++ b/src/fileio.c @@ -64,7 +64,7 @@ static void check_marks_read __ARGS((voi #endif #ifdef FEAT_CRYPT static int get_crypt_method __ARGS((char *ptr, int len)); -static char_u *check_for_cryptkey __ARGS((char_u *cryptkey, char_u *ptr, long *sizep, off_t *filesizep, int newfile, int *did_ask)); +static char_u *check_for_cryptkey __ARGS((char_u *cryptkey, char_u *ptr, long *sizep, off_t *filesizep, int newfile, char_u *fname, int *did_ask)); #endif #ifdef UNIX static void set_file_time __ARGS((char_u *fname, time_t atime, time_t mtime)); @@ -995,6 +995,13 @@ retry: #endif } +#ifdef FEAT_CRYPT + if (cryptkey != NULL) + /* Need to reset the state, but keep the key, don't want to ask for it + * again. */ + crypt_pop_state(); +#endif + /* * When retrying with another "fenc" and the first time "fileformat" * will be reset. @@ -1426,7 +1433,8 @@ retry: */ if (filesize == 0) cryptkey = check_for_cryptkey(cryptkey, ptr, &size, - &filesize, newfile, &did_ask_for_key); + &filesize, newfile, sfname, + &did_ask_for_key); /* * Decrypt the read bytes. */ @@ -2277,8 +2285,14 @@ failed: save_file_ff(curbuf); /* remember the current file format */ #ifdef FEAT_CRYPT - if (cryptkey != curbuf->b_p_key) - free_crypt_key(cryptkey); + if (cryptkey != NULL) + { + crypt_pop_state(); + if (cryptkey != curbuf->b_p_key) + free_crypt_key(cryptkey); + /* don't set cryptkey to NULL, it's used below as a flag that + * encryption was used */ + } #endif #ifdef FEAT_MBYTE @@ -2869,12 +2883,13 @@ get_crypt_method(ptr, len) * Return the (new) encryption key, NULL for no encryption. */ static char_u * -check_for_cryptkey(cryptkey, ptr, sizep, filesizep, newfile, did_ask) +check_for_cryptkey(cryptkey, ptr, sizep, filesizep, newfile, fname, did_ask) char_u *cryptkey; /* previous encryption key or NULL */ char_u *ptr; /* pointer to read bytes */ long *sizep; /* length of read bytes */ off_t *filesizep; /* nr of bytes used from file */ int newfile; /* editing a new buffer */ + char_u *fname; /* file name to display */ int *did_ask; /* flag: whether already asked for key */ { int method = get_crypt_method((char *)ptr, *sizep); @@ -2882,7 +2897,6 @@ check_for_cryptkey(cryptkey, ptr, sizep, if (method >= 0) { curbuf->b_p_cm = method; - use_crypt_method = method; if (method > 0) (void)blowfish_self_test(); if (cryptkey == NULL && !*did_ask) @@ -2895,6 +2909,8 @@ check_for_cryptkey(cryptkey, ptr, sizep, * option and don't free it. bf needs hash of the key saved. * Don't ask for the key again when first time Enter was hit. * Happens when retrying to detect encoding. */ + smsg((char_u *)_(need_key_msg), fname); + msg_scroll = TRUE; cryptkey = get_crypt_key(newfile, FALSE); *did_ask = TRUE; @@ -2913,6 +2929,8 @@ check_for_cryptkey(cryptkey, ptr, sizep, int seed_len = crypt_seed_len[method]; int salt_len = crypt_salt_len[method]; + crypt_push_state(); + use_crypt_method = method; if (method == 0) crypt_init_keys(cryptkey); else @@ -2924,7 +2942,8 @@ check_for_cryptkey(cryptkey, ptr, sizep, /* Remove magic number from the text */ *filesizep += CRYPT_MAGIC_LEN + salt_len + seed_len; *sizep -= CRYPT_MAGIC_LEN + salt_len + seed_len; - mch_memmove(ptr, ptr + CRYPT_MAGIC_LEN + salt_len + seed_len, (size_t)*sizep); + mch_memmove(ptr, ptr + CRYPT_MAGIC_LEN + salt_len + seed_len, + (size_t)*sizep); } } /* When starting to edit a new file which does not have encryption, clear @@ -2956,6 +2975,7 @@ prepare_crypt_read(fp) if (method < 0 || method != curbuf->b_p_cm) return FAIL; + crypt_push_state(); if (method == 0) crypt_init_keys(curbuf->b_p_key); else @@ -2974,6 +2994,8 @@ prepare_crypt_read(fp) /* * Prepare for writing encrypted bytes for buffer "buf". * Returns a pointer to an allocated header of length "*lenp". + * When out of memory returns NULL. + * Otherwise calls crypt_push_state(), call crypt_pop_state() later. */ char_u * prepare_crypt_write(buf, lenp) @@ -2990,6 +3012,7 @@ prepare_crypt_write(buf, lenp) + CRYPT_SEED_LEN_MAX + 2); if (header != NULL) { + crypt_push_state(); use_crypt_method = buf->b_p_cm; /* select pkzip or blowfish */ vim_strncpy(header, (char_u *)crypt_magic[use_crypt_method], CRYPT_MAGIC_LEN); @@ -4404,7 +4427,7 @@ restore_backup: write_info.bw_fd = fd; #ifdef FEAT_CRYPT - if (*buf->b_p_key && !filtering) + if (*buf->b_p_key != NUL && !filtering) { char_u *header; int header_len; @@ -4674,6 +4697,10 @@ restore_backup: if (!backup_copy) mch_set_acl(wfname, acl); #endif +#ifdef FEAT_CRYPT + if (wb_flags & FIO_ENCRYPTED) + crypt_pop_state(); +#endif #if defined(FEAT_MBYTE) && defined(FEAT_EVAL) diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1564,6 +1564,10 @@ EXTERN short disallow_gui INIT(= FALSE); EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP")); +#ifdef FEAT_CRYPT +EXTERN char need_key_msg[] INIT(= N_("Need encryption key for \"%s\"")); +#endif + /* * Comms. with the session manager (XSMP) */ diff --git a/src/main.c b/src/main.c --- a/src/main.c +++ b/src/main.c @@ -595,7 +595,7 @@ main */ if (recoverymode && fname == NULL) { - recover_names(NULL, TRUE, 0); + recover_names(NULL, TRUE, 0, NULL); mch_exit(0); } diff --git a/src/memfile.c b/src/memfile.c --- a/src/memfile.c +++ b/src/memfile.c @@ -85,6 +85,7 @@ static void mf_ins_free __ARGS((memfile_ static bhdr_T *mf_rem_free __ARGS((memfile_T *)); static int mf_read __ARGS((memfile_T *, bhdr_T *)); static int mf_write __ARGS((memfile_T *, bhdr_T *)); +static int mf_write_block __ARGS((memfile_T *mfp, bhdr_T *hp, off_t offset, unsigned size)); static int mf_trans_add __ARGS((memfile_T *, bhdr_T *)); static void mf_do_open __ARGS((memfile_T *, char_u *, int)); @@ -161,6 +162,9 @@ mf_open(fname, flags) mfp->mf_trans[i] = NULL; /* trans lists are empty */ } mfp->mf_page_size = MEMFILE_PAGE_SIZE; +#ifdef FEAT_CRYPT + mfp->mf_old_key = NULL; +#endif #ifdef USE_FSTATFS /* @@ -422,7 +426,7 @@ mf_new(mfp, negative, page_count) } /* - * get existing block 'nr' with 'page_count' pages + * Get existing block "nr" with "page_count" pages. * * Note: The caller should first check a negative nr with mf_trans_del() */ @@ -1050,6 +1054,13 @@ mf_read(mfp, hp) PERROR(_("E295: Read error in swap file")); return FAIL; } + +#ifdef FEAT_CRYPT + /* Decrypt if 'key' is set and this is a data block. */ + if (*mfp->mf_buffer->b_p_key != NUL) + ml_decrypt_data(mfp, hp->bh_data, offset, size); +#endif + return OK; } @@ -1107,8 +1118,7 @@ mf_write(mfp, hp) else page_count = hp2->bh_page_count; size = page_size * page_count; - if ((unsigned)vim_write(mfp->mf_fd, - (hp2 == NULL ? hp : hp2)->bh_data, size) != size) + if (mf_write_block(mfp, hp2 == NULL ? hp : hp2, offset, size) == FAIL) { /* * Avoid repeating the error message, this mostly happens when the @@ -1134,6 +1144,42 @@ mf_write(mfp, hp) } /* + * Write block "hp" with data size "size" to file "mfp->mf_fd". + * Takes care of encryption. + * Return FAIL or OK. + */ + static int +mf_write_block(mfp, hp, offset, size) + memfile_T *mfp; + bhdr_T *hp; + off_t offset UNUSED; + unsigned size; +{ + char_u *data = hp->bh_data; + int result = OK; + +#ifdef FEAT_CRYPT + /* Encrypt if 'key' is set and this is a data block. */ + if (*mfp->mf_buffer->b_p_key != NUL) + { + data = ml_encrypt_data(mfp, data, offset, size); + if (data == NULL) + return FAIL; + } +#endif + + if ((unsigned)vim_write(mfp->mf_fd, data, size) != size) + result = FAIL; + +#ifdef FEAT_CRYPT + if (data != hp->bh_data) + vim_free(data); +#endif + + return result; +} + +/* * Make block number for *hp positive and add it to the translation list * * Return FAIL for failure, OK otherwise @@ -1156,7 +1202,7 @@ mf_trans_add(mfp, hp) return FAIL; /* - * get a new number for the block. + * Get a new number for the block. * If the first item in the free list has sufficient pages, use its number * Otherwise use mf_blocknr_max. */ diff --git a/src/memline.c b/src/memline.c --- a/src/memline.c +++ b/src/memline.c @@ -65,10 +65,12 @@ typedef struct pointer_block PTR_BL; typedef struct data_block DATA_BL; /* contents of a data block */ typedef struct pointer_entry PTR_EN; /* block/line-count pair */ -#define DATA_ID (('d' << 8) + 'a') /* data block id */ -#define PTR_ID (('p' << 8) + 't') /* pointer block id */ -#define BLOCK0_ID0 'b' /* block 0 id 0 */ -#define BLOCK0_ID1 '0' /* block 0 id 1 */ +#define DATA_ID (('d' << 8) + 'a') /* data block id */ +#define PTR_ID (('p' << 8) + 't') /* pointer block id */ +#define BLOCK0_ID0 'b' /* block 0 id 0 */ +#define BLOCK0_ID1 '0' /* block 0 id 1 */ +#define BLOCK0_ID1_C0 'c' /* block 0 id 1 'cm' 0 */ +#define BLOCK0_ID1_C1 'C' /* block 0 id 1 'cm' 1 */ /* * pointer to a block, used in a pointer block @@ -128,7 +130,8 @@ struct data_block #define HEADER_SIZE (sizeof(DATA_BL) - INDEX_SIZE) /* size of data block header */ #define B0_FNAME_SIZE_ORG 900 /* what it was in older versions */ -#define B0_FNAME_SIZE 898 +#define B0_FNAME_SIZE_NOCRYPT 898 /* 2 bytes used for other things */ +#define B0_FNAME_SIZE_CRYPT 890 /* 10 bytes used for other things */ #define B0_UNAME_SIZE 40 #define B0_HNAME_SIZE 40 /* @@ -155,7 +158,8 @@ struct data_block */ struct block0 { - char_u b0_id[2]; /* id for block 0: BLOCK0_ID0 and BLOCK0_ID1 */ + char_u b0_id[2]; /* id for block 0: BLOCK0_ID0 and BLOCK0_ID1, + * BLOCK0_ID1_C0, BLOCK0_ID1_C1 */ char_u b0_version[10]; /* Vim version string */ char_u b0_page_size[4];/* number of bytes per page */ char_u b0_mtime[4]; /* last modification time of file */ @@ -177,12 +181,18 @@ struct block0 * when there is room, for very long file names it's omitted. */ #define B0_DIRTY 0x55 -#define b0_dirty b0_fname[B0_FNAME_SIZE_ORG-1] +#define b0_dirty b0_fname[B0_FNAME_SIZE_ORG - 1] /* * The b0_flags field is new in Vim 7.0. */ -#define b0_flags b0_fname[B0_FNAME_SIZE_ORG-2] +#define b0_flags b0_fname[B0_FNAME_SIZE_ORG - 2] + +/* + * Crypt seed goes here, 8 bytes. New in Vim 7.3. + * Without encryption these bytes may be used for 'fenc'. + */ +#define b0_seed b0_fname[B0_FNAME_SIZE_ORG - 2 - MF_SEED_LEN] /* The lowest two bits contain the fileformat. Zero means it's not set * (compatible with Vim 6.x), otherwise it's EOL_UNIX + 1, EOL_DOS + 1 or @@ -216,7 +226,18 @@ static linenr_T lowest_marked = 0; #define ML_FLUSH 0x02 /* flush locked block */ #define ML_SIMPLE(x) (x & 0x10) /* DEL, INS or FIND */ -static void ml_upd_block0 __ARGS((buf_T *buf, int set_fname)); +/* argument for ml_upd_block0() */ +typedef enum { + UB_FNAME = 0 /* update timestamp and filename */ + , UB_SAME_DIR /* update the B0_SAME_DIR flag */ + , UB_CRYPT /* update crypt key */ +} upd_block0_T; + +#ifdef FEAT_CRYPT +static void ml_set_b0_crypt __ARGS((buf_T *buf, ZERO_BL *b0p)); +#endif +static int ml_check_b0_id __ARGS((ZERO_BL *b0p)); +static void ml_upd_block0 __ARGS((buf_T *buf, upd_block0_T what)); static void set_b0_fname __ARGS((ZERO_BL *, buf_T *buf)); static void set_b0_dir_flag __ARGS((ZERO_BL *b0p, buf_T *buf)); #ifdef FEAT_MBYTE @@ -242,6 +263,9 @@ static long char_to_long __ARGS((char_u #if defined(UNIX) || defined(WIN3264) static char_u *make_percent_swname __ARGS((char_u *dir, char_u *name)); #endif +#ifdef FEAT_CRYPT +static void ml_crypt_prepare __ARGS((memfile_T *mfp, off_t offset, int reading)); +#endif #ifdef FEAT_BYTEOFF static void ml_updatechunk __ARGS((buf_T *buf, long line, long len, int updtype)); #endif @@ -264,7 +288,7 @@ ml_open(buf) /* * init fields in memline struct */ - buf->b_ml.ml_stack_size = 0; /* no stack yet */ + buf->b_ml.ml_stack_size = 0; /* no stack yet */ buf->b_ml.ml_stack = NULL; /* no stack yet */ buf->b_ml.ml_stack_top = 0; /* nothing in the stack */ buf->b_ml.ml_locked = NULL; /* no cached block */ @@ -289,6 +313,9 @@ ml_open(buf) goto error; buf->b_ml.ml_mfp = mfp; +#ifdef FEAT_CRYPT + mfp->mf_buffer = buf; +#endif buf->b_ml.ml_flags = ML_EMPTY; buf->b_ml.ml_line_count = 1; #ifdef FEAT_LINEBREAK @@ -336,12 +363,16 @@ ml_open(buf) mch_get_host_name(b0p->b0_hname, B0_HNAME_SIZE); b0p->b0_hname[B0_HNAME_SIZE - 1] = NUL; long_to_char(mch_get_pid(), b0p->b0_pid); +#ifdef FEAT_CRYPT + if (*buf->b_p_key != NUL) + ml_set_b0_crypt(buf, b0p); +#endif } /* * Always sync block number 0 to disk, so we can check the file name in - * the swap file in findswapname(). Don't do this for help files though - * and spell buffer though. + * the swap file in findswapname(). Don't do this for a help files or + * a spell buffer though. * Only works when there's a swapfile, otherwise it's done when the file * is created. */ @@ -397,6 +428,165 @@ error: return FAIL; } +#if defined(FEAT_CRYPT) || defined(PROTO) +/* + * Prepare encryption for "buf" with block 0 "b0p". + */ + static void +ml_set_b0_crypt(buf, b0p) + buf_T *buf; + ZERO_BL *b0p; +{ + if (*buf->b_p_key == NUL) + b0p->b0_id[1] = BLOCK0_ID1; + else + { + if (buf->b_p_cm == 0) + b0p->b0_id[1] = BLOCK0_ID1_C0; + else + { + b0p->b0_id[1] = BLOCK0_ID1_C1; + /* Generate a seed and store it in block 0 and in the memfile. */ + sha2_seed(&b0p->b0_seed, MF_SEED_LEN, NULL, 0); + mch_memmove(buf->b_ml.ml_mfp->mf_seed, &b0p->b0_seed, MF_SEED_LEN); + } + } +} + +/* + * Called after the crypt key or 'cryptmethod' was changed for "buf". + * Will apply this to the swapfile. + * "old_key" is the previous key. It is equal to buf->b_p_key when + * 'cryptmethod' is changed. + * "old_cm" is the previous 'cryptmethod'. It is equal to buf->b_p_cm when + * 'key' is changed. + */ + void +ml_set_crypt_key(buf, old_key, old_cm) + buf_T *buf; + char_u *old_key; + int old_cm; +{ + memfile_T *mfp = buf->b_ml.ml_mfp; + bhdr_T *hp; + int page_count; + int idx; + long error; + infoptr_T *ip; + PTR_BL *pp; + DATA_BL *dp; + blocknr_T bnum; + int top; + + if (mfp == NULL || mfp->mf_fd < 0) + return; /* no memfile yet, nothing to do */ + + /* Set the key, method and seed to be used for reading, these must be the + * old values. */ + mfp->mf_old_key = old_key; + mfp->mf_old_cm = old_cm; + if (old_cm > 0) + mch_memmove(mfp->mf_old_seed, mfp->mf_seed, MF_SEED_LEN); + + /* Update block 0 with the crypt flag and may set a new seed. */ + ml_upd_block0(buf, UB_CRYPT); + + if (mfp->mf_infile_count > 2) + { + /* + * Need to read back all data blocks from disk, decrypt them with the + * old key/method and mark them to be written. The algorithm is + * similar to what happens in ml_recover(), but we skip negative block + * numbers. + */ + ml_flush_line(buf); /* flush buffered line */ + (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); /* flush locked block */ + + hp = NULL; + bnum = 1; /* start with block 1 */ + page_count = 1; /* which is 1 page */ + idx = 0; /* start with first index in block 1 */ + error = 0; + buf->b_ml.ml_stack_top = 0; + buf->b_ml.ml_stack = NULL; + buf->b_ml.ml_stack_size = 0; /* no stack yet */ + + for ( ; !got_int; line_breakcheck()) + { + if (hp != NULL) + mf_put(mfp, hp, FALSE, FALSE); /* release previous block */ + + /* get the block (pointer or data) */ + if ((hp = mf_get(mfp, (blocknr_T)bnum, page_count)) == NULL) + { + if (bnum == 1) + break; + ++error; + } + else + { + pp = (PTR_BL *)(hp->bh_data); + if (pp->pb_id == PTR_ID) /* it is a pointer block */ + { + if (pp->pb_count == 0) + { + /* empty block? */ + ++error; + } + else if (idx < (int)pp->pb_count) /* go a block deeper */ + { + if (pp->pb_pointer[idx].pe_bnum < 0) + { + /* Skip data block with negative block number. */ + ++idx; /* get same block again for next index */ + continue; + } + + /* going one block deeper in the tree, new entry in + * stack */ + if ((top = ml_add_stack(buf)) < 0) + { + ++error; + break; /* out of memory */ + } + ip = &(buf->b_ml.ml_stack[top]); + ip->ip_bnum = bnum; + ip->ip_index = idx; + + bnum = pp->pb_pointer[idx].pe_bnum; + page_count = pp->pb_pointer[idx].pe_page_count; + continue; + } + } + else /* not a pointer block */ + { + dp = (DATA_BL *)(hp->bh_data); + if (dp->db_id != DATA_ID) /* block id wrong */ + ++error; + else + { + /* It is a data block, need to write it back to disk. */ + mf_put(mfp, hp, TRUE, FALSE); + hp = NULL; + } + } + } + + if (buf->b_ml.ml_stack_top == 0) /* finished */ + break; + + /* go one block up in the tree */ + ip = &(buf->b_ml.ml_stack[--(buf->b_ml.ml_stack_top)]); + bnum = ip->ip_bnum; + idx = ip->ip_index + 1; /* go to next index */ + page_count = 1; + } + } + + mfp->mf_old_key = NULL; +} +#endif + /* * ml_setname() is called when the file name of "buf" has been changed. * It may rename the swap file. @@ -475,7 +665,7 @@ ml_setname(buf) #else mf_set_ffname(mfp); #endif - ml_upd_block0(buf, FALSE); + ml_upd_block0(buf, UB_SAME_DIR); break; } vim_free(fname); /* this fname didn't work, try another */ @@ -569,7 +759,7 @@ ml_open_file(buf) */ mf_fullname(mfp); #endif - ml_upd_block0(buf, FALSE); + ml_upd_block0(buf, UB_SAME_DIR); /* Flush block zero, so others can read it */ if (mf_sync(mfp, MFS_ZERO) == OK) @@ -680,16 +870,32 @@ ml_close_notmod() ml_timestamp(buf) buf_T *buf; { - ml_upd_block0(buf, TRUE); + ml_upd_block0(buf, UB_FNAME); +} + +/* + * Return FAIL when the ID of "b0p" is wrong. + */ + static int +ml_check_b0_id(b0p) + ZERO_BL *b0p; +{ + if (b0p->b0_id[0] != BLOCK0_ID0 + || (b0p->b0_id[1] != BLOCK0_ID1 + && b0p->b0_id[1] != BLOCK0_ID1_C0 + && b0p->b0_id[1] != BLOCK0_ID1_C1) + ) + return FAIL; + return OK; } /* * Update the timestamp or the B0_SAME_DIR flag of the .swp file. */ static void -ml_upd_block0(buf, set_fname) +ml_upd_block0(buf, what) buf_T *buf; - int set_fname; + upd_block0_T what; { memfile_T *mfp; bhdr_T *hp; @@ -699,13 +905,17 @@ ml_upd_block0(buf, set_fname) if (mfp == NULL || (hp = mf_get(mfp, (blocknr_T)0, 1)) == NULL) return; b0p = (ZERO_BL *)(hp->bh_data); - if (b0p->b0_id[0] != BLOCK0_ID0 || b0p->b0_id[1] != BLOCK0_ID1) + if (ml_check_b0_id(b0p) == FAIL) EMSG(_("E304: ml_upd_block0(): Didn't get block 0??")); else { - if (set_fname) + if (what == UB_FNAME) set_b0_fname(b0p, buf); - else +#ifdef FEAT_CRYPT + else if (what == UB_CRYPT) + ml_set_b0_crypt(buf, b0p); +#endif + else /* what == UB_SAME_DIR */ set_b0_dir_flag(b0p, buf); } mf_put(mfp, hp, TRUE, FALSE); @@ -731,7 +941,7 @@ set_b0_fname(b0p, buf) /* Systems that cannot translate "~user" back into a path: copy the * file name unmodified. Do use slashes instead of backslashes for * portability. */ - vim_strncpy(b0p->b0_fname, buf->b_ffname, B0_FNAME_SIZE - 1); + vim_strncpy(b0p->b0_fname, buf->b_ffname, B0_FNAME_SIZE_CRYPT - 1); # ifdef BACKSLASH_IN_FILENAME forward_slash(b0p->b0_fname); # endif @@ -746,14 +956,16 @@ set_b0_fname(b0p, buf) * First replace home dir path with "~/" with home_replace(). * Then insert the user name to get "~user/". */ - home_replace(NULL, buf->b_ffname, b0p->b0_fname, B0_FNAME_SIZE, TRUE); + home_replace(NULL, buf->b_ffname, b0p->b0_fname, + B0_FNAME_SIZE_CRYPT, TRUE); if (b0p->b0_fname[0] == '~') { flen = STRLEN(b0p->b0_fname); /* If there is no user name or it is too long, don't use "~/" */ if (get_user_name(uname, B0_UNAME_SIZE) == FAIL - || (ulen = STRLEN(uname)) + flen > B0_FNAME_SIZE - 1) - vim_strncpy(b0p->b0_fname, buf->b_ffname, B0_FNAME_SIZE - 1); + || (ulen = STRLEN(uname)) + flen > B0_FNAME_SIZE_CRYPT - 1) + vim_strncpy(b0p->b0_fname, buf->b_ffname, + B0_FNAME_SIZE_CRYPT - 1); else { mch_memmove(b0p->b0_fname + ulen + 1, b0p->b0_fname + 1, flen); @@ -816,15 +1028,24 @@ add_b0_fenc(b0p, buf) buf_T *buf; { int n; + int size = B0_FNAME_SIZE_NOCRYPT; + +# ifdef FEAT_CRYPT + /* Without encryption use the same offset as in Vim 7.2 to be compatible. + * With encryption it's OK to move elsewhere, the swap file is not + * compatible anyway. */ + if (*buf->b_p_key != NUL) + size = B0_FNAME_SIZE_CRYPT; +# endif n = (int)STRLEN(buf->b_p_fenc); - if (STRLEN(b0p->b0_fname) + n + 1 > B0_FNAME_SIZE) + if ((int)STRLEN(b0p->b0_fname) + n + 1 > size) b0p->b0_flags &= ~B0_HAS_FENC; else { - mch_memmove((char *)b0p->b0_fname + B0_FNAME_SIZE - n, + mch_memmove((char *)b0p->b0_fname + size - n, (char *)buf->b_p_fenc, (size_t)n); - *(b0p->b0_fname + B0_FNAME_SIZE - n - 1) = NUL; + *(b0p->b0_fname + size - n - 1) = NUL; b0p->b0_flags |= B0_HAS_FENC; } } @@ -832,7 +1053,7 @@ add_b0_fenc(b0p, buf) /* - * try to recover curbuf from the .swp file + * Try to recover curbuf from the .swp file. */ void ml_recover() @@ -840,10 +1061,14 @@ ml_recover() buf_T *buf = NULL; memfile_T *mfp = NULL; char_u *fname; + char_u *fname_used = NULL; bhdr_T *hp = NULL; ZERO_BL *b0p; int b0_ff; char_u *b0_fenc = NULL; +#ifdef FEAT_CRYPT + int b0_cm = -1; +#endif PTR_BL *pp; DATA_BL *dp; infoptr_T *ip; @@ -892,14 +1117,14 @@ ml_recover() && ASCII_ISALPHA(fname[len - 1])) { directly = TRUE; - fname = vim_strsave(fname); /* make a copy for mf_open() */ + fname_used = vim_strsave(fname); /* make a copy for mf_open() */ } else { directly = FALSE; /* count the number of matching swap files */ - len = recover_names(&fname, FALSE, 0); + len = recover_names(fname, FALSE, 0, NULL); if (len == 0) /* no swap files found */ { EMSG2(_("E305: No swap file found for %s"), fname); @@ -910,7 +1135,7 @@ ml_recover() else /* several swap files found, choose */ { /* list the names of the swap files */ - (void)recover_names(&fname, TRUE, 0); + (void)recover_names(fname, TRUE, 0, NULL); msg_putchar('\n'); MSG_PUTS(_("Enter number of swap file to use (0 to quit): ")); i = get_number(FALSE, NULL); @@ -918,28 +1143,26 @@ ml_recover() goto theend; } /* get the swap file name that will be used */ - (void)recover_names(&fname, FALSE, i); + (void)recover_names(fname, FALSE, i, &fname_used); } - if (fname == NULL) + if (fname_used == NULL) goto theend; /* out of memory */ /* When called from main() still need to initialize storage structure */ if (called_from_main && ml_open(curbuf) == FAIL) getout(1); -/* - * allocate a buffer structure (only the memline in it is really used) - */ + /* + * Allocate a buffer structure for the swap file that is used for recovery. + * Only the memline in it is really used. + */ buf = (buf_T *)alloc((unsigned)sizeof(buf_T)); if (buf == NULL) - { - vim_free(fname); goto theend; - } - -/* - * init fields in memline struct - */ + + /* + * init fields in memline struct + */ buf->b_ml.ml_stack_size = 0; /* no stack yet */ buf->b_ml.ml_stack = NULL; /* no stack yet */ buf->b_ml.ml_stack_top = 0; /* nothing in the stack */ @@ -947,23 +1170,24 @@ ml_recover() buf->b_ml.ml_locked = NULL; /* no locked block */ buf->b_ml.ml_flags = 0; -/* - * open the memfile from the old swap file - */ - p = vim_strsave(fname); /* save fname for the message - (mf_open() may free fname) */ - mfp = mf_open(fname, O_RDONLY); /* consumes fname! */ + /* + * open the memfile from the old swap file + */ + p = vim_strsave(fname_used); /* save "fname_used" for the message: + mf_open() will consume "fname_used"! */ + mfp = mf_open(fname_used, O_RDONLY); + fname_used = p; if (mfp == NULL || mfp->mf_fd < 0) { - if (p != NULL) - { - EMSG2(_("E306: Cannot open %s"), p); - vim_free(p); - } + if (fname_used != NULL) + EMSG2(_("E306: Cannot open %s"), fname_used); goto theend; } - vim_free(p); buf->b_ml.ml_mfp = mfp; +#ifdef FEAT_CRYPT + mfp->mf_buffer = buf; + buf->b_p_key = empty_option; +#endif /* * The page size set in mf_open() might be different from the page size @@ -973,16 +1197,15 @@ ml_recover() */ mfp->mf_page_size = MIN_SWAP_PAGE_SIZE; -/* - * try to read block 0 - */ + /* + * try to read block 0 + */ if ((hp = mf_get(mfp, (blocknr_T)0, 1)) == NULL) { msg_start(); MSG_PUTS_ATTR(_("Unable to read block 0 from "), attr | MSG_HIST); msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST); - MSG_PUTS_ATTR( - _("\nMaybe no changes were made or Vim did not update the swap file."), + MSG_PUTS_ATTR(_("\nMaybe no changes were made or Vim did not update the swap file."), attr | MSG_HIST); msg_end(); goto theend; @@ -998,7 +1221,7 @@ ml_recover() msg_end(); goto theend; } - if (b0p->b0_id[0] != BLOCK0_ID0 || b0p->b0_id[1] != BLOCK0_ID1) + if (ml_check_b0_id(b0p) == FAIL) { EMSG2(_("E307: %s does not look like a Vim swap file"), mfp->mf_fname); goto theend; @@ -1024,6 +1247,22 @@ ml_recover() goto theend; } +#ifdef FEAT_CRYPT + if (b0p->b0_id[1] == BLOCK0_ID1_C0) + buf->b_p_cm = b0_cm = 0; + else if (b0p->b0_id[1] == BLOCK0_ID1_C1) + { + buf->b_p_cm = b0_cm = 1; + mch_memmove(mfp->mf_seed, &b0p->b0_seed, MF_SEED_LEN); + } +#else + if (b0p->b0_id[1] != BLOCK0_ID1) + { + EMSG2(_("E000: %s is encrypted and this version of Vim does not support encryption"), mfp->mf_fname); + goto theend; + } +#endif + /* * If we guessed the wrong page size, we have to recalculate the * highest block number in the file. @@ -1058,9 +1297,9 @@ ml_recover() b0p = (ZERO_BL *)(hp->bh_data); } -/* - * If .swp file name given directly, use name from swap file for buffer. - */ + /* + * If .swp file name given directly, use name from swap file for buffer. + */ if (directly) { expand_env(b0p->b0_fname, NameBuff, MAXPATHL); @@ -1078,9 +1317,9 @@ ml_recover() smsg((char_u *)_("Original file \"%s\""), NameBuff); msg_putchar('\n'); -/* - * check date of swap file and original file - */ + /* + * check date of swap file and original file + */ mtime = char_to_long(b0p->b0_mtime); if (curbuf->b_ffname != NULL && mch_stat((char *)curbuf->b_ffname, &org_stat) != -1 @@ -1096,10 +1335,16 @@ ml_recover() b0_ff = (b0p->b0_flags & B0_FF_MASK); if (b0p->b0_flags & B0_HAS_FENC) { - for (p = b0p->b0_fname + B0_FNAME_SIZE; - p > b0p->b0_fname && p[-1] != NUL; --p) + int size = B0_FNAME_SIZE_NOCRYPT; + +#ifdef FEAT_CRYPT + /* Use the same size as in add_b0_fenc(). */ + if (b0p->b0_id[1] != BLOCK0_ID1) + size = B0_FNAME_SIZE_CRYPT; +#endif + for (p = b0p->b0_fname + size; p > b0p->b0_fname && p[-1] != NUL; --p) ; - b0_fenc = vim_strnsave(p, (int)(b0p->b0_fname + B0_FNAME_SIZE - p)); + b0_fenc = vim_strnsave(p, (int)(b0p->b0_fname + size - p)); } mf_put(mfp, hp, FALSE, FALSE); /* release block 0 */ @@ -1115,11 +1360,40 @@ ml_recover() /* * Try reading the original file to obtain the values of 'fileformat', * 'fileencoding', etc. Ignore errors. The text itself is not used. + * When the file is encrypted the user is asked to enter the key. */ if (curbuf->b_ffname != NULL) orig_file_status = readfile(curbuf->b_ffname, NULL, (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, NULL, READ_NEW); +#ifdef FEAT_CRYPT + if (b0_cm >= 0) + { + /* Need to ask the user for the crypt key. If this fails we continue + * without a key, will probably get garbage text. */ + if (*curbuf->b_p_key != NUL) + { + smsg((char_u *)_("Swap file is encrypted: \"%s\""), fname_used); + MSG_PUTS(_("\nIf you entered a new crypt key but did not write the text file,")); + MSG_PUTS(_("\nenter the new crypt key.")); + MSG_PUTS(_("\nIf you wrote the text file after changing the crypt key press enter")); + MSG_PUTS(_("\nto use the same key for text file and swap file")); + } + else + smsg((char_u *)_(need_key_msg), fname_used); + buf->b_p_key = get_crypt_key(FALSE, FALSE); + if (buf->b_p_key == NULL) + buf->b_p_key = curbuf->b_p_key; + else if (*buf->b_p_key == NUL) + { + vim_free(buf->b_p_key); + buf->b_p_key = curbuf->b_p_key; + } + if (buf->b_p_key == NULL) + buf->b_p_key = empty_option; + } +#endif + /* Use the 'fileformat' and 'fileencoding' as stored in the swap file. */ if (b0_ff != 0) set_fileformat(b0_ff - 1, OPT_LOCAL); @@ -1386,9 +1660,17 @@ ml_recover() MSG_PUTS(_("\nYou may want to delete the .swp file now.\n\n")); cmdline_row = msg_row; } +#ifdef FEAT_CRYPT + if (*buf->b_p_key != NUL && STRCMP(curbuf->b_p_key, buf->b_p_key) != 0) + { + MSG_PUTS(_("Using crypt key from swap file for the text file.\n")); + set_option_value((char_u *)"key", 0L, buf->b_p_key, OPT_LOCAL); + } +#endif redraw_curbuf_later(NOT_VALID); theend: + vim_free(fname_used); recoverymode = FALSE; if (mfp != NULL) { @@ -1398,6 +1680,10 @@ theend: } if (buf != NULL) { +#ifdef FEAT_CRYPT + if (buf->b_p_key != curbuf->b_p_key) + free_string_option(buf->b_p_key); +#endif vim_free(buf->b_ml.ml_stack); vim_free(buf); } @@ -1424,10 +1710,11 @@ theend: * - find the name of the n'th swap file when recovering */ int -recover_names(fname, list, nr) - char_u **fname; /* base for swap file name */ - int list; /* when TRUE, list the swap file names */ - int nr; /* when non-zero, return nr'th swap file name */ +recover_names(fname, list, nr, fname_out) + char_u *fname; /* base for swap file name */ + int list; /* when TRUE, list the swap file names */ + int nr; /* when non-zero, return nr'th swap file name */ + char_u **fname_out; /* result when "nr" > 0 */ { int num_names; char_u *(names[6]); @@ -1447,13 +1734,13 @@ recover_names(fname, list, nr) if (fname != NULL) { #ifdef HAVE_READLINK - /* Expand symlink in the file name, because the swap file is created with - * the actual file instead of with the symlink. */ - if (resolve_symlink(*fname, fname_buf) == OK) - fname_res = fname_buf; - else + /* Expand symlink in the file name, because the swap file is created + * with the actual file instead of with the symlink. */ + if (resolve_symlink(fname, fname_buf) == OK) + fname_res = fname_buf; + else #endif - fname_res = *fname; + fname_res = fname; } if (list) @@ -1480,7 +1767,7 @@ recover_names(fname, list, nr) if (dir_name[0] == '.' && dir_name[1] == NUL) /* check current dir */ { - if (fname == NULL || *fname == NULL) + if (fname == NULL) { #ifdef VMS names[0] = vim_strsave((char_u *)"*_sw%"); @@ -1511,7 +1798,7 @@ recover_names(fname, list, nr) } else /* check directory dir_name */ { - if (fname == NULL || *fname == NULL) + if (fname == NULL) { #ifdef VMS names[0] = concat_fnames(dir_name, (char_u *)"*_sw%", TRUE); @@ -1583,8 +1870,7 @@ recover_names(fname, list, nr) * not able to execute the shell). * Try finding a swap file by simply adding ".swp" to the file name. */ - if (*dirp == NUL && file_count + num_files == 0 - && fname != NULL && *fname != NULL) + if (*dirp == NUL && file_count + num_files == 0 && fname != NULL) { struct stat st; char_u *swapname; @@ -1637,7 +1923,8 @@ recover_names(fname, list, nr) file_count += num_files; if (nr <= file_count) { - *fname = vim_strsave(files[nr - 1 + num_files - file_count]); + *fname_out = vim_strsave( + files[nr - 1 + num_files - file_count]); dirp = (char_u *)""; /* stop searching */ } } @@ -1645,7 +1932,7 @@ recover_names(fname, list, nr) { if (dir_name[0] == '.' && dir_name[1] == NUL) { - if (fname == NULL || *fname == NULL) + if (fname == NULL) MSG_PUTS(_(" In current directory:\n")); else MSG_PUTS(_(" Using specified name:\n")); @@ -1772,7 +2059,7 @@ swapfile_info(fname) { MSG_PUTS(_(" [from Vim version 3.0]")); } - else if (b0.b0_id[0] != BLOCK0_ID0 || b0.b0_id[1] != BLOCK0_ID1) + else if (ml_check_b0_id(&b0) == FAIL) { MSG_PUTS(_(" [does not look like a Vim swap file]")); } @@ -3478,11 +3765,11 @@ ml_find_line(buf, lnum, action) error_block: mf_put(mfp, hp, FALSE, FALSE); error_noblock: -/* - * If action is ML_DELETE or ML_INSERT we have to correct the tree for - * the incremented/decremented line counts, because there won't be a line - * inserted/deleted after all. - */ + /* + * If action is ML_DELETE or ML_INSERT we have to correct the tree for + * the incremented/decremented line counts, because there won't be a line + * inserted/deleted after all. + */ if (action == ML_DELETE) ml_lineadd(buf, 1); else if (action == ML_INSERT) @@ -3505,10 +3792,10 @@ ml_add_stack(buf) top = buf->b_ml.ml_stack_top; - /* may have to increase the stack size */ + /* may have to increase the stack size */ if (top == buf->b_ml.ml_stack_size) { - CHECK(top > 0, _("Stack size increases")); /* more than 5 levels??? */ + CHECK(top > 0, _("Stack size increases")); /* more than 5 levels??? */ newstack = (infoptr_T *)alloc((unsigned)sizeof(infoptr_T) * (buf->b_ml.ml_stack_size + STACK_INCR)); @@ -4518,6 +4805,132 @@ ml_setflags(buf) } } +#if defined(FEAT_CRYPT) || defined(PROTO) +/* + * If "data" points to a data block encrypt the text in it and return a copy + * in allocated memory. Return NULL when out of memory. + * Otherwise return "data". + */ + char_u * +ml_encrypt_data(mfp, data, offset, size) + memfile_T *mfp; + char_u *data; + off_t offset; + unsigned size; +{ + DATA_BL *dp = (DATA_BL *)data; + char_u *head_end; + char_u *text_start; + char_u *new_data; + int text_len; + + if (dp->db_id != DATA_ID) + return data; + + new_data = (char_u *)alloc(size); + if (new_data == NULL) + return NULL; + head_end = (char_u *)(&dp->db_index[dp->db_line_count]); + text_start = (char_u *)dp + dp->db_txt_start; + text_len = size - dp->db_txt_start; + + /* Copy the header and the text. */ + mch_memmove(new_data, dp, head_end - (char_u *)dp); + + /* Encrypt the text. */ + crypt_push_state(); + ml_crypt_prepare(mfp, offset, FALSE); + crypt_encode(text_start, text_len, new_data + dp->db_txt_start); + crypt_pop_state(); + + /* Clear the gap. */ + if (head_end < text_start) + vim_memset(new_data + (head_end - data), 0, text_start - head_end); + + return new_data; +} + +/* + * Decrypt the text in "data" if it points to a data block. + */ + void +ml_decrypt_data(mfp, data, offset, size) + memfile_T *mfp; + char_u *data; + off_t offset; + unsigned size; +{ + DATA_BL *dp = (DATA_BL *)data; + char_u *head_end; + char_u *text_start; + int text_len; + + if (dp->db_id == DATA_ID) + { + head_end = (char_u *)(&dp->db_index[dp->db_line_count]); + text_start = (char_u *)dp + dp->db_txt_start; + text_len = dp->db_txt_end - dp->db_txt_start; + + if (head_end > text_start || dp->db_txt_start > size + || dp->db_txt_end > size) + return; /* data was messed up */ + + /* Decrypt the text in place. */ + crypt_push_state(); + ml_crypt_prepare(mfp, offset, TRUE); + crypt_decode(text_start, text_len); + crypt_pop_state(); + } +} + +/* + * Prepare for encryption/decryption, using the key, seed and offset. + */ + static void +ml_crypt_prepare(mfp, offset, reading) + memfile_T *mfp; + off_t offset; + int reading; +{ + buf_T *buf = mfp->mf_buffer; + char_u salt[50]; + int method; + char_u *key; + char_u *seed; + + if (reading && mfp->mf_old_key != NULL) + { + /* Reading back blocks with the previous key/method/seed. */ + method = mfp->mf_old_cm; + key = mfp->mf_old_key; + seed = mfp->mf_old_seed; + } + else + { + method = buf->b_p_cm; + key = buf->b_p_key; + seed = mfp->mf_seed; + } + + use_crypt_method = method; /* select pkzip or blowfish */ + if (method == 0) + { + vim_snprintf((char *)salt, sizeof(salt), "%s%ld", key, (long)offset); + crypt_init_keys(salt); + } + else + { + /* Using blowfish, add salt and seed. We use the byte offset of the + * block for the salt. */ + vim_snprintf((char *)salt, sizeof(salt), "%ld", (long)offset); + bf_key_init(key, salt, STRLEN(salt)); + bf_ofb_init(seed, MF_SEED_LEN); + } +} + +#endif + + #if defined(FEAT_BYTEOFF) || defined(PROTO) #define MLCS_MAXL 800 /* max no of lines in chunk */ diff --git a/src/misc2.c b/src/misc2.c --- a/src/misc2.c +++ b/src/misc2.c @@ -3746,6 +3746,59 @@ static ulg keys[3]; /* keys defining the keys[2] = CRC32(keys[2], (int)(keys[1] >> 24)); \ } +static int crypt_busy = 0; +static ulg saved_keys[3]; +static int saved_crypt_method; + +/* + * Prepare for initializing encryption. If already doing encryption then save + * the state. + * Must always be called symmetrycally with crypt_pop_state(). + */ + void +crypt_push_state() +{ + if (crypt_busy == 1) + { + /* save the state */ + if (use_crypt_method == 0) + { + saved_keys[0] = keys[0]; + saved_keys[1] = keys[1]; + saved_keys[2] = keys[2]; + } + else + bf_crypt_save(); + saved_crypt_method = use_crypt_method; + } + else if (crypt_busy > 1) + EMSG2(_(e_intern2), "crypt_push_state()"); + ++crypt_busy; +} + +/* + * End encryption. If doing encryption before crypt_push_state() then restore + * the saved state. + * Must always be called symmetrycally with crypt_push_state(). + */ + void +crypt_pop_state() +{ + --crypt_busy; + if (crypt_busy == 1) + { + use_crypt_method = saved_crypt_method; + if (use_crypt_method == 0) + { + keys[0] = saved_keys[0]; + keys[1] = saved_keys[1]; + keys[2] = saved_keys[2]; + } + else + bf_crypt_restore(); + } +} + /* * Encrypt "from[len]" into "to[len]". * "from" and "to" can be equal to encrypt in place. @@ -3894,6 +3947,8 @@ get_crypt_key(store, twice) /* since the user typed this, no need to wait for return */ need_wait_return = FALSE; + if (msg_didout) + msg_putchar('\n'); msg_didout = FALSE; free_crypt_key(p2); diff --git a/src/option.c b/src/option.c --- a/src/option.c +++ b/src/option.c @@ -5969,13 +5969,18 @@ did_set_string_option(opt_idx, varp, new } } -#if defined(FEAT_CRYPT) && defined(FEAT_CMDHIST) +#if defined(FEAT_CRYPT) /* 'cryptkey' */ else if (gvarp == &p_key) { +# if defined(FEAT_CMDHIST) /* Make sure the ":set" command doesn't show the new value in the * history. */ remove_key_from_history(); +# endif + if (STRCMP(curbuf->b_p_key, oldval) != 0) + /* Need to update the swapfile. */ + ml_set_crypt_key(curbuf, oldval, curbuf->b_p_cm); } #endif @@ -7941,15 +7946,19 @@ set_num_option(opt_idx, varp, value, err if (curbuf->b_p_cm < 0) { errmsg = e_positive; - curbuf->b_p_cm = 0; + curbuf->b_p_cm = old_value; } if (curbuf->b_p_cm > 1) { errmsg = e_invarg; - curbuf->b_p_cm = 1; + curbuf->b_p_cm = old_value; } if (curbuf->b_p_cm > 0 && blowfish_self_test() == FAIL) - curbuf->b_p_cm = 0; + curbuf->b_p_cm = old_value; + + if (curbuf->b_p_cm != old_value && *curbuf->b_p_key != NUL) + /* Need to update the swapfile. */ + ml_set_crypt_key(curbuf, curbuf->b_p_key, old_value); } #endif diff --git a/src/proto/blowfish.pro b/src/proto/blowfish.pro --- a/src/proto/blowfish.pro +++ b/src/proto/blowfish.pro @@ -4,5 +4,7 @@ void bf_ofb_init __ARGS((char_u *iv, int void bf_crypt_encode __ARGS((char_u *from, size_t len, char_u *to)); void bf_crypt_decode __ARGS((char_u *ptr, long len)); void bf_crypt_init_keys __ARGS((char_u *passwd)); +void bf_crypt_save __ARGS((void)); +void bf_crypt_restore __ARGS((void)); int blowfish_self_test __ARGS((void)); /* vim: set ft=c : */ diff --git a/src/proto/memline.pro b/src/proto/memline.pro --- a/src/proto/memline.pro +++ b/src/proto/memline.pro @@ -1,5 +1,6 @@ /* memline.c */ int ml_open __ARGS((buf_T *buf)); +void ml_set_crypt_key __ARGS((buf_T *buf, char_u *old_key, int old_cm)); void ml_setname __ARGS((buf_T *buf)); void ml_open_files __ARGS((void)); void ml_open_file __ARGS((buf_T *buf)); @@ -9,7 +10,7 @@ void ml_close_all __ARGS((int del_file)) void ml_close_notmod __ARGS((void)); void ml_timestamp __ARGS((buf_T *buf)); void ml_recover __ARGS((void)); -int recover_names __ARGS((char_u **fname, int list, int nr)); +int recover_names __ARGS((char_u *fname, int list, int nr, char_u **fname_out)); void ml_sync_all __ARGS((int check_file, int check_char)); void ml_preserve __ARGS((buf_T *buf, int message)); char_u *ml_get __ARGS((linenr_T lnum)); @@ -29,6 +30,8 @@ int resolve_symlink __ARGS((char_u *fnam char_u *makeswapname __ARGS((char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name)); char_u *get_file_in_dir __ARGS((char_u *fname, char_u *dname)); void ml_setflags __ARGS((buf_T *buf)); +char_u *ml_encrypt_data __ARGS((memfile_T *mfp, char_u *data, off_t offset, unsigned size)); +void ml_decrypt_data __ARGS((memfile_T *mfp, char_u *data, off_t offset, unsigned size)); long ml_find_line_or_offset __ARGS((buf_T *buf, linenr_T lnum, long *offp)); void goto_byte __ARGS((long cnt)); /* vim: set ft=c : */ diff --git a/src/proto/misc2.pro b/src/proto/misc2.pro --- a/src/proto/misc2.pro +++ b/src/proto/misc2.pro @@ -80,6 +80,8 @@ int illegal_slash __ARGS((char *name)); char_u *parse_shape_opt __ARGS((int what)); int get_shape_idx __ARGS((int mouse)); void update_mouseshape __ARGS((int shape_idx)); +void crypt_push_state __ARGS((void)); +void crypt_pop_state __ARGS((void)); void crypt_encode __ARGS((char_u *from, size_t len, char_u *to)); void crypt_decode __ARGS((char_u *ptr, long len)); void crypt_init_keys __ARGS((char_u *passwd)); diff --git a/src/sha256.c b/src/sha256.c --- a/src/sha256.c +++ b/src/sha256.c @@ -402,7 +402,8 @@ get_some_time() } /* - * set header = sha2_seed(random_data); + * Fill "header[header_len]" with random_data. + * Also "salt[salt_len]" when "salt" is not NULL. */ void sha2_seed(header, header_len, salt, salt_len) @@ -429,8 +430,9 @@ sha2_seed(header, header_len, salt, salt header[i] = sha256sum[i % sizeof(sha256sum)]; /* put remaining block into salt. */ - for (i = 0; i < salt_len; i++) - salt[i] = sha256sum[(i + header_len) % sizeof(sha256sum)]; + if (salt != NULL) + for (i = 0; i < salt_len; i++) + salt[i] = sha256sum[(i + header_len) % sizeof(sha256sum)]; } #endif /* FEAT_CRYPT */ diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -392,7 +392,7 @@ struct block_hdr bhdr_T *bh_prev; /* previous block_hdr in used list */ bhdr_T *bh_hash_next; /* next block_hdr in hash list */ bhdr_T *bh_hash_prev; /* previous block_hdr in hash list */ - blocknr_T bh_bnum; /* block number */ + blocknr_T bh_bnum; /* block number */ char_u *bh_data; /* pointer to memory (for used block) */ int bh_page_count; /* number of pages in this block */ @@ -491,12 +491,15 @@ typedef struct # endif } cmdmod_T; +typedef struct file_buffer buf_T; /* forward declaration */ + /* * Simplistic hashing scheme to quickly locate the blocks in the used list. * 64 blocks are found directly (64 * 4K = 256K, most files are smaller). */ #define MEMHASHSIZE 64 #define MEMHASH(nr) ((nr) & (MEMHASHSIZE - 1)) +#define MF_SEED_LEN 8 struct memfile { @@ -516,6 +519,16 @@ struct memfile blocknr_T mf_infile_count; /* number of pages in the file */ unsigned mf_page_size; /* number of bytes in a page */ int mf_dirty; /* TRUE if there are dirty blocks */ +#ifdef FEAT_CRYPT + buf_T *mf_buffer; /* bufer this memfile is for */ + char_u mf_seed[MF_SEED_LEN]; /* seed for encryption */ + + /* Values for key, method and seed used for reading data blocks when + * updating for a newly set key or method. Only when mf_old_key != NULL. */ + char_u *mf_old_key; + int mf_old_cm; + char_u mf_old_seed[MF_SEED_LEN]; +#endif }; /* @@ -1229,8 +1242,6 @@ typedef struct { * A buffer is new if the associated file has never been loaded yet. */ -typedef struct file_buffer buf_T; - struct file_buffer { memline_T b_ml; /* associated memline (also contains line diff --git a/src/testdir/test72.in b/src/testdir/test72.in --- a/src/testdir/test72.in +++ b/src/testdir/test72.in @@ -6,7 +6,7 @@ STARTTEST :so small.vim :" :" Test 'undofile': first a simple one-line change. -:set nocp ul=100 undofile +:set nocp ul=100 undofile nomore :e! Xtestfile ggdGithis is one line:set ul=100 :s/one/ONE/ diff --git a/src/undo.c b/src/undo.c --- a/src/undo.c +++ b/src/undo.c @@ -886,7 +886,10 @@ serialize_header(fp, buf, hash) len = (int)fwrite(header, (size_t)header_len, (size_t)1, fp); vim_free(header); if (len != 1) + { + crypt_pop_state(); return FAIL; + } } else #endif @@ -1240,6 +1243,9 @@ u_write_undo(name, forceit, buf, hash) struct stat st_old; struct stat st_new; #endif +#ifdef FEAT_CRYPT + int do_crypt = FALSE; +#endif if (name == NULL) { @@ -1397,6 +1403,10 @@ u_write_undo(name, forceit, buf, hash) */ if (serialize_header(fp, buf, hash) == FAIL) goto write_error; +#ifdef FEAT_CRYPT + if (*buf->b_p_key) + do_crypt = TRUE; +#endif /* * Iteratively serialize UHPs and their UEPs from the top down. @@ -1462,6 +1472,10 @@ write_error: #endif theend: +#ifdef FEAT_CRYPT + if (do_crypt) + crypt_pop_state(); +#endif if (file_name != name) vim_free(file_name); } @@ -1505,6 +1519,9 @@ u_read_undo(name, hash, orig_name) struct stat st_orig; struct stat st_undo; #endif +#ifdef FEAT_CRYPT + int do_decrypt = FALSE; +#endif if (name == NULL) { @@ -1572,6 +1589,7 @@ u_read_undo(name, hash, orig_name) EMSG2(_("E826: Undo file decryption failed: %s"), file_name); goto error; } + do_decrypt = TRUE; #else EMSG2(_("E827: Undo file is encrypted: %s"), file_name); goto error; @@ -1776,6 +1794,10 @@ error: } theend: +#ifdef FEAT_CRYPT + if (do_decrypt) + crypt_pop_state(); +#endif if (fp != NULL) fclose(fp); if (file_name != name) diff --git a/src/workshop.c b/src/workshop.c --- a/src/workshop.c +++ b/src/workshop.c @@ -1304,13 +1304,15 @@ load_window( } else { +#ifdef FEAT_WINDOWS /* buf is in a window */ if (win != curwin) { win_enter(win, False); - /* wsdebug("load_window: window endter %s\n", + /* wsdebug("load_window: window enter %s\n", win->w_buffer->b_sfname); */ } +#endif if (lnum > 0 && win->w_cursor.lnum != lnum) { warp_to_pc(lnum);