]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 'refs/heads/t/sha256'
authorAndre Noll <maan@tuebingen.mpg.de>
Mon, 21 Feb 2022 14:52:42 +0000 (15:52 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Mon, 21 Feb 2022 14:53:52 +0000 (15:53 +0100)
A couple of changes which start to eliminate the use of sha1 in favor
of sha256. This series is only the first step, though, as we need to
keep sha1 for the time being to provide backward compatibility.

Cooking for four months.

* refs/heads/t/sha256:
  manual: Avoid sha1.
  upgrade_db: Add copyright and purpose to upgrade_db.c.
  web: Add link to the para_upgrade_db(1) man page.
  afs: Switch to sha256 and change default database path.
  Add para_upgrade_db.
  Use sha256 for the challenge response.
  Introduce hash2 (sha256).
  Assume sideband and aes_ctr128 are always supported/requested.

17 files changed:
Makefile.in
Makefile.real
NEWS.md
afs.c
aft.c
client_common.c
command.c
configure.ac
crypt.h
crypt_common.c
gcrypt.c
m4/lls/server.suite.m4
m4/lls/upgrade_db.suite.m4 [new file with mode: 0644]
openssl.c
upgrade_db.c [new file with mode: 0644]
web/documentation.in.html
web/manual.md

index 11fa2001d1e6678cdc8968d58d6729d7ce0955b4..c618561d4dfeb18ebeb31f93a90ac69e32f84c63 100644 (file)
@@ -23,6 +23,7 @@ audiod_objs := @audiod_objs@
 audioc_objs := @audioc_objs@
 mixer_objs := @mixer_objs@
 server_objs := @server_objs@
+upgrade_db_objs := @upgrade_db_objs@
 write_objs := @write_objs@
 afh_objs := @afh_objs@
 play_objs := @play_objs@
index b4af64e42c0c8325da84ec6fb10a241696fc2182..6e8084d542b8888e2a838671b6a9be845ac2cae2 100644 (file)
@@ -55,6 +55,7 @@ gui_objs += gui.lsg.o
 play_objs += $(addsuffix _cmd.lsg.o, recv filter play write) play.lsg.o
 recv_objs += recv_cmd.lsg.o recv.lsg.o
 server_objs += server_cmd.lsg.o server.lsg.o
+upgrade_db_objs += upgrade_db.lsg.o
 write_objs += write_cmd.lsg.o write.lsg.o
 
 cmd_suites := $(addsuffix _cmd, audiod server play recv filter write)
@@ -77,6 +78,7 @@ audiod_objs := $(addprefix $(object_dir)/, $(audiod_objs))
 audioc_objs := $(addprefix $(object_dir)/, $(audioc_objs))
 mixer_objs := $(addprefix $(object_dir)/, $(mixer_objs))
 server_objs := $(addprefix $(object_dir)/, $(server_objs))
+upgrade_db_objs := $(addprefix $(object_dir)/, $(upgrade_db_objs))
 write_objs := $(addprefix $(object_dir)/, $(write_objs))
 afh_objs := $(addprefix $(object_dir)/, $(afh_objs))
 play_objs := $(addprefix $(object_dir)/, $(play_objs))
@@ -262,7 +264,7 @@ para_recv para_afh para_play para_server: LDFLAGS += $(id3tag_ldflags)
 para_write para_play para_audiod \
 : LDFLAGS += $(ao_ldflags) $(pthread_ldflags)
 para_client para_audioc para_play : LDFLAGS += $(readline_ldflags)
-para_server: LDFLAGS += $(osl_ldflags)
+para_server para_upgrade_db: LDFLAGS += $(osl_ldflags)
 para_gui: LDFLAGS += $(curses_ldflags)
 para_server \
 para_client \
@@ -299,6 +301,7 @@ para_gui \
 para_play \
 para_recv \
 para_server \
+para_upgrade_db \
 para_write \
 : LDFLAGS += $(lopsub_ldflags)
 
diff --git a/NEWS.md b/NEWS.md
index a31aeaf18e04b32b496f47cea04988fd74f5536f..9e67364d9d6c2bb4dd8390e4a5ee5a524bbe5157 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -5,6 +5,21 @@ NEWS
 0.7.0 (to be announced) "seismic orbit"
 ---------------------------------------
 
+- Starting with paraslash-0.7.0, the sha256 hash value of each known
+  audio file is stored in the database while older versions employed the
+  sha1 hash algorithm which has been considered insecure since 2005
+  and should no longer be used today. The switch from sha1 to sha256
+  requires users to upgrade their database using the new para_upgrade_db
+  program, followed by re-adding all files to recompute the hashes. With
+  this approach all metadata stored in the database (last played date,
+  num played value, moods, playlists, attributes etc.) are kept. An
+  simpler alternative is to start over from scratch by running the
+  "init" command but this will lose these metadata.
+- Server and client now hash the session keys with sha256 rather
+  than sha1 during the initial handshake. This feature is optional and
+  backwards compatible: old clients can still connect to a new server
+  (using sha1). Also new clients can connect to an old server (again
+  using sha1).
 - The new "duration" keyword of the mood grammar makes it possible to
   impose a constraint on the duration of the admissible files.
 - The long deprecated version 1 mood syntax is no longer supported.
diff --git a/afs.c b/afs.c
index 3d86d1925e97517ee1222d9b3b04f46f541cc688..e1639cc7c1447e0e9f31deff65f79fab1e3239e3 100644 (file)
--- a/afs.c
+++ b/afs.c
@@ -659,7 +659,7 @@ static void get_database_dir(void)
                else {
                        char *home = para_homedir();
                        database_dir = make_message(
-                               "%s/.paraslash/afs_database-0.4", home);
+                               "%s/.paraslash/afs_database-0.7", home);
                        free(home);
                }
        }
diff --git a/aft.c b/aft.c
index c8c98e7ab679b5a5254eed9405afa3cca6cf870e..35a53904a33a85ba484830bc43cf46ec1ae1bd98 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -42,7 +42,7 @@ struct ls_data {
  */
 static struct osl_table *audio_file_table; /* NULL if table not open */
 static struct osl_row *current_aft_row; /* NULL if no audio file open */
-static unsigned char current_hash[HASH_SIZE]; /* only used on sighup */
+static unsigned char current_hash[HASH2_SIZE]; /* only used on sighup */
 
 static char *status_items;
 static char *parser_friendly_status_items;
@@ -226,7 +226,7 @@ static struct osl_column_description aft_cols[] = {
                .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
                .name = "hash",
                .compare_function = aft_hash_compare,
-               .data_size = HASH_SIZE
+               .data_size = HASH2_SIZE
        },
        [AFTCOL_PATH] = {
                .storage_type = OSL_MAPPED_STORAGE,
@@ -251,7 +251,7 @@ static struct osl_column_description aft_cols[] = {
 };
 
 static struct osl_table_description audio_file_table_desc = {
-       .name = "audio_files",
+       .name = "audio-files",
        .num_columns = NUM_AFT_COLUMNS,
        .flags = OSL_LARGE_TABLE,
        .column_descriptions = aft_cols
@@ -442,7 +442,7 @@ int aft_get_row_of_path(const char *path, struct osl_row **row)
  */
 static int aft_get_row_of_hash(unsigned char *hash, struct osl_row **row)
 {
-       const struct osl_object obj = {.data = hash, .size = HASH_SIZE};
+       const struct osl_object obj = {.data = hash, .size = HASH2_SIZE};
        return osl(osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row));
 }
 
@@ -838,7 +838,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
        char duration_buf[30]; /* nobody has an audio file long enough to overflow this */
        struct afs_info *afsi = &d->afsi;
        struct afh_info *afhi = &d->afhi;
-       char asc_hash[2 * HASH_SIZE + 1];
+       char asc_hash[2 * HASH2_SIZE + 1];
 
        if (opts->mode == LS_MODE_SHORT) {
                para_printf(b, "%s\n", d->path);
@@ -1041,7 +1041,7 @@ out:
  */
 int open_and_update_audio_file(int *fd)
 {
-       unsigned char file_hash[HASH_SIZE];
+       unsigned char file_hash[HASH2_SIZE];
        struct osl_object afsi_obj;
        struct afs_info new_afsi;
        int ret;
@@ -1101,7 +1101,7 @@ again:
        ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, fd);
        if (ret < 0)
                goto out;
-       hash_function(map.data, map.size, file_hash);
+       hash2_function(map.data, map.size, file_hash);
        ret = hash_compare(file_hash, d->hash);
        para_munmap(map.data, map.size);
        if (ret) {
@@ -1141,7 +1141,7 @@ out:
 static int ls_hash_compare(const void *a, const void *b)
 {
        struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
-       return memcmp(d1->hash, d2->hash, HASH_SIZE);
+       return memcmp(d1->hash, d2->hash, HASH2_SIZE);
 }
 
 static int ls_audio_format_compare(const void *a, const void *b)
@@ -1536,7 +1536,7 @@ enum com_add_buffer_offsets {
        /** The hash of the audio file being added. */
        CAB_HASH_OFFSET = 13,
        /** Start of the path of the audio file. */
-       CAB_PATH_OFFSET = (CAB_HASH_OFFSET + HASH_SIZE),
+       CAB_PATH_OFFSET = (CAB_HASH_OFFSET + HASH2_SIZE),
 };
 
 /*
@@ -1559,7 +1559,7 @@ static void save_add_callback_buffer(unsigned char *hash, const char *path,
 
        assert(size <= ~(uint32_t)0);
        write_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET, audio_format_num);
-       memcpy(buf + CAB_HASH_OFFSET, hash, HASH_SIZE);
+       memcpy(buf + CAB_HASH_OFFSET, hash, HASH2_SIZE);
        strcpy(buf + CAB_PATH_OFFSET, path);
        pos = CAB_PATH_OFFSET + path_len;
        write_u32(buf + CAB_AFHI_OFFSET_POS, pos);
@@ -1635,7 +1635,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
        struct osl_row *hs;
        struct osl_object objs[NUM_AFT_COLUMNS];
        unsigned char *hash;
-       char asc[2 * HASH_SIZE + 1];
+       char asc[2 * HASH2_SIZE + 1];
        int ret;
        char afsi_buf[AFSI_SIZE];
        char *slpr = buf + read_u32(buf + CAB_LPR_OFFSET);
@@ -1652,7 +1652,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
        hash = (unsigned char *)buf + CAB_HASH_OFFSET;
        hash_to_asc(hash, asc);
        objs[AFTCOL_HASH].data = buf + CAB_HASH_OFFSET;
-       objs[AFTCOL_HASH].size = HASH_SIZE;
+       objs[AFTCOL_HASH].size = HASH2_SIZE;
 
        path = buf + CAB_PATH_OFFSET;
        objs[AFTCOL_PATH].data = path;
@@ -1715,7 +1715,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
        objs[AFTCOL_CHUNKS].data = buf + chunks_offset;
        objs[AFTCOL_CHUNKS].size = aca->query.size - chunks_offset;
        if (pb && !hs) { /* update pb's hash */
-               char old_asc[2 * HASH_SIZE + 1];
+               char old_asc[2 * HASH2_SIZE + 1];
                unsigned char *old_hash;
                ret = get_hash_of_row(pb, &old_hash);
                if (ret < 0)
@@ -1820,7 +1820,7 @@ static int add_one_audio_file(const char *path, void *private_data)
        struct afh_info afhi, *afhi_ptr = NULL;
        struct osl_row *pb = NULL, *hs = NULL; /* path brother/hash sister */
        struct osl_object map, obj = {.data = NULL}, query;
-       unsigned char hash[HASH_SIZE];
+       unsigned char hash[HASH2_SIZE];
        bool a_given = SERVER_CMD_OPT_GIVEN(ADD, ALL, pad->lpr);
        bool f_given = SERVER_CMD_OPT_GIVEN(ADD, FORCE, pad->lpr);
        bool l_given = SERVER_CMD_OPT_GIVEN(ADD, LAZY, pad->lpr);
@@ -1848,11 +1848,11 @@ static int add_one_audio_file(const char *path, void *private_data)
        ret = mmap_full_file(path, O_RDONLY, &map.data, &map.size, &fd);
        if (ret < 0)
                goto out_free;
-       hash_function(map.data, map.size, hash);
+       hash2_function(map.data, map.size, hash);
 
        /* Check whether the database contains a file with the same hash. */
        query.data = hash;
-       query.size = HASH_SIZE;
+       query.size = HASH2_SIZE;
        ret = send_callback_request(hash_sister_callback, &query,
                get_row_pointer_from_result, &hs);
        if (ret < 0)
@@ -2517,7 +2517,7 @@ static void aft_close(void)
                        PARA_WARNING_LOG("hash lookup failure\n");
                        current_aft_row = NULL;
                } else
-                       memcpy(current_hash, p, HASH_SIZE);
+                       memcpy(current_hash, p, HASH2_SIZE);
        }
        osl_close_table(audio_file_table, OSL_MARK_CLEAN);
        audio_file_table = NULL;
index c25da96b169126ab36f5b6e7f95a2161d19a3aa9..94a6e86544e63824664b114cc400161d4e223684 100644 (file)
@@ -258,6 +258,11 @@ static int send_sb_command(struct client_task *ct)
        return send_sb(ct, 0, command, len, SBD_COMMAND, false);
 }
 
+static bool has_feature(const char *feature, struct client_task *ct)
+{
+       return find_arg(feature, ct->features) >= 0? true : false;
+}
+
 /*
  * This function reads or writes to the socket file descriptor which
  * corresponds to an established connection between the client and the server.
@@ -290,16 +295,25 @@ static int client_post_select(struct sched *s, void *context)
                ct->status = CL_RECEIVED_WELCOME;
                return 0;
        case CL_RECEIVED_WELCOME: /* send auth command */
+               {
+               /*
+                * Use sha256 iff the server announced the feature. After 0.7.0
+                * we may request and use the feature unconditionally. After
+                * 0.8.0 we no longer need to request the feature.
+                */
+               bool has_sha256;
                if (!FD_ISSET(ct->scc.fd, &s->wfds))
                        return 0;
-               sprintf(buf, AUTH_REQUEST_MSG "%s sideband,aes_ctr128",
-                       ct->user);
+               has_sha256 = has_feature("sha256", ct);
+               sprintf(buf, AUTH_REQUEST_MSG "%s%s", ct->user, has_sha256?
+                       " sha256" : "");
                PARA_INFO_LOG("--> %s\n", buf);
                ret = write_buffer(ct->scc.fd, buf);
                if (ret < 0)
                        goto out;
                ct->status = CL_SENT_AUTH;
                return 0;
+               }
        case CL_SENT_AUTH:
                /*
                 * Receive challenge and session keys, decrypt the challenge and
@@ -325,19 +339,29 @@ static int client_post_select(struct sched *s, void *context)
                free(sbb.iov.iov_base);
                if (ret < 0)
                        goto out;
-               ct->challenge_hash = para_malloc(HASH_SIZE);
-               hash_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash);
+               ct->challenge_hash = para_malloc(HASH2_SIZE);
+
+               if (has_feature("sha256", ct)) {
+                       hash2_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash);
+                       hash2_to_asc(ct->challenge_hash, buf);
+               } else {
+                       hash_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash);
+                       hash_to_asc(ct->challenge_hash, buf);
+               }
                ct->scc.send = sc_new(crypt_buf + APC_CHALLENGE_SIZE, SESSION_KEY_LEN);
                ct->scc.recv = sc_new(crypt_buf + APC_CHALLENGE_SIZE + SESSION_KEY_LEN,
                        SESSION_KEY_LEN);
-               hash_to_asc(ct->challenge_hash, buf);
                PARA_INFO_LOG("--> %s\n", buf);
                ct->status = CL_RECEIVED_CHALLENGE;
                return 0;
                }
        case CL_RECEIVED_CHALLENGE:
-               ret = send_sb(ct, 0, ct->challenge_hash, HASH_SIZE,
-                       SBD_CHALLENGE_RESPONSE, false);
+               if (has_feature("sha256", ct))
+                       ret = send_sb(ct, 0, ct->challenge_hash, HASH2_SIZE,
+                               SBD_CHALLENGE_RESPONSE, false);
+               else
+                       ret = send_sb(ct, 0, ct->challenge_hash, HASH_SIZE,
+                               SBD_CHALLENGE_RESPONSE, false);
                if (ret != 0)
                        ct->challenge_hash = NULL;
                if (ret <= 0)
index 7b2660e3301b80551fb157adf12b3bfa223c7283..5b17f116d53c93956faf60f88d903b233b7e9b69 100644 (file)
--- a/command.c
+++ b/command.c
@@ -750,7 +750,7 @@ static void reset_signals(void)
 }
 
 struct connection_features {
-       int dummy; /* none at the moment */
+       bool sha256_requested; /* can be removed after 0.7.0 */
 };
 
 static int parse_auth_request(char *buf, int len, const struct user **u,
@@ -775,11 +775,22 @@ static int parse_auth_request(char *buf, int len, const struct user **u,
                *p = '\0';
                p++;
                create_argv(p, ",", &features);
+               /*
+                * Still accept sideband and AES feature requests (as a no-op)
+                * because some 0.6.x clients request them. The two checks
+                * below may be removed after 0.7.1.
+                */
                for (i = 0; features[i]; i++) {
                        if (strcmp(features[i], "sideband") == 0)
                                continue;
                        if (strcmp(features[i], "aes_ctr128") == 0)
                                continue;
+                       /*
+                        * ->sha256_requested can go away after 0.7.0 but the
+                        * check has to stay until 0.9.0.
+                        */
+                       if (strcmp(features[i], "sha256") == 0)
+                               cf->sha256_requested = true;
                        else {
                                ret = -E_BAD_FEATURE;
                                goto out;
@@ -877,7 +888,7 @@ int handle_connect(int fd)
 {
        int ret;
        unsigned char rand_buf[APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
-       unsigned char challenge_hash[HASH_SIZE];
+       unsigned char challenge_hash[HASH2_SIZE];
        char *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */;
        size_t numbytes;
        struct command_context cc_struct = {.u = NULL}, *cc = &cc_struct;
@@ -893,7 +904,7 @@ int handle_connect(int fd)
        /* send Welcome message */
        ret = write_va_buffer(fd, "This is para_server, version "
                PACKAGE_VERSION ".\n"
-               "Features: sideband,aes_ctr128\n"
+               "Features: sha256\n" /* no longer announce this after 0.8.0 */
        );
        if (ret < 0)
                goto net_err;
@@ -941,11 +952,19 @@ int handle_connect(int fd)
         * of the random data.
         */
        ret = -E_BAD_AUTH;
-       if (numbytes != HASH_SIZE)
-               goto net_err;
-       hash_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
-       if (memcmp(challenge_hash, buf, HASH_SIZE))
-               goto net_err;
+       if (cf.sha256_requested) {
+               if (numbytes != HASH2_SIZE)
+                       goto net_err;
+               hash2_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
+               if (memcmp(challenge_hash, buf, HASH2_SIZE))
+                       goto net_err;
+       } else { /* old client. This can be removed after 0.7.0 */
+               if (numbytes != HASH_SIZE)
+                       goto net_err;
+               hash_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
+               if (memcmp(challenge_hash, buf, HASH_SIZE))
+                       goto net_err;
+       }
        /* auth successful */
        alarm(0);
        PARA_INFO_LOG("good auth for %s\n", cc->u->name);
index 99e82b2892ef4c3641f91f018c9150582083974d..b817979f39ec79c141cd67eaaef6f49f559b5d21 100644 (file)
@@ -382,7 +382,7 @@ AC_SUBST(ENABLE_UBSAN)
 if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \
                test -n "$FLEX"; then
        build_server="yes"
-       executables="$executables server"
+       executables="$executables server upgrade_db"
        server_errlist_objs="
                server
                afh_common
@@ -441,6 +441,17 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \
 else
        build_server="no"
 fi
+############################################################# upgrade_db
+upgrade_db_objs='
+       crypt_common
+       exec
+       fd
+       string
+       upgrade_db
+       version
+       base64
+'
+AC_SUBST(upgrade_db_objs, add_dot_o($upgrade_db_objs))
 ############################################################# client
 if test -n "$CRYPTOLIB"; then
        build_client="yes"
@@ -859,6 +870,7 @@ id3 version 2 support: $HAVE_ID3TAG
 faad: $HAVE_FAAD
 audio format handlers: $audio_format_handlers
 
+exe: $executables
 para_server: $build_server
 para_gui: $build_gui
 para_mixer: $build_mixer
diff --git a/crypt.h b/crypt.h
index 9623a0035a3b1a89818bd99c3c09c1dac041519b..5ca6a54112b60f6d61b16485248fb28ff708df6b 100644 (file)
--- a/crypt.h
+++ b/crypt.h
@@ -200,3 +200,42 @@ void hash_to_asc(const unsigned char *hash, char *asc);
  * less than or equal to h2, respectively.
  */
 int hash_compare(const unsigned char *h1, const unsigned char *h2);
+
+/** Size of the hash value in bytes. */
+#define HASH2_SIZE 32
+
+/**
+ * Compute the hash2 of the given input data.
+ *
+ * \param data Pointer to the data to compute the hash value from.
+ * \param len The length of \a data in bytes.
+ * \param hash Result pointer.
+ *
+ * \a hash must point to an area at least \p HASH2_SIZE bytes large.
+ *
+ * \sa sha(3), openssl(1).
+ * */
+void hash2_function(const char *data, unsigned long len, unsigned char *hash);
+
+/**
+ * Convert a hash2 value to ascii format.
+ *
+ * \param hash the hash value.
+ * \param asc Result pointer.
+ *
+ * \a asc must point to an area of at least 2 * \p HASH2_SIZE + 1 bytes which
+ * will be filled by the function with the ascii representation of the hash
+ * value given by \a hash, and a terminating \p NULL byte.
+ */
+void hash2_to_asc(const unsigned char *hash, char *asc);
+
+/**
+ * Compare two version 2 hashes.
+ *
+ * \param h1 Pointer to the first hash value.
+ * \param h2 Pointer to the second hash value.
+ *
+ * \return 1, -1, or zero, depending on whether \a h1 is greater than,
+ * less than or equal to h2, respectively.
+ */
+int hash2_compare(const unsigned char *h1, const unsigned char *h2);
index ff24e356ab8d837f8d59d67a3c983d5264376db1..3a44dbdddcd1cf59108ba110c8d83c4fac11d609 100644 (file)
@@ -160,6 +160,31 @@ int hash_compare(const unsigned char *h1, const unsigned char *h2)
        return 0;
 }
 
+void hash2_to_asc(const unsigned char *hash, char *asc)
+{
+       int i;
+       const char hexchar[] = "0123456789abcdef";
+
+       for (i = 0; i < HASH2_SIZE; i++) {
+               asc[2 * i] = hexchar[hash[i] >> 4];
+               asc[2 * i + 1] = hexchar[hash[i] & 0xf];
+       }
+       asc[2 * HASH2_SIZE] = '\0';
+}
+
+int hash2_compare(const unsigned char *h1, const unsigned char *h2)
+{
+       int i;
+
+       for (i = 0; i < HASH2_SIZE; i++) {
+               if (h1[i] < h2[i])
+                       return -1;
+               if (h1[i] > h2[i])
+                       return 1;
+       }
+       return 0;
+}
+
 /**
  * Check header of an openssh private key and compute bignum offset.
  *
index dbe4900862fef83a552d9c60d9ac21d4c8253d5e..506f0bb84e6c199d065942553935dc27a8833d93 100644 (file)
--- a/gcrypt.c
+++ b/gcrypt.c
@@ -46,6 +46,22 @@ void hash_function(const char *data, unsigned long len, unsigned char *hash)
        gcry_md_close(handle);
 }
 
+void hash2_function(const char *data, unsigned long len, unsigned char *hash)
+{
+       gcry_error_t gret;
+       gcry_md_hd_t handle;
+       unsigned char *md;
+
+       gret = gcry_md_open(&handle, GCRY_MD_SHA256, 0);
+       assert(gret == 0);
+       gcry_md_write(handle, data, (size_t)len);
+       gcry_md_final(handle);
+       md = gcry_md_read(handle, GCRY_MD_SHA256);
+       assert(md);
+       memcpy(hash, md, HASH2_SIZE);
+       gcry_md_close(handle);
+}
+
 void get_random_bytes_or_die(unsigned char *buf, int num)
 {
        gcry_randomize(buf, (size_t)num, GCRY_STRONG_RANDOM);
index 93e4b573e5ae694581b5f111f1a898d45b6c4f7b..1c941c433b8fcd753beb09f1c619e1976e8c5817 100644 (file)
@@ -133,7 +133,7 @@ version-string = GIT_VERSION()
                typestr = directory
                [help]
                        The directory which contains the database for the audio file
-                       selector. The default is ~/.paraslash/afs_database-0.4.
+                       selector. The default is ~/.paraslash/afs_database-0.7.
 
                        If no database was found, the "init" command must be executed to
                        initialize the database. Once initialized, audio files may added with
diff --git a/m4/lls/upgrade_db.suite.m4 b/m4/lls/upgrade_db.suite.m4
new file mode 100644 (file)
index 0000000..7f46b74
--- /dev/null
@@ -0,0 +1,33 @@
+m4_define(PROGRAM, para_upgrade_db)
+[suite upgrade_db]
+version-string = GIT_VERSION()
+[supercommand para_upgrade_db]
+       purpose = upgrade the paraslash database to version 0.7
+       [description]
+               The database format changes with paraslash-0.7.0. This program converts
+               the database from the older 0.4 format that was used in paraslash 0.4.x
+               through 0.6.x. In has to be executed only once.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(loglevel.m4)
+       [option src-database-dir]
+               summary = location of the old afs database
+               arg_info = required_arg
+               arg_type = string
+               typestr = directory
+               [help]
+                       The directory which contains the database to be converted. The default
+                       is ~/.paraslash/afs_database-0.4.
+               [/help]
+       [option dst-database-dir]
+               summary = location of the new afs database
+               arg_info = required_arg
+               arg_type = string
+               typestr = directory
+               [help]
+                       The directory which contains the converted database after the program
+                       has terminated. The default is ~/.paraslash/afs_database-0.7.
+               [/help]
index 0ad9d7db4e7f035dce140365b2b38abc144b6923..32891cbb012b66c0eaf5b8ca56f2c4288cd3bb1b 100644 (file)
--- a/openssl.c
+++ b/openssl.c
@@ -414,3 +414,11 @@ void hash_function(const char *data, unsigned long len, unsigned char *hash)
        SHA1_Update(&c, data, len);
        SHA1_Final(hash, &c);
 }
+
+void hash2_function(const char *data, unsigned long len, unsigned char *hash)
+{
+       SHA256_CTX c;
+       SHA256_Init(&c);
+       SHA256_Update(&c, data, len);
+       SHA256_Final(hash, &c);
+}
diff --git a/upgrade_db.c b/upgrade_db.c
new file mode 100644 (file)
index 0000000..487d46c
--- /dev/null
@@ -0,0 +1,386 @@
+/* Copyright (C) 2020 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file upgrade_db.c Prepare the paraslash database for paraslash-0.7. */
+
+#include <osl.h>
+#include <lopsub.h>
+#include <regex.h>
+
+#include "upgrade_db.lsg.h"
+#include "para.h"
+#include "error.h"
+#include "string.h"
+#include "fd.h"
+#include "crypt.h"
+#include "version.h"
+
+#define CMD_PTR (lls_cmd(0, upgrade_db_suite))
+#define OPT_RESULT(_name, _lpr) \
+       (lls_opt_result(LSG_UPGRADE_DB_PARA_UPGRADE_DB_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name, _lpr) (lls_opt_given(OPT_RESULT(_name, _lpr)))
+#define OPT_UINT32_VAL(_name, _lpr) (lls_uint32_val(0, OPT_RESULT(_name, _lpr)))
+#define OPT_STRING_VAL(_name, _lpr) (lls_string_val(0, OPT_RESULT(_name, _lpr)))
+
+static int loglevel;
+INIT_STDERR_LOGGING(loglevel);
+
+/** Array of error strings. */
+DEFINE_PARA_ERRLIST;
+
+static void handle_help_flag(struct lls_parse_result *lpr)
+{
+       char *help;
+
+       if (OPT_GIVEN(DETAILED_HELP, lpr))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP, lpr))
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       exit(EXIT_SUCCESS);
+}
+
+static struct stat *path_exists(const char *path)
+{
+       static struct stat sb;
+
+       if (stat(path, &sb) < 0)
+               return NULL;
+       return &sb;
+}
+
+static bool is_dir(const char *path)
+{
+       struct stat *sb = path_exists(path);
+       if (!sb)
+               return false;
+       return (sb->st_mode & S_IFMT) == S_IFDIR;
+}
+
+__noreturn static void die(const char *msg)
+{
+       PARA_EMERG_LOG("%s\n", msg);
+       exit(EXIT_FAILURE);
+}
+
+static int string_compare(const struct osl_object *obj1, const struct osl_object *obj2)
+{
+       const char *str1 = obj1->data;
+       const char *str2 = obj2->data;
+       return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size));
+}
+
+static char *src_db_dir, *dst_db_dir, *src_aft_dir, *dst_aft_dir;
+
+static void set_paths(const struct lls_parse_result *lpr)
+{
+       char *home = para_homedir();
+
+       if (OPT_GIVEN(SRC_DATABASE_DIR, lpr))
+               src_db_dir = para_strdup(OPT_STRING_VAL(SRC_DATABASE_DIR,
+                       lpr));
+       else
+               src_db_dir = make_message(
+                       "%s/.paraslash/afs_database-0.4", home);
+       if (OPT_GIVEN(DST_DATABASE_DIR, lpr))
+               dst_db_dir = para_strdup(OPT_STRING_VAL(DST_DATABASE_DIR,
+                       lpr));
+       else
+               dst_db_dir = make_message(
+                       "%s/.paraslash/afs_database-0.7", home);
+       free(home);
+       src_aft_dir = make_message("%s/audio_files", src_db_dir);
+       dst_aft_dir = make_message("%s/audio-files", src_db_dir);
+       PARA_NOTICE_LOG("source aft dir: %s\n", src_aft_dir);
+       PARA_NOTICE_LOG("destination aft dir: %s\n", dst_aft_dir);
+}
+
+static void check_sanity(void)
+{
+       PARA_INFO_LOG("checking source and destination directories\n");
+       if (!is_dir(src_db_dir))
+               die("source db directory does not exist");
+       if (path_exists(dst_db_dir))
+               die("destination db already exists");
+       if (!is_dir(src_aft_dir))
+               die("source audio file table does not exist");
+       if (path_exists(dst_aft_dir))
+               die("destination audio file table already exists");
+}
+
+/** The columns of the audio file table (both old and new). */
+enum audio_file_table_columns {
+       /** The hash on the content of the audio file. */
+       AFTCOL_HASH,
+       /** The full path in the filesystem. */
+       AFTCOL_PATH,
+       /** The audio file selector info. */
+       AFTCOL_AFSI,
+       /** The audio format handler info. */
+       AFTCOL_AFHI,
+       /** The chunk table info and the chunk table of the audio file. */
+       AFTCOL_CHUNKS,
+       /** The number of columns of this table. */
+       NUM_AFT_COLUMNS
+};
+
+#define AFSI_SIZE 32
+
+static int src_aft_hash_compare(const struct osl_object *obj1,
+               const struct osl_object *obj2)
+{
+       return hash_compare((unsigned char *)obj1->data,
+               (unsigned char *)obj2->data);
+}
+
+static struct osl_column_description src_aft_cols[] = {
+       [AFTCOL_HASH] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
+               .name = "hash",
+               .compare_function = src_aft_hash_compare,
+               .data_size = HASH_SIZE
+       },
+       [AFTCOL_PATH] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_UNIQUE,
+               .name = "path",
+               .compare_function = string_compare,
+       },
+       [AFTCOL_AFSI] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_FIXED_SIZE,
+               .name = "afs_info",
+               .data_size = AFSI_SIZE
+       },
+       [AFTCOL_AFHI] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .name = "afh_info",
+       },
+       [AFTCOL_CHUNKS] = {
+               .storage_type = OSL_DISK_STORAGE,
+               .name = "chunks",
+       }
+};
+
+static struct osl_table_description src_aft_desc = {
+       .name = "audio_files",
+       .num_columns = NUM_AFT_COLUMNS,
+       .flags = OSL_LARGE_TABLE,
+       .column_descriptions = src_aft_cols
+};
+
+static struct osl_table *src_aft, *dst_aft;
+
+static void open_src_aft(void)
+{
+       int ret;
+
+       PARA_NOTICE_LOG("opening: %s\n", src_aft_dir);
+       src_aft_desc.dir = src_db_dir;
+       ret = osl(osl_open_table(&src_aft_desc, &src_aft));
+       if (ret < 0) {
+               PARA_EMERG_LOG("can not open source audio file table: %s\n",
+                        para_strerror(-ret));
+               exit(EXIT_FAILURE);
+       }
+       PARA_INFO_LOG("successfully opened source audio file table\n");
+}
+
+static int dst_aft_hash_compare(const struct osl_object *obj1,
+               const struct osl_object *obj2)
+{
+       return hash2_compare((unsigned char *)obj1->data,
+               (unsigned char *)obj2->data);
+}
+
+/* identical to src_aft_cols except the comparator and the hash size. */
+static struct osl_column_description dst_aft_cols[] = {
+       [AFTCOL_HASH] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
+               .name = "hash",
+               .compare_function = dst_aft_hash_compare,
+               .data_size = HASH2_SIZE
+       },
+       [AFTCOL_PATH] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_UNIQUE,
+               .name = "path",
+               .compare_function = string_compare,
+       },
+       [AFTCOL_AFSI] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_FIXED_SIZE,
+               .name = "afs_info",
+               .data_size = AFSI_SIZE
+       },
+       [AFTCOL_AFHI] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .name = "afh_info",
+       },
+       [AFTCOL_CHUNKS] = {
+               .storage_type = OSL_DISK_STORAGE,
+               .name = "chunks",
+       }
+};
+
+static struct osl_table_description dst_aft_desc = {
+       .name = "audio-files",
+       .num_columns = NUM_AFT_COLUMNS,
+       .flags = OSL_LARGE_TABLE,
+       .column_descriptions = dst_aft_cols
+};
+
+static int create_and_open_dst_aft(void)
+{
+       int ret;
+
+       PARA_NOTICE_LOG("creating %s\n", dst_aft_dir);
+       dst_aft_desc.dir = src_db_dir;
+       ret = osl(osl_create_table(&dst_aft_desc));
+       if (ret < 0) {
+               PARA_EMERG_LOG("could not create destination audio file table\n");
+               return ret;
+       }
+       ret = osl(osl_open_table(&dst_aft_desc, &dst_aft));
+       if (ret < 0) {
+               PARA_EMERG_LOG("could not open destination audio file table: %s\n",
+                        para_strerror(-ret));
+               exit(EXIT_FAILURE);
+       }
+       PARA_INFO_LOG("successfully opened destination audio file table\n");
+       return 0;
+}
+
+static int copy_aft_row(struct osl_row *row, void *data)
+{
+       unsigned *n = data;
+       int i, ret;
+       unsigned char hash2[HASH2_SIZE] = "\0";
+       struct osl_object objs[NUM_AFT_COLUMNS] = {
+               [AFTCOL_HASH] = {.data = hash2, .size = HASH2_SIZE}
+       };
+
+       ret = osl(osl_open_disk_object(src_aft, row, AFTCOL_CHUNKS,
+               objs + AFTCOL_CHUNKS));
+       if (ret < 0) {
+               PARA_ERROR_LOG("can not open disk object: %s\n",
+                       para_strerror(-ret));
+               return ret;
+       }
+       for (i = 0; i < NUM_AFT_COLUMNS; i++) {
+               if (i == AFTCOL_HASH) /* never assign to this index */
+                       continue;
+               if (i == AFTCOL_CHUNKS) /* disk storage object handled above */
+                       continue;
+               /* mapped storage */
+               ret = osl(osl_get_object(src_aft, row, i, objs + i));
+               if (ret < 0) {
+                       PARA_ERROR_LOG("get_object (col = %d): %s\n",
+                               i, para_strerror(-ret));
+                       return ret;
+               }
+               if (i == AFTCOL_PATH)
+                       PARA_DEBUG_LOG("copying %s\n", (char *)objs[i].data);
+       }
+       (*n)++;
+       memcpy(hash2, n, sizeof(*n));
+       ret = osl(osl_add_row(dst_aft, objs));
+       if (ret < 0)
+               PARA_ERROR_LOG("failed to add row: %s\n", para_strerror(-ret));
+       osl_close_disk_object(objs + AFTCOL_CHUNKS);
+       return ret;
+}
+
+static int convert_aft(void)
+{
+       unsigned n;
+       int ret;
+
+       osl_get_num_rows(src_aft, &n);
+       PARA_NOTICE_LOG("converting hash of %u rows to sha256\n", n);
+       n = 0;
+       ret = osl(osl_rbtree_loop(src_aft, AFTCOL_HASH, &n, copy_aft_row));
+       if (ret < 0)
+               PARA_ERROR_LOG("osl_rbtree_loop failed\n");
+       return ret;
+}
+
+static int remove_source_aft(void)
+{
+       pid_t pid;
+       int fds[3] = {-1, -1, -1}; /* no redirection of stdin/stdout/stderr */
+       int ret, wstatus;
+       char *cmdline = make_message("rm -rf %s", src_aft_dir);
+
+       PARA_NOTICE_LOG("removing %s\n", src_aft_dir);
+       ret = para_exec_cmdline_pid(&pid, cmdline, fds);
+       if (ret < 0) {
+               PARA_ERROR_LOG("exec failure\n");
+               goto out;
+       }
+       do {
+               ret = waitpid(pid, &wstatus, 0);
+       } while (ret < 0 && errno == EINTR);
+       if (ret < 0)
+               PARA_ERROR_LOG("waitpid failure\n");
+out:
+       return ret;
+}
+
+static int rename_db(void)
+{
+       PARA_NOTICE_LOG("renaming %s -> %s\n", src_db_dir, dst_db_dir);
+       if (rename(src_db_dir, dst_db_dir) < 0) {
+               int ret = -ERRNO_TO_PARA_ERROR(errno);
+               PARA_ERROR_LOG("rename failed\n");
+               return ret;
+       }
+       return 1;
+}
+
+int main(int argc, char *argv[])
+{
+       struct lls_parse_result *lpr; /* command line */
+       char *errctx;
+       int ret;
+
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
+       if (ret < 0)
+               goto out;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL, lpr);
+       version_handle_flag("recv", OPT_GIVEN(VERSION, lpr));
+       handle_help_flag(lpr);
+       set_paths(lpr);
+       check_sanity();
+       open_src_aft();
+       ret = create_and_open_dst_aft();
+       if (ret < 0)
+               goto close_src_aft;
+       ret = convert_aft();
+       if (ret < 0)
+               goto close_dst_aft;
+       ret = remove_source_aft();
+       if (ret < 0)
+               goto close_dst_aft;
+       ret = rename_db();
+close_dst_aft:
+       osl_close_table(dst_aft, OSL_MARK_CLEAN);
+close_src_aft:
+       PARA_INFO_LOG("closing audio file tables\n");
+       osl_close_table(src_aft, OSL_MARK_CLEAN);
+out:
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       } else {
+               PARA_WARNING_LOG("success. Now start para_server and force-add"
+                       " all audio files.\n");
+       }
+       return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
+}
index e1587afbfe3bd9a0a9c3c10a2d32657794633dc2..8e85f7fc5b260b73b095cbea04f7ff7a72fb2017 100644 (file)
@@ -25,6 +25,7 @@
        [<a href="para_gui.man.html">para_gui</a>]
        [<a href="para_mixer.man.html">para_mixer</a>]
        [<a href="para_play.man.html">para_play</a>]
+       [<a href="para_upgrade_db.man.html">para_upgrade_db</a>]
 </p>
 
 <h2> Source code documentation </h2>
index d73263b3929ba75cef49073011005f2996ab2270..94de694470a2c2d5a4f02aab2eb7c9b558fd333c 100644 (file)
@@ -488,10 +488,10 @@ An empty database is created with
        para_client init
 
 This initializes a couple of empty tables under
-~/.paraslash/afs_database-0.4. You normally don't need to look at these
+~/.paraslash/afs_database-0.7. You normally don't need to look at these
 tables, but it's good to know that you can start from scratch with
 
-       rm -rf ~/.paraslash/afs_database-0.4
+       rm -rf ~/.paraslash/afs_database-0.7
 
 in case something went wrong.
 
@@ -613,10 +613,11 @@ while the second part is the session key.
 
 - para_client receives the encrypted buffer and decrypts it with the
 user's private key, thereby obtaining the challenge buffer and the
-session key. It sends the SHA1 hash value of the challenge back to
-para_server and stores the session key for further use.
+session key. It hashes the challenge buffer with a crytographic hash
+function, sends the hash value back to para_server and stores the
+session key for further use.
 
-- para_server also computes the SHA1 hash of the challenge and compares
+- para_server also computes the hash value of the challenge and compares
 it against what was sent back by the client.
 
 - If the two hashes do not match, the authentication has failed and
@@ -630,7 +631,7 @@ the session key known to both peers.
 paraslash relies on the quality of the pseudo-random bytes provided
 by the crypto library (openssl or libgcrypt), on the security of
 the implementation of the RSA and AES crypto routines and on the
-infeasibility to invert the SHA1 function.
+infeasibility to invert the hash function.
 
 Neither para_server or para_client create RSA keys on their
 own. This has to be done once for each user as sketched in
@@ -804,10 +805,11 @@ This is the most important and usually also the largest table of the
 AFS database. It contains the information needed to stream each audio
 file. In particular the following data is stored for each audio file.
 
-- SHA1 hash value of the audio file contents. This is computed once
-when the file is added to the database. Whenever AFS selects this
-audio file for streaming the hash value is recomputed and checked
-against the value stored in the database to detect content changes.
+- The cryptographic hash value of the audio file contents. This is
+computed once when the file is added to the database. Whenever AFS
+selects this audio file for streaming the hash value is recomputed
+and checked against the value stored in the database to detect
+content changes.
 
 - The time when this audio file was last played.
 
@@ -1147,7 +1149,7 @@ if the "-a" switch is given:
 File renames and content changes
 --------------------------------
 
-Since the audio file selector knows the SHA1 of each audio file that
+Since the audio file selector knows the hash of each audio file that
 has been added to the afs database, it recognizes if the content of
 a file has changed, e.g. because an ID3 tag was added or modified.
 Also, if a file has been renamed or moved to a different location,
@@ -1176,14 +1178,14 @@ may refuse to start again because of "dirty osl tables". In this
 case you'll have to run the oslfsck program of libosl to fix your
 database:
 
-       oslfsck -fd ~/.paraslash/afs_database-0.4
+       oslfsck -fd ~/.paraslash/afs_database-0.7
 
 However, make sure para_server isn't running before executing oslfsck.
 
 If you don't mind to recreate your database you can start
 from scratch by removing the entire database directory, i.e.
 
-       rm -rf ~/.paraslash/afs_database-0.4
+       rm -rf ~/.paraslash/afs_database-0.7
 
 Be aware that this removes all attribute definitions, all playlists
 and all mood definitions and requires to re-initialize the tables.