SMOLNET PORTAL home about changes
iEncrypt/authenticate snapshot metadata - dedup - deduplicating backup program	Err	bitreich.org	70
hgit clone git://bitreich.org/dedup/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/dedup/	URL:git://bitreich.org/dedup/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/dedup/	bitreich.org	70
1Log	/scm/dedup/log.gph	bitreich.org	70
1Files	/scm/dedup/files.gph	bitreich.org	70
1Refs	/scm/dedup/refs.gph	bitreich.org	70
1Tags	/scm/dedup/tag	bitreich.org	70
1README	/scm/dedup/file/README.gph	bitreich.org	70
1LICENSE	/scm/dedup/file/LICENSE.gph	bitreich.org	70
i---	Err	bitreich.org	70
1commit 72ba1a8a75b1990156ec209538cc00498efcb43d	/scm/dedup/commit/72ba1a8a75b1990156ec209538cc00498efcb43d.gph	bitreich.org	70
1parent 9c6cdcc42dc9b0def23ed2839cb528f3867f9cfb	/scm/dedup/commit/9c6cdcc42dc9b0def23ed2839cb528f3867f9cfb.gph	bitreich.org	70
hAuthor: sin <sin@2f30.org>	URL:mailto:sin@2f30.org	bitreich.org	70
iDate:   Sun, 12 May 2019 15:24:55 +0100	Err	bitreich.org	70
i	Err	bitreich.org	70
iEncrypt/authenticate snapshot metadata	Err	bitreich.org	70
i	Err	bitreich.org	70
iDiffstat:	Err	bitreich.org	70
i  M bencrypt.c                          |       2 +-	Err	bitreich.org	70
i  M snap.c                              |     197 +++++++++++++++++++++++++------	Err	bitreich.org	70
i	Err	bitreich.org	70
i2 files changed, 160 insertions(+), 39 deletions(-)	Err	bitreich.org	70
i---	Err	bitreich.org	70
1diff --git a/bencrypt.c b/bencrypt.c	/scm/dedup/file/bencrypt.c.gph	bitreich.org	70
i@@ -112,7 +112,7 @@ becreat(struct bctx *bctx, char *path, int mode)	Err	bitreich.org	70
i                 return -1;	Err	bitreich.org	70
i         }	Err	bitreich.org	70
i 	Err	bitreich.org	70
i-        /* Ensure that if caller requested encryption, a key was provided */	Err	bitreich.org	70
i+        /* Ensure a key has been provided if caller requested encryption */	Err	bitreich.org	70
i         if (type != EDNONETYPE && !param.keyloaded) {	Err	bitreich.org	70
i                 seterr("expected encryption key");	Err	bitreich.org	70
i                 return -1;	Err	bitreich.org	70
1diff --git a/snap.c b/snap.c	/scm/dedup/file/snap.c.gph	bitreich.org	70
i@@ -10,6 +10,7 @@	Err	bitreich.org	70
i #include <stdio.h>	Err	bitreich.org	70
i #include <stdlib.h>	Err	bitreich.org	70
i #include <string.h>	Err	bitreich.org	70
i+#include <strings.h>	Err	bitreich.org	70
i #include <unistd.h>	Err	bitreich.org	70
i 	Err	bitreich.org	70
i #include <sodium.h>	Err	bitreich.org	70
i@@ -18,6 +19,7 @@	Err	bitreich.org	70
i #include "misc.h"	Err	bitreich.org	70
i #include "queue.h"	Err	bitreich.org	70
i #include "snap.h"	Err	bitreich.org	70
i+#include "state.h"	Err	bitreich.org	70
i 	Err	bitreich.org	70
i /* snapshot header constants */	Err	bitreich.org	70
i #define SHDRMAGIC        "SNAPSNAPPYSNOOP"	Err	bitreich.org	70
i@@ -29,7 +31,9 @@	Err	bitreich.org	70
i #define VMAJSHIFT        8	Err	bitreich.org	70
i #define VMAJMASK        0xff	Err	bitreich.org	70
i 	Err	bitreich.org	70
i-#define SHDRSIZE        (NSHDRMAGIC + 24 + 24 + 8 + 8)	Err	bitreich.org	70
i+#define SHDRSIZE        (NSHDRMAGIC + 24 + 8 + 8)	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+extern struct param param;	Err	bitreich.org	70
i 	Err	bitreich.org	70
i /* misc helpers */	Err	bitreich.org	70
i extern int pack(unsigned char *, char *, ...);	Err	bitreich.org	70
i@@ -38,7 +42,6 @@ extern int unpack(unsigned char *, char *, ...);	Err	bitreich.org	70
i /* Snapshot header structure */	Err	bitreich.org	70
i struct shdr {	Err	bitreich.org	70
i         char magic[NSHDRMAGIC];                /* magic number for file(1) */	Err	bitreich.org	70
i-        unsigned char nonce[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES];	Err	bitreich.org	70
i         unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];	Err	bitreich.org	70
i         uint64_t flags;                        /* version number */	Err	bitreich.org	70
i         uint64_t nbd;                        /* number of block hashes */	Err	bitreich.org	70
i@@ -52,6 +55,7 @@ struct mdnode {	Err	bitreich.org	70
i struct sctx {	Err	bitreich.org	70
i         TAILQ_HEAD(mdhead, mdnode) mdhead;        /* list of hashes contained in snapshot */	Err	bitreich.org	70
i         struct mdnode *mdnext;                        /* next hash to be returned via sget() */	Err	bitreich.org	70
i+        int crypto;                                /* when set, snapshots are encrypted */	Err	bitreich.org	70
i         int fd;                                        /* underlying snapshot file descriptor */	Err	bitreich.org	70
i         int rdonly;                                /* when set, ssync() is a no-op */	Err	bitreich.org	70
i         struct shdr shdr;                        /* snapshot header */	Err	bitreich.org	70
i@@ -69,9 +73,8 @@ unpackshdr(int fd, struct shdr *shdr)	Err	bitreich.org	70
i                 return -1;	Err	bitreich.org	70
i         }	Err	bitreich.org	70
i 	Err	bitreich.org	70
i-        n = unpack(buf, "'16'24'24qq",	Err	bitreich.org	70
i+        n = unpack(buf, "'16'24qq",	Err	bitreich.org	70
i                    shdr->magic,	Err	bitreich.org	70
i-                   shdr->nonce,	Err	bitreich.org	70
i                    shdr->header,	Err	bitreich.org	70
i                    &shdr->flags,	Err	bitreich.org	70
i                    &shdr->nbd);	Err	bitreich.org	70
i@@ -87,9 +90,8 @@ packshdr(int fd, struct shdr *shdr)	Err	bitreich.org	70
i         unsigned char buf[SHDRSIZE];	Err	bitreich.org	70
i         int n;	Err	bitreich.org	70
i 	Err	bitreich.org	70
i-        n = pack(buf, "'16'24'24qq",	Err	bitreich.org	70
i+        n = pack(buf, "'16'24qq",	Err	bitreich.org	70
i                  shdr->magic,	Err	bitreich.org	70
i-                 shdr->nonce,	Err	bitreich.org	70
i                  shdr->header,	Err	bitreich.org	70
i                  shdr->flags,	Err	bitreich.org	70
i                  shdr->nbd);	Err	bitreich.org	70
i@@ -103,52 +105,99 @@ packshdr(int fd, struct shdr *shdr)	Err	bitreich.org	70
i }	Err	bitreich.org	70
i 	Err	bitreich.org	70
i static int	Err	bitreich.org	70
i-loadmd(struct sctx *sctx)	Err	bitreich.org	70
i+initmdhead(struct sctx *sctx)	Err	bitreich.org	70
i {	Err	bitreich.org	70
i-        struct mdnode *mdnode;	Err	bitreich.org	70
i+        unsigned char ad[SHDRSIZE];	Err	bitreich.org	70
i+        struct shdr *shdr;	Err	bitreich.org	70
i+        uint64_t i;	Err	bitreich.org	70
i 	Err	bitreich.org	70
i-        mdnode = calloc(1, sizeof(*mdnode));	Err	bitreich.org	70
i-        if (mdnode == NULL) {	Err	bitreich.org	70
i-                seterr("calloc: %s", strerror(errno));	Err	bitreich.org	70
i+        if (lseek(sctx->fd, 0, SEEK_SET) < 0) {	Err	bitreich.org	70
i+                seterr("lseek: %s", strerror(errno));	Err	bitreich.org	70
i                 return -1;	Err	bitreich.org	70
i         }	Err	bitreich.org	70
i-        if (xread(sctx->fd, mdnode->md, MDSIZE) != MDSIZE) {	Err	bitreich.org	70
i-                free(mdnode);	Err	bitreich.org	70
i-                seterr("failed to read block hash: %s", strerror(errno));	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+        if (xread(sctx->fd, ad, sizeof(ad)) != sizeof(ad)) {	Err	bitreich.org	70
i+                seterr("failed to read snapshot header: %s\n", strerror(errno));	Err	bitreich.org	70
i                 return -1;	Err	bitreich.org	70
i         }	Err	bitreich.org	70
i-        TAILQ_INSERT_TAIL(&sctx->mdhead, mdnode, e);	Err	bitreich.org	70
i-        return 0;	Err	bitreich.org	70
i-}	Err	bitreich.org	70
i-	Err	bitreich.org	70
i-static int	Err	bitreich.org	70
i-initmdhead(struct sctx *sctx)	Err	bitreich.org	70
i-{	Err	bitreich.org	70
i-        struct shdr *shdr;	Err	bitreich.org	70
i-        uint64_t i;	Err	bitreich.org	70
i 	Err	bitreich.org	70
i         shdr = &sctx->shdr;	Err	bitreich.org	70
i-        for (i = 0; i < shdr->nbd; i++) {	Err	bitreich.org	70
i-                if (loadmd(sctx) == 0)	Err	bitreich.org	70
i-                        continue;	Err	bitreich.org	70
i+        if (sctx->crypto) {	Err	bitreich.org	70
i+                crypto_secretstream_xchacha20poly1305_state state;	Err	bitreich.org	70
i+                struct shdr *shdr;	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                shdr = &sctx->shdr;	Err	bitreich.org	70
i+                if (crypto_secretstream_xchacha20poly1305_init_pull(&state,	Err	bitreich.org	70
i+                                                                    shdr->header,	Err	bitreich.org	70
i+                                                                    param.key) != 0) {	Err	bitreich.org	70
i+                        seterr("invalid crypto header");	Err	bitreich.org	70
i+                        return -1;	Err	bitreich.org	70
i+                }	Err	bitreich.org	70
i 	Err	bitreich.org	70
i-                /* Cleanup */	Err	bitreich.org	70
i-                while (!TAILQ_EMPTY(&sctx->mdhead)) {	Err	bitreich.org	70
i+                for (i = 0; i < shdr->nbd; i++) {	Err	bitreich.org	70
i+                        unsigned char buf[MDSIZE + crypto_secretstream_xchacha20poly1305_ABYTES];	Err	bitreich.org	70
i+                        unsigned char md[MDSIZE];	Err	bitreich.org	70
i+                        struct mdnode *mdnode;	Err	bitreich.org	70
i+                        unsigned char tag;	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                        if (xread(sctx->fd, buf, sizeof(buf)) != sizeof(buf)) {	Err	bitreich.org	70
i+                                seterr("failed to read block hash: %s", strerror(errno));	Err	bitreich.org	70
i+                                goto err0;	Err	bitreich.org	70
i+                        }	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                        if (crypto_secretstream_xchacha20poly1305_pull(&state, md, NULL, &tag,	Err	bitreich.org	70
i+                                                                       buf, sizeof(buf),	Err	bitreich.org	70
i+                                                                       ad, sizeof(ad)) != 0) {	Err	bitreich.org	70
i+                                seterr("authentication failed");	Err	bitreich.org	70
i+                                goto err0;	Err	bitreich.org	70
i+                        }	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                        mdnode = calloc(1, sizeof(*mdnode));	Err	bitreich.org	70
i+                        if (mdnode == NULL) {	Err	bitreich.org	70
i+                                seterr("calloc: %s", strerror(errno));	Err	bitreich.org	70
i+                                goto err0;	Err	bitreich.org	70
i+                        }	Err	bitreich.org	70
i+                        memcpy(mdnode->md, md, MDSIZE);	Err	bitreich.org	70
i+                        TAILQ_INSERT_TAIL(&sctx->mdhead, mdnode, e);	Err	bitreich.org	70
i+                }	Err	bitreich.org	70
i+        } else {	Err	bitreich.org	70
i+                for (i = 0; i < shdr->nbd; i++) {	Err	bitreich.org	70
i+                        unsigned char md[MDSIZE];	Err	bitreich.org	70
i                         struct mdnode *mdnode;	Err	bitreich.org	70
i 	Err	bitreich.org	70
i-                        mdnode = TAILQ_FIRST(&sctx->mdhead);	Err	bitreich.org	70
i-                        TAILQ_REMOVE(&sctx->mdhead, mdnode, e);	Err	bitreich.org	70
i-                        free(mdnode);	Err	bitreich.org	70
i+                        if (xread(sctx->fd, md, MDSIZE) != MDSIZE) {	Err	bitreich.org	70
i+                                seterr("failed to read block hash: %s", strerror(errno));	Err	bitreich.org	70
i+                                goto err0;	Err	bitreich.org	70
i+                        }	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                        mdnode = calloc(1, sizeof(*mdnode));	Err	bitreich.org	70
i+                        if (mdnode == NULL) {	Err	bitreich.org	70
i+                                seterr("calloc: %s", strerror(errno));	Err	bitreich.org	70
i+                                goto err0;	Err	bitreich.org	70
i+                        }	Err	bitreich.org	70
i+                        memcpy(mdnode->md, md, MDSIZE);	Err	bitreich.org	70
i+                        TAILQ_INSERT_TAIL(&sctx->mdhead, mdnode, e);	Err	bitreich.org	70
i                 }	Err	bitreich.org	70
i-                return -1;	Err	bitreich.org	70
i         }	Err	bitreich.org	70
i         return 0;	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+err0:	Err	bitreich.org	70
i+        /* Cleanup */	Err	bitreich.org	70
i+        while (!TAILQ_EMPTY(&sctx->mdhead)) {	Err	bitreich.org	70
i+                struct mdnode *mdnode;	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                mdnode = TAILQ_FIRST(&sctx->mdhead);	Err	bitreich.org	70
i+                TAILQ_REMOVE(&sctx->mdhead, mdnode, e);	Err	bitreich.org	70
i+                free(mdnode);	Err	bitreich.org	70
i+        }	Err	bitreich.org	70
i+        return -1;	Err	bitreich.org	70
i }	Err	bitreich.org	70
i 	Err	bitreich.org	70
i int	Err	bitreich.org	70
i screat(char *path, int mode, struct sctx **sctx)	Err	bitreich.org	70
i {	Err	bitreich.org	70
i         struct shdr *shdr;	Err	bitreich.org	70
i+        int crypto;	Err	bitreich.org	70
i         int fd;	Err	bitreich.org	70
i 	Err	bitreich.org	70
i         if (path == NULL || sctx == NULL) {	Err	bitreich.org	70
i@@ -156,6 +205,22 @@ screat(char *path, int mode, struct sctx **sctx)	Err	bitreich.org	70
i                 return -1;	Err	bitreich.org	70
i         }	Err	bitreich.org	70
i 	Err	bitreich.org	70
i+        /* Determine algorithm type */	Err	bitreich.org	70
i+        if (strcasecmp(param.ealgo, "none") == 0) {	Err	bitreich.org	70
i+                crypto = 0;	Err	bitreich.org	70
i+        } else if (strcasecmp(param.ealgo, "XChaCha20-Poly1305") == 0) {	Err	bitreich.org	70
i+                crypto = 1;	Err	bitreich.org	70
i+        } else {	Err	bitreich.org	70
i+                seterr("invalid encryption type: %s", param.ealgo);	Err	bitreich.org	70
i+                return -1;	Err	bitreich.org	70
i+        }	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+        /* Ensure a key has been provided if caller requested encryption */	Err	bitreich.org	70
i+        if (crypto && !param.keyloaded) {	Err	bitreich.org	70
i+                seterr("expected encryption key");	Err	bitreich.org	70
i+                return -1;	Err	bitreich.org	70
i+        }	Err	bitreich.org	70
i+	Err	bitreich.org	70
i         if (sodium_init() < 0) {	Err	bitreich.org	70
i                 seterr("sodium_init: failed");	Err	bitreich.org	70
i                 return -1;	Err	bitreich.org	70
i@@ -176,6 +241,7 @@ screat(char *path, int mode, struct sctx **sctx)	Err	bitreich.org	70
i 	Err	bitreich.org	70
i         TAILQ_INIT(&(*sctx)->mdhead);	Err	bitreich.org	70
i         (*sctx)->mdnext = NULL;	Err	bitreich.org	70
i+        (*sctx)->crypto = crypto;	Err	bitreich.org	70
i         (*sctx)->fd = fd;	Err	bitreich.org	70
i 	Err	bitreich.org	70
i         shdr = &(*sctx)->shdr;	Err	bitreich.org	70
i@@ -196,6 +262,7 @@ int	Err	bitreich.org	70
i sopen(char *path, int flags, int mode, struct sctx **sctx)	Err	bitreich.org	70
i {	Err	bitreich.org	70
i         struct shdr *shdr;	Err	bitreich.org	70
i+        int crypto;	Err	bitreich.org	70
i         int fd;	Err	bitreich.org	70
i 	Err	bitreich.org	70
i         if (path == NULL || sctx == NULL) {	Err	bitreich.org	70
i@@ -209,6 +276,16 @@ sopen(char *path, int flags, int mode, struct sctx **sctx)	Err	bitreich.org	70
i                 return -1;	Err	bitreich.org	70
i         }	Err	bitreich.org	70
i 	Err	bitreich.org	70
i+        /* Determine algorithm type */	Err	bitreich.org	70
i+        if (strcasecmp(param.ealgo, "none") == 0) {	Err	bitreich.org	70
i+                crypto = 0;	Err	bitreich.org	70
i+        } else if (strcasecmp(param.ealgo, "XChaCha20-Poly1305") == 0) {	Err	bitreich.org	70
i+                crypto = 1;	Err	bitreich.org	70
i+        } else {	Err	bitreich.org	70
i+                seterr("invalid encryption type: %s", param.ealgo);	Err	bitreich.org	70
i+                return -1;	Err	bitreich.org	70
i+        }	Err	bitreich.org	70
i+	Err	bitreich.org	70
i         if (sodium_init() < 0) {	Err	bitreich.org	70
i                 seterr("sodium_init: failed");	Err	bitreich.org	70
i                 return -1;	Err	bitreich.org	70
i@@ -229,6 +306,7 @@ sopen(char *path, int flags, int mode, struct sctx **sctx)	Err	bitreich.org	70
i 	Err	bitreich.org	70
i         TAILQ_INIT(&(*sctx)->mdhead);	Err	bitreich.org	70
i         (*sctx)->mdnext = NULL;	Err	bitreich.org	70
i+        (*sctx)->crypto = crypto;	Err	bitreich.org	70
i         (*sctx)->fd = fd;	Err	bitreich.org	70
i         (*sctx)->rdonly = 1;	Err	bitreich.org	70
i 	Err	bitreich.org	70
i@@ -340,16 +418,59 @@ ssync(struct sctx *sctx)	Err	bitreich.org	70
i         }	Err	bitreich.org	70
i 	Err	bitreich.org	70
i         shdr = &sctx->shdr;	Err	bitreich.org	70
i-        if (packshdr(sctx->fd, shdr) < 0)	Err	bitreich.org	70
i-                return -1;	Err	bitreich.org	70
i-        TAILQ_FOREACH(mdnode, &sctx->mdhead, e) {	Err	bitreich.org	70
i-                if (xwrite(sctx->fd, mdnode->md, MDSIZE) != MDSIZE) {	Err	bitreich.org	70
i-                        seterr("failed to write block hash: %s",	Err	bitreich.org	70
i-                                strerror(errno));	Err	bitreich.org	70
i+        if (sctx->crypto) {	Err	bitreich.org	70
i+                unsigned char ad[SHDRSIZE];	Err	bitreich.org	70
i+                crypto_secretstream_xchacha20poly1305_state state;	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                crypto_secretstream_xchacha20poly1305_init_push(&state,	Err	bitreich.org	70
i+                                                                shdr->header,	Err	bitreich.org	70
i+                                                                param.key);	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                if (packshdr(sctx->fd, shdr) < 0)	Err	bitreich.org	70
i+                        return -1;	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                if (lseek(sctx->fd, 0, SEEK_SET) < 0) {	Err	bitreich.org	70
i+                        seterr("lseek: %s", strerror(errno));	Err	bitreich.org	70
i+                        return -1;	Err	bitreich.org	70
i+                }	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                if (xread(sctx->fd, ad, sizeof(ad)) != sizeof(ad)) {	Err	bitreich.org	70
i+                        seterr("failed to read snapshot header: %s\n", strerror(errno));	Err	bitreich.org	70
i                         return -1;	Err	bitreich.org	70
i                 }	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                TAILQ_FOREACH(mdnode, &sctx->mdhead, e) {	Err	bitreich.org	70
i+                        unsigned char buf[MDSIZE + crypto_secretstream_xchacha20poly1305_ABYTES];	Err	bitreich.org	70
i+                        unsigned char tag;	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                        if (TAILQ_LAST(&sctx->mdhead, mdhead) == mdnode)	Err	bitreich.org	70
i+                                tag = crypto_secretstream_xchacha20poly1305_TAG_FINAL;	Err	bitreich.org	70
i+                        else	Err	bitreich.org	70
i+                                tag = 0;	Err	bitreich.org	70
i+	Err	bitreich.org	70
i+                        crypto_secretstream_xchacha20poly1305_push(&state,	Err	bitreich.org	70
i+                                                                   buf, NULL,	Err	bitreich.org	70
i+                                                                   mdnode->md, MDSIZE,	Err	bitreich.org	70
i+                                                                   ad, sizeof(ad), tag);	Err	bitreich.org	70
i+                        if (xwrite(sctx->fd, buf, sizeof(buf)) != sizeof(buf)) {	Err	bitreich.org	70
i+                                seterr("failed to write block hash: %s",	Err	bitreich.org	70
i+                                        strerror(errno));	Err	bitreich.org	70
i+                                return -1;	Err	bitreich.org	70
i+                        }	Err	bitreich.org	70
i+                }	Err	bitreich.org	70
i+        } else {	Err	bitreich.org	70
i+                if (packshdr(sctx->fd, shdr) < 0)	Err	bitreich.org	70
i+                        return -1;	Err	bitreich.org	70
i+                TAILQ_FOREACH(mdnode, &sctx->mdhead, e) {	Err	bitreich.org	70
i+                        if (xwrite(sctx->fd, mdnode->md, MDSIZE) != MDSIZE) {	Err	bitreich.org	70
i+                                seterr("failed to write block hash: %s",	Err	bitreich.org	70
i+                                        strerror(errno));	Err	bitreich.org	70
i+                                return -1;	Err	bitreich.org	70
i+                        }	Err	bitreich.org	70
i+                }	Err	bitreich.org	70
i         }	Err	bitreich.org	70
i         fsync(sctx->fd);	Err	bitreich.org	70
i+	Err	bitreich.org	70
i         return 0;	Err	bitreich.org	70
i }	Err	bitreich.org	70
i 	Err	bitreich.org	70
.
Response: text/plain
Original URLgopher://bitreich.org/0/scm/dedup/commit/72ba1a8a75b19901...
Content-Typetext/plain; charset=utf-8