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@
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)
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))
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 \
para_play \
para_recv \
para_server \
+para_upgrade_db \
para_write \
: LDFLAGS += $(lopsub_ldflags)
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.
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);
}
}
*/
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;
.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,
};
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
*/
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));
}
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);
*/
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;
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) {
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)
/** 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),
};
/*
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);
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);
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;
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)
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);
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)
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;
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.
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
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)
}
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,
*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;
{
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;
/* 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;
* 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);
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
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"
faad: $HAVE_FAAD
audio format handlers: $audio_format_handlers
+exe: $executables
para_server: $build_server
para_gui: $build_gui
para_mixer: $build_mixer
* 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);
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.
*
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);
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
--- /dev/null
+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]
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);
+}
--- /dev/null
+/* 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;
+}
[<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>
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.
- 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
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
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.
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,
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.