Improve the checksum code.
[rsync-patches.git] / fix-checksums.diff
1 This patch fixes various issues with the checksum code, such as:
2
3 - changing the xattr code's hashing to obey the transfer checksum choice
4 - size the objects to hold the active checksum size instead of the max
5 - add a negotiated hash method to the daemon auth code.
6
7 To use this patch, run these commands for a successful build:
8
9     patch -p1 <patches/fix-checksums.diff
10     ./configure                                 (optional if already run)
11     make
12
13 based-on: 9cb7529ba60cd59519489ad0fc7fbb69ced6411f
14 diff --git a/authenticate.c b/authenticate.c
15 --- a/authenticate.c
16 +++ b/authenticate.c
17 @@ -24,6 +24,7 @@
18  
19  extern int read_only;
20  extern char *password_file;
21 +extern struct name_num_obj valid_auth_checksums;
22  
23  /***************************************************************************
24  encode a buffer using base64 - simple and slow algorithm. null terminates
25 @@ -72,9 +73,9 @@ static void gen_challenge(const char *addr, char *challenge)
26         SIVAL(input, 20, tv.tv_usec);
27         SIVAL(input, 24, getpid());
28  
29 -       sum_init(-1, 0);
30 +       len = sum_init(valid_auth_checksums.negotiated_num, 0);
31         sum_update(input, sizeof input);
32 -       len = sum_end(digest);
33 +       sum_end(digest);
34  
35         base64_encode(digest, len, challenge, 0);
36  }
37 @@ -86,10 +87,10 @@ static void generate_hash(const char *in, const char *challenge, char *out)
38         char buf[MAX_DIGEST_LEN];
39         int len;
40  
41 -       sum_init(-1, 0);
42 +       len = sum_init(valid_auth_checksums.negotiated_num, 0);
43         sum_update(in, strlen(in));
44         sum_update(challenge, strlen(challenge));
45 -       len = sum_end(buf);
46 +       sum_end(buf);
47  
48         base64_encode(buf, len, out, 0);
49  }
50 @@ -238,6 +239,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
51         if (!users || !*users)
52                 return "";
53  
54 +       negotiate_daemon_auth(f_out);
55         gen_challenge(addr, challenge);
56  
57         io_printf(f_out, "%s%s\n", leader, challenge);
58 @@ -368,6 +370,7 @@ void auth_client(int fd, const char *user, const char *challenge)
59         if (!pass)
60                 pass = "";
61  
62 +       negotiate_daemon_auth(-1);
63         generate_hash(pass, challenge, pass2);
64         io_printf(fd, "%s %s\n", user, pass2);
65  }
66 diff --git a/checksum.c b/checksum.c
67 --- a/checksum.c
68 +++ b/checksum.c
69 @@ -59,8 +59,18 @@ struct name_num_obj valid_checksums = {
70         }
71  };
72  
73 -int xfersum_type = 0; /* used for the file transfer checksums */
74 -int checksum_type = 0; /* used for the pre-transfer (--checksum) checksums */
75 +struct name_num_obj valid_auth_checksums = {
76 +       "daemon auth checksum", NULL, NULL, 0, 0, {
77 +               { CSUM_MD5, "md5", NULL },
78 +               { CSUM_MD4, "md4", NULL },
79 +               { 0, NULL, NULL }
80 +       }
81 +};
82 +
83 +int xfer_sum_type = 0; /* used for the file transfer checksums */
84 +int xfer_sum_len = 0;
85 +int file_sum_type = 0; /* used for the pre-transfer (--checksum) checksums */
86 +int file_sum_len = 0;
87  
88  int parse_csum_name(const char *name, int len)
89  {
90 @@ -99,26 +109,28 @@ static const char *checksum_name(int num)
91  void parse_checksum_choice(int final_call)
92  {
93         if (valid_checksums.negotiated_name)
94 -               xfersum_type = checksum_type = valid_checksums.negotiated_num;
95 +               xfer_sum_type = file_sum_type = valid_checksums.negotiated_num;
96         else {
97                 char *cp = checksum_choice ? strchr(checksum_choice, ',') : NULL;
98                 if (cp) {
99 -                       xfersum_type = parse_csum_name(checksum_choice, cp - checksum_choice);
100 -                       checksum_type = parse_csum_name(cp+1, -1);
101 +                       xfer_sum_type = parse_csum_name(checksum_choice, cp - checksum_choice);
102 +                       file_sum_type = parse_csum_name(cp+1, -1);
103                 } else
104 -                       xfersum_type = checksum_type = parse_csum_name(checksum_choice, -1);
105 +                       xfer_sum_type = file_sum_type = parse_csum_name(checksum_choice, -1);
106                 if (am_server && checksum_choice)
107 -                       validate_choice_vs_env(NSTR_CHECKSUM, xfersum_type, checksum_type);
108 +                       validate_choice_vs_env(NSTR_CHECKSUM, xfer_sum_type, file_sum_type);
109         }
110 +       xfer_sum_len = csum_len_for_type(xfer_sum_type, 0);
111 +       file_sum_len = csum_len_for_type(file_sum_type, 0);
112  
113 -       if (xfersum_type == CSUM_NONE)
114 +       if (xfer_sum_type == CSUM_NONE)
115                 whole_file = 1;
116  
117         /* Snag the checksum name for both write_batch's option output & the following debug output. */
118         if (valid_checksums.negotiated_name)
119                 checksum_choice = valid_checksums.negotiated_name;
120         else if (checksum_choice == NULL)
121 -               checksum_choice = checksum_name(xfersum_type);
122 +               checksum_choice = checksum_name(xfer_sum_type);
123  
124         if (final_call && DEBUG_GTE(NSTR, am_server ? 3 : 1)) {
125                 rprintf(FINFO, "%s%s checksum: %s\n",
126 @@ -204,7 +216,7 @@ uint32 get_checksum1(char *buf1, int32 len)
127  
128  void get_checksum2(char *buf, int32 len, char *sum)
129  {
130 -       switch (xfersum_type) {
131 +       switch (xfer_sum_type) {
132  #ifdef SUPPORT_XXHASH
133           case CSUM_XXH64:
134                 SIVAL64(sum, 0, XXH64(buf, len, checksum_seed));
135 @@ -288,7 +300,7 @@ void get_checksum2(char *buf, int32 len, char *sum)
136                  * are multiples of 64.  This is fixed by calling mdfour_update()
137                  * even when there are no more bytes.
138                  */
139 -               if (len - i > 0 || xfersum_type > CSUM_MD4_BUSTED)
140 +               if (len - i > 0 || xfer_sum_type > CSUM_MD4_BUSTED)
141                         mdfour_update(&m, (uchar *)(buf1+i), len-i);
142  
143                 mdfour_result(&m, (uchar *)sum);
144 @@ -306,7 +318,7 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum)
145         int32 remainder;
146         int fd;
147  
148 -       memset(sum, 0, MAX_DIGEST_LEN);
149 +       memset(sum, 0, file_sum_len);
150  
151         fd = do_open(fname, O_RDONLY, 0);
152         if (fd == -1)
153 @@ -314,7 +326,7 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum)
154  
155         buf = map_file(fd, len, MAX_MAP_SIZE, CHUNK_SIZE);
156  
157 -       switch (checksum_type) {
158 +       switch (file_sum_type) {
159  #ifdef SUPPORT_XXHASH
160           case CSUM_XXH64: {
161                 static XXH64_state_t* state = NULL;
162 @@ -421,7 +433,7 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum)
163                  * are multiples of 64.  This is fixed by calling mdfour_update()
164                  * even when there are no more bytes. */
165                 remainder = (int32)(len - i);
166 -               if (remainder > 0 || checksum_type > CSUM_MD4_BUSTED)
167 +               if (remainder > 0 || file_sum_type > CSUM_MD4_BUSTED)
168                         mdfour_update(&m, (uchar *)map_ptr(buf, i, remainder), remainder);
169  
170                 mdfour_result(&m, (uchar *)sum);
171 @@ -429,7 +441,7 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum)
172           }
173           default:
174                 rprintf(FERROR, "Invalid checksum-choice for --checksum: %s (%d)\n",
175 -                       checksum_name(checksum_type), checksum_type);
176 +                       checksum_name(file_sum_type), file_sum_type);
177                 exit_cleanup(RERR_UNSUPPORTED);
178         }
179  
180 @@ -452,14 +464,16 @@ static XXH64_state_t* xxh64_state;
181  static XXH3_state_t* xxh3_state;
182  #endif
183  static int cursum_type;
184 +static int cursum_len;
185  
186 -void sum_init(int csum_type, int seed)
187 +int sum_init(int csum_type, int seed)
188  {
189         char s[4];
190  
191         if (csum_type < 0)
192                 csum_type = parse_csum_name(NULL, 0);
193         cursum_type = csum_type;
194 +       cursum_len = csum_len_for_type(cursum_type, 0);
195  
196         switch (csum_type) {
197  #ifdef SUPPORT_XXHASH
198 @@ -505,6 +519,8 @@ void sum_init(int csum_type, int seed)
199           default: /* paranoia to prevent missing case values */
200                 exit_cleanup(RERR_UNSUPPORTED);
201         }
202 +
203 +       return cursum_len;
204  }
205  
206  /**
207 @@ -573,11 +589,11 @@ void sum_update(const char *p, int32 len)
208         }
209  }
210  
211 -/* NOTE: all the callers of sum_end() pass in a pointer to a buffer that is
212 - * MAX_DIGEST_LEN in size, so even if the csum-len is shorter than that (i.e.
213 - * CSUM_MD4_ARCHAIC), we don't have to worry about limiting the data we write
214 - * into the "sum" buffer. */
215 -int sum_end(char *sum)
216 +/* The sum buffer only needs to be as long as the current checksum's digest
217 + * len, not MAX_DIGEST_LEN. Note that for CSUM_MD4_ARCHAIC that is the full
218 + * MD4_DIGEST_LEN even if the file-list code is going to ignore all but the
219 + * first 2 bytes of it. */
220 +void sum_end(char *sum)
221  {
222         switch (cursum_type) {
223  #ifdef SUPPORT_XXHASH
224 @@ -620,6 +636,4 @@ int sum_end(char *sum)
225           default: /* paranoia to prevent missing case values */
226                 exit_cleanup(RERR_UNSUPPORTED);
227         }
228 -
229 -       return csum_len_for_type(cursum_type, 0);
230  }
231 diff --git a/clientserver.c b/clientserver.c
232 --- a/clientserver.c
233 +++ b/clientserver.c
234 @@ -67,6 +67,7 @@ extern uid_t our_uid;
235  extern gid_t our_gid;
236  
237  char *auth_user;
238 +char *daemon_auth_choices;
239  int read_only = 0;
240  int module_id = -1;
241  int pid_file_fd = -1;
242 @@ -149,13 +150,9 @@ int start_socket_client(char *host, int remote_argc, char *remote_argv[],
243  static int exchange_protocols(int f_in, int f_out, char *buf, size_t bufsiz, int am_client)
244  {
245         int remote_sub = -1;
246 -#if SUBPROTOCOL_VERSION != 0
247 -       int our_sub = protocol_version < PROTOCOL_VERSION ? 0 : SUBPROTOCOL_VERSION;
248 -#else
249 -       int our_sub = 0;
250 -#endif
251 +       int our_sub = get_subprotocol_version();
252  
253 -       io_printf(f_out, "@RSYNCD: %d.%d\n", protocol_version, our_sub);
254 +       output_daemon_greeting(f_out);
255         if (!am_client) {
256                 char *motd = lp_motd_file();
257                 if (motd && *motd) {
258 @@ -197,6 +194,14 @@ static int exchange_protocols(int f_in, int f_out, char *buf, size_t bufsiz, int
259                 remote_sub = 0;
260         }
261  
262 +       daemon_auth_choices = strchr(buf + 9, ' ');
263 +       if (daemon_auth_choices) {
264 +               char *cp;
265 +               daemon_auth_choices = strdup(daemon_auth_choices + 1);
266 +               if ((cp = strchr(daemon_auth_choices, '\n')) != NULL)
267 +                       *cp = '\0';
268 +       }
269 +
270         if (protocol_version > remote_protocol) {
271                 protocol_version = remote_protocol;
272                 if (remote_sub)
273 diff --git a/compat.c b/compat.c
274 --- a/compat.c
275 +++ b/compat.c
276 @@ -54,19 +54,21 @@ extern int do_compression;
277  extern int do_compression_level;
278  extern int saw_stderr_opt;
279  extern int msgs2stderr;
280 +extern int xfer_sum_type;
281  extern char *shell_cmd;
282  extern char *partial_dir;
283  extern char *files_from;
284  extern char *filesfrom_host;
285  extern const char *checksum_choice;
286  extern const char *compress_choice;
287 +extern char *daemon_auth_choices;
288  extern filter_rule_list filter_list;
289  extern int need_unsorted_flist;
290  #ifdef ICONV_OPTION
291  extern iconv_t ic_send, ic_recv;
292  extern char *iconv_opt;
293  #endif
294 -extern struct name_num_obj valid_checksums;
295 +extern struct name_num_obj valid_checksums, valid_auth_checksums;
296  
297  int remote_protocol = 0;
298  int file_extra_cnt = 0; /* count of file-list extras that everyone gets */
299 @@ -78,6 +80,8 @@ int proper_seed_order = 0;
300  int inplace_partial = 0;
301  int do_negotiated_strings = 0;
302  int xmit_id0_names = 0;
303 +int xattr_sum_type = 0;
304 +int xattr_sum_len = 0;
305  
306  /* These index values are for the file-list's extra-attribute array. */
307  int pathname_ndx, depth_ndx, atimes_ndx, crtimes_ndx, uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
308 @@ -115,6 +119,7 @@ struct name_num_obj valid_compressions = {
309  #define CF_INPLACE_PARTIAL_DIR (1<<6)
310  #define CF_VARINT_FLIST_FLAGS (1<<7)
311  #define CF_ID0_NAMES (1<<8)
312 +#define CF_XATTR_SUM_CHOICE (1<<9)
313  
314  static const char *client_info;
315  
316 @@ -125,11 +130,7 @@ static void check_sub_protocol(void)
317  {
318         char *dot;
319         int their_protocol, their_sub;
320 -#if SUBPROTOCOL_VERSION != 0
321 -       int our_sub = protocol_version < PROTOCOL_VERSION ? 0 : SUBPROTOCOL_VERSION;
322 -#else
323 -       int our_sub = 0;
324 -#endif
325 +       int our_sub = get_subprotocol_version();
326  
327         /* client_info starts with VER.SUB string if client is a pre-release. */
328         if (!(their_protocol = atoi(client_info))
329 @@ -321,13 +322,45 @@ static int parse_nni_str(struct name_num_obj *nno, const char *from, char *tobuf
330         return to - tobuf;
331  }
332  
333 +static int parse_negotiate_str(struct name_num_obj *nno, char *tmpbuf)
334 +{
335 +       struct name_num_item *nni, *ret = NULL;
336 +       int best = nno->saw_len; /* We want best == 1 from the client list, so start with a big number. */
337 +       char *space, *tok = tmpbuf;
338 +       while (tok) {
339 +               while (*tok == ' ') tok++; /* Should be unneeded... */
340 +               if (!*tok)
341 +                       break;
342 +               if ((space = strchr(tok, ' ')) != NULL)
343 +                       *space = '\0';
344 +               nni = get_nni_by_name(nno, tok, -1);
345 +               if (space) {
346 +                       *space = ' ';
347 +                       tok = space + 1;
348 +               } else
349 +                       tok = NULL;
350 +               if (!nni || !nno->saw[nni->num] || best <= nno->saw[nni->num])
351 +                       continue;
352 +               ret = nni;
353 +               best = nno->saw[nni->num];
354 +               if (best == 1 || am_server) /* The server side stops at the first acceptable client choice */
355 +                       break;
356 +       }
357 +       if (ret) {
358 +               free(nno->saw);
359 +               nno->saw = NULL;
360 +               nno->negotiated_name = ret->main_name ? ret->main_name : ret->name;
361 +               nno->negotiated_num = ret->num;
362 +               return 1;
363 +       }
364 +       return 0;
365 +}
366 +
367  /* This routine is always called with a tmpbuf of MAX_NSTR_STRLEN length, but the
368   * buffer may be pre-populated with a "len" length string to use OR a len of -1
369   * to tell us to read a string from the fd. */
370  static void recv_negotiate_str(int f_in, struct name_num_obj *nno, char *tmpbuf, int len)
371  {
372 -       struct name_num_item *ret = NULL;
373 -
374         if (len < 0)
375                 len = read_vstring(f_in, tmpbuf, MAX_NSTR_STRLEN);
376  
377 @@ -338,37 +371,8 @@ static void recv_negotiate_str(int f_in, struct name_num_obj *nno, char *tmpbuf,
378                         rprintf(FINFO, "Server %s list (on client): %s\n", nno->type, tmpbuf);
379         }
380  
381 -       if (len > 0) {
382 -               struct name_num_item *nni;
383 -               int best = nno->saw_len; /* We want best == 1 from the client list, so start with a big number. */
384 -               char *space, *tok = tmpbuf;
385 -               while (tok) {
386 -                       while (*tok == ' ') tok++; /* Should be unneeded... */
387 -                       if (!*tok)
388 -                               break;
389 -                       if ((space = strchr(tok, ' ')) != NULL)
390 -                               *space = '\0';
391 -                       nni = get_nni_by_name(nno, tok, -1);
392 -                       if (space) {
393 -                               *space = ' ';
394 -                               tok = space + 1;
395 -                       } else
396 -                               tok = NULL;
397 -                       if (!nni || !nno->saw[nni->num] || best <= nno->saw[nni->num])
398 -                               continue;
399 -                       ret = nni;
400 -                       best = nno->saw[nni->num];
401 -                       if (best == 1 || am_server) /* The server side stops at the first acceptable client choice */
402 -                               break;
403 -               }
404 -               if (ret) {
405 -                       free(nno->saw);
406 -                       nno->saw = NULL;
407 -                       nno->negotiated_name = ret->main_name ? ret->main_name : ret->name;
408 -                       nno->negotiated_num = ret->num;
409 -                       return;
410 -               }
411 -       }
412 +       if (len > 0 && parse_negotiate_str(nno, tmpbuf))
413 +               return;
414  
415         if (!am_server || !do_negotiated_strings) {
416                 char *cp = tmpbuf;
417 @@ -716,6 +720,8 @@ void setup_protocol(int f_out,int f_in)
418                                 do_negotiated_strings = 1;
419                                 compat_flags |= CF_VARINT_FLIST_FLAGS;
420                         }
421 +                       if (strchr(client_info, 'X') != NULL)
422 +                               compat_flags |= CF_XATTR_SUM_CHOICE;
423                         if (strchr(client_info, 'V') != NULL) { /* Support a pre-release 'V' that got superseded */
424                                 if (!write_batch)
425                                         compat_flags |= CF_VARINT_FLIST_FLAGS;
426 @@ -801,11 +807,66 @@ void setup_protocol(int f_out,int f_in)
427                 checksum_seed = read_int(f_in);
428         }
429  
430 -       parse_checksum_choice(1); /* Sets checksum_type & xfersum_type */
431 +       parse_checksum_choice(1); /* Sets file_sum_type & xfer_sum_type */
432         parse_compress_choice(1); /* Sets do_compression */
433  
434 +       if (compat_flags & CF_XATTR_SUM_CHOICE)
435 +               xattr_sum_type = xfer_sum_type;
436 +       else
437 +               xattr_sum_type = parse_csum_name(NULL, 0);
438 +       xattr_sum_len = csum_len_for_type(xattr_sum_type, 0);
439 +
440         if (write_batch && !am_server)
441                 write_batch_shell_file();
442  
443         init_flist();
444  }
445 +
446 +void output_daemon_greeting(int f_out)
447 +{
448 +       char tmpbuf[MAX_NSTR_STRLEN];
449 +       int our_sub = get_subprotocol_version();
450 +
451 +       get_default_nno_list(&valid_auth_checksums, tmpbuf, MAX_NSTR_STRLEN, '\0');
452 +
453 +       io_printf(f_out, "@RSYNCD: %d.%d %s\n", protocol_version, our_sub, tmpbuf);
454 +}
455 +
456 +void negotiate_daemon_auth(int f_out)
457 +{
458 +       char tmpbuf[MAX_NSTR_STRLEN];
459 +       int save_am_server = am_server;
460 +       int md4_is_old = 0;
461 +
462 +       if (f_out >= 0)
463 +               am_server = 1;
464 +
465 +       if (daemon_auth_choices)
466 +               strlcpy(tmpbuf, daemon_auth_choices, MAX_NSTR_STRLEN);
467 +       else {
468 +               strlcpy(tmpbuf, protocol_version >= 30 ? "md5" : "md4", MAX_NSTR_STRLEN);
469 +               md4_is_old = 1;
470 +       }
471 +
472 +       if (am_server) {
473 +               if (!parse_negotiate_str(&valid_auth_checksums, tmpbuf)) {
474 +                       get_default_nno_list(&valid_auth_checksums, tmpbuf, MAX_NSTR_STRLEN, '\0');
475 +                       io_printf(f_out, "@ERROR: your client does not support one of our daemon-auth checksums: %s\n",
476 +                                 tmpbuf);
477 +                       exit_cleanup(RERR_UNSUPPORTED);
478 +               }
479 +       } else
480 +               recv_negotiate_str(-1, &valid_auth_checksums, tmpbuf, strlen(tmpbuf));
481 +       am_server = save_am_server;
482 +       if (md4_is_old && valid_auth_checksums.negotiated_num == CSUM_MD4)
483 +               valid_auth_checksums.negotiated_num = CSUM_MD4_OLD;
484 +}
485 +
486 +int get_subprotocol_version()
487 +{
488 +#if SUBPROTOCOL_VERSION != 0
489 +       return protocol_version < PROTOCOL_VERSION ? 0 : SUBPROTOCOL_VERSION;
490 +#else
491 +       return 0;
492 +#endif
493 +}
494 diff --git a/flist.c b/flist.c
495 --- a/flist.c
496 +++ b/flist.c
497 @@ -33,7 +33,7 @@ extern int am_sender;
498  extern int am_generator;
499  extern int inc_recurse;
500  extern int always_checksum;
501 -extern int checksum_type;
502 +extern int file_sum_type;
503  extern int module_id;
504  extern int ignore_errors;
505  extern int numeric_ids;
506 @@ -145,7 +145,8 @@ void init_flist(void)
507                 rprintf(FINFO, "FILE_STRUCT_LEN=%d, EXTRA_LEN=%d\n",
508                         (int)FILE_STRUCT_LEN, (int)EXTRA_LEN);
509         }
510 -       flist_csum_len = csum_len_for_type(checksum_type, 1);
511 +       /* Note that this isn't identical to file_sum_len in the case of CSUM_MD4_ARCHAIC: */
512 +       flist_csum_len = csum_len_for_type(file_sum_type, 1);
513  
514         show_filelist_progress = INFO_GTE(FLIST, 1) && xfer_dirs && !am_server && !inc_recurse;
515  }
516 diff --git a/log.c b/log.c
517 --- a/log.c
518 +++ b/log.c
519 @@ -36,8 +36,8 @@ extern int protocol_version;
520  extern int always_checksum;
521  extern int preserve_mtimes;
522  extern int msgs2stderr;
523 -extern int xfersum_type;
524 -extern int checksum_type;
525 +extern int xfer_sum_type;
526 +extern int file_sum_type;
527  extern int stdout_format_has_i;
528  extern int stdout_format_has_o_or_i;
529  extern int logfile_format_has_i;
530 @@ -680,12 +680,12 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
531                         n = NULL;
532                         if (S_ISREG(file->mode)) {
533                                 if (always_checksum)
534 -                                       n = sum_as_hex(checksum_type, F_SUM(file), 1);
535 +                                       n = sum_as_hex(file_sum_type, F_SUM(file), 1);
536                                 else if (iflags & ITEM_TRANSFER)
537 -                                       n = sum_as_hex(xfersum_type, sender_file_sum, 0);
538 +                                       n = sum_as_hex(xfer_sum_type, sender_file_sum, 0);
539                         }
540                         if (!n) {
541 -                               int sum_len = csum_len_for_type(always_checksum ? checksum_type : xfersum_type,
542 +                               int sum_len = csum_len_for_type(always_checksum ? file_sum_type : xfer_sum_type,
543                                                                 always_checksum);
544                                 memset(buf2, ' ', sum_len*2);
545                                 buf2[sum_len*2] = '\0';
546 diff --git a/match.c b/match.c
547 --- a/match.c
548 +++ b/match.c
549 @@ -24,7 +24,8 @@
550  
551  extern int checksum_seed;
552  extern int append_mode;
553 -extern int xfersum_type;
554 +extern int xfer_sum_type;
555 +extern int xfer_sum_len;
556  
557  int updating_basis_file;
558  char sender_file_sum[MAX_DIGEST_LEN];
559 @@ -356,15 +357,13 @@ static void hash_search(int f,struct sum_struct *s,
560   **/
561  void match_sums(int f, struct sum_struct *s, struct map_struct *buf, OFF_T len)
562  {
563 -       int sum_len;
564 -
565         last_match = 0;
566         false_alarms = 0;
567         hash_hits = 0;
568         matches = 0;
569         data_transfer = 0;
570  
571 -       sum_init(xfersum_type, checksum_seed);
572 +       sum_init(xfer_sum_type, checksum_seed);
573  
574         if (append_mode > 0) {
575                 if (append_mode == 2) {
576 @@ -405,22 +404,22 @@ void match_sums(int f, struct sum_struct *s, struct map_struct *buf, OFF_T len)
577                 matched(f, s, buf, len, -1);
578         }
579  
580 -       sum_len = sum_end(sender_file_sum);
581 +       sum_end(sender_file_sum);
582  
583         /* If we had a read error, send a bad checksum.  We use all bits
584          * off as long as the checksum doesn't happen to be that, in
585          * which case we turn the last 0 bit into a 1. */
586         if (buf && buf->status != 0) {
587                 int i;
588 -               for (i = 0; i < sum_len && sender_file_sum[i] == 0; i++) {}
589 -               memset(sender_file_sum, 0, sum_len);
590 -               if (i == sum_len)
591 +               for (i = 0; i < xfer_sum_len && sender_file_sum[i] == 0; i++) {}
592 +               memset(sender_file_sum, 0, xfer_sum_len);
593 +               if (i == xfer_sum_len)
594                         sender_file_sum[i-1]++;
595         }
596  
597         if (DEBUG_GTE(DELTASUM, 2))
598                 rprintf(FINFO,"sending file_sum\n");
599 -       write_buf(f, sender_file_sum, sum_len);
600 +       write_buf(f, sender_file_sum, xfer_sum_len);
601  
602         if (DEBUG_GTE(DELTASUM, 2)) {
603                 rprintf(FINFO, "false_alarms=%d hash_hits=%d matches=%d\n",
604 diff --git a/options.c b/options.c
605 --- a/options.c
606 +++ b/options.c
607 @@ -3006,6 +3006,7 @@ int maybe_add_e_option(char *buf, int buf_len)
608                 buf[x++] = 'I'; /* support inplace_partial behavior */
609                 buf[x++] = 'v'; /* use varint for flist & compat flags; negotiate checksum */
610                 buf[x++] = 'u'; /* include name of uid 0 & gid 0 in the id map */
611 +               buf[x++] = 'X'; /* xattr checksums use negotiated checksum not protocol default */
612  
613                 /* NOTE: Avoid using 'V' -- it was represented with the high bit of a write_byte() that became a write_varint(). */
614         }
615 diff --git a/receiver.c b/receiver.c
616 --- a/receiver.c
617 +++ b/receiver.c
618 @@ -56,7 +56,8 @@ extern int inplace;
619  extern int inplace_partial;
620  extern int allowed_lull;
621  extern int delay_updates;
622 -extern int xfersum_type;
623 +extern int xfer_sum_type;
624 +extern int xfer_sum_len;
625  extern BOOL want_progress_now;
626  extern mode_t orig_umask;
627  extern struct stats stats;
628 @@ -240,7 +241,6 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
629         static char file_sum1[MAX_DIGEST_LEN];
630         struct map_struct *mapbuf;
631         struct sum_struct sum;
632 -       int sum_len;
633         int32 len;
634         OFF_T total_size = F_LENGTH(file);
635         OFF_T offset = 0;
636 @@ -280,7 +280,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
637         } else
638                 mapbuf = NULL;
639  
640 -       sum_init(xfersum_type, checksum_seed);
641 +       sum_init(xfer_sum_type, checksum_seed);
642  
643         if (append_mode > 0) {
644                 OFF_T j;
645 @@ -393,7 +393,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
646         if (INFO_GTE(PROGRESS, 1))
647                 end_progress(total_size);
648  
649 -       sum_len = sum_end(file_sum1);
650 +       sum_end(file_sum1);
651  
652         if (do_fsync && fd != -1 && fsync(fd) != 0) {
653                 rsyserr(FERROR, errno, "fsync failed on %s", full_fname(fname));
654 @@ -403,10 +403,10 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
655         if (mapbuf)
656                 unmap_file(mapbuf);
657  
658 -       read_buf(f_in, sender_file_sum, sum_len);
659 +       read_buf(f_in, sender_file_sum, xfer_sum_len);
660         if (DEBUG_GTE(DELTASUM, 2))
661                 rprintf(FINFO,"got file_sum\n");
662 -       if (fd != -1 && memcmp(file_sum1, sender_file_sum, sum_len) != 0)
663 +       if (fd != -1 && memcmp(file_sum1, sender_file_sum, xfer_sum_len) != 0)
664                 return 0;
665         return 1;
666  }
667 diff --git a/rsync.h b/rsync.h
668 --- a/rsync.h
669 +++ b/rsync.h
670 @@ -826,6 +826,7 @@ extern int uid_ndx;
671  extern int gid_ndx;
672  extern int acls_ndx;
673  extern int xattrs_ndx;
674 +extern int file_sum_len;
675  
676  #ifdef USE_FLEXIBLE_ARRAY
677  #define FILE_STRUCT_LEN (sizeof (struct file_struct))
678 @@ -836,7 +837,7 @@ extern int xattrs_ndx;
679  #define DEV_EXTRA_CNT 2
680  #define DIRNODE_EXTRA_CNT 3
681  #define EXTRA64_CNT ((sizeof (union file_extras64) + EXTRA_LEN - 1) / EXTRA_LEN)
682 -#define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
683 +#define SUM_EXTRA_CNT ((file_sum_len + EXTRA_LEN - 1) / EXTRA_LEN)
684  
685  #define REQ_EXTRA(f,ndx) ((union file_extras*)(f) - (ndx))
686  #define OPT_EXTRA(f,bump) ((union file_extras*)(f) - file_extra_cnt - 1 - (bump))
687 diff --git a/xattrs.c b/xattrs.c
688 --- a/xattrs.c
689 +++ b/xattrs.c
690 @@ -38,6 +38,8 @@ extern int preserve_devices;
691  extern int preserve_specials;
692  extern int checksum_seed;
693  extern int saw_xattr_filter;
694 +extern int xattr_sum_type;
695 +extern int xattr_sum_len;
696  
697  #define RSYNC_XAL_INITIAL 5
698  #define RSYNC_XAL_LIST_INITIAL 100
699 @@ -270,7 +272,7 @@ static int rsync_xal_get(const char *fname, item_list *xalp)
700                 if (datum_len > MAX_FULL_DATUM) {
701                         /* For large datums, we store a flag and a checksum. */
702                         name_offset = 1 + MAX_DIGEST_LEN;
703 -                       sum_init(-1, checksum_seed);
704 +                       sum_init(xattr_sum_type, checksum_seed);
705                         sum_update(ptr, datum_len);
706                         free(ptr);
707  
708 @@ -382,7 +384,7 @@ static int64 xattr_lookup_hash(const item_list *xalp)
709         for (i = 0; i < xalp->count; i++) {
710                 key += hashlittle(rxas[i].name, rxas[i].name_len);
711                 if (rxas[i].datum_len > MAX_FULL_DATUM)
712 -                       key += hashlittle(rxas[i].datum, MAX_DIGEST_LEN);
713 +                       key += hashlittle(rxas[i].datum, xattr_sum_len);
714                 else
715                         key += hashlittle(rxas[i].datum, rxas[i].datum_len);
716         }
717 @@ -435,7 +437,7 @@ static int find_matching_xattr(const item_list *xalp)
718                         if (rxas1[j].datum_len > MAX_FULL_DATUM) {
719                                 if (memcmp(rxas1[j].datum + 1,
720                                            rxas2[j].datum + 1,
721 -                                          MAX_DIGEST_LEN) != 0)
722 +                                          xattr_sum_len) != 0)
723                                         break;
724                         } else {
725                                 if (memcmp(rxas1[j].datum, rxas2[j].datum,
726 @@ -535,7 +537,7 @@ int send_xattr(int f, stat_x *sxp)
727  #endif
728                         write_buf(f, name, name_len);
729                         if (rxa->datum_len > MAX_FULL_DATUM)
730 -                               write_buf(f, rxa->datum + 1, MAX_DIGEST_LEN);
731 +                               write_buf(f, rxa->datum + 1, xattr_sum_len);
732                         else
733                                 write_bigbuf(f, rxa->datum, rxa->datum_len);
734                 }
735 @@ -588,7 +590,7 @@ int xattr_diff(struct file_struct *file, stat_x *sxp, int find_all)
736                 else if (snd_rxa->datum_len > MAX_FULL_DATUM) {
737                         same = cmp == 0 && snd_rxa->datum_len == rec_rxa->datum_len
738                             && memcmp(snd_rxa->datum + 1, rec_rxa->datum + 1,
739 -                                     MAX_DIGEST_LEN) == 0;
740 +                                     xattr_sum_len) == 0;
741                         /* Flag unrequested items that we need. */
742                         if (!same && find_all && snd_rxa->datum[0] == XSTATE_ABBREV)
743                                 snd_rxa->datum[0] = XSTATE_TODO;
744 @@ -797,7 +799,7 @@ void receive_xattr(int f, struct file_struct *file)
745                 rsync_xa *rxa;
746                 size_t name_len = read_varint(f);
747                 size_t datum_len = read_varint(f);
748 -               size_t dget_len = datum_len > MAX_FULL_DATUM ? 1 + MAX_DIGEST_LEN : datum_len;
749 +               size_t dget_len = datum_len > MAX_FULL_DATUM ? 1 + (size_t)xattr_sum_len : datum_len;
750                 size_t extra_len = MIGHT_NEED_RPRE ? RPRE_LEN : 0;
751                 if (SIZE_MAX - dget_len < extra_len || SIZE_MAX - dget_len - extra_len < name_len)
752                         overflow_exit("receive_xattr");
753 @@ -812,7 +814,7 @@ void receive_xattr(int f, struct file_struct *file)
754                         read_buf(f, ptr, dget_len);
755                 else {
756                         *ptr = XSTATE_ABBREV;
757 -                       read_buf(f, ptr + 1, MAX_DIGEST_LEN);
758 +                       read_buf(f, ptr + 1, xattr_sum_len);
759                 }
760  
761                 if (saw_xattr_filter) {
762 @@ -958,7 +960,6 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
763                 name = rxas[i].name;
764  
765                 if (XATTR_ABBREV(rxas[i])) {
766 -                       int sum_len;
767                         /* See if the fnamecmp version is identical. */
768                         len = name_len = rxas[i].name_len;
769                         if ((ptr = get_xattr_data(fnamecmp, name, &len, 1)) == NULL) {
770 @@ -975,10 +976,10 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
771                                 goto still_abbrev;
772                         }
773  
774 -                       sum_init(-1, checksum_seed);
775 +                       sum_init(xattr_sum_type, checksum_seed);
776                         sum_update(ptr, len);
777 -                       sum_len = sum_end(sum);
778 -                       if (memcmp(sum, rxas[i].datum + 1, sum_len) != 0) {
779 +                       sum_end(sum);
780 +                       if (memcmp(sum, rxas[i].datum + 1, xattr_sum_len) != 0) {
781                                 free(ptr);
782                                 goto still_abbrev;
783                         }