ifeq ("$(origin CC)", "default")
CC := cc
endif
+.ONESHELL:
+.SHELLFLAGS := -ec
LOGLEVELS := LL_DEBUG,LL_INFO,LL_NOTICE,LL_WARNING,LL_ERROR,LL_CRIT,LL_EMERG
vardir := /var/paraslash
$(object_dir) $(man_dir) $(dep_dir) $(m4depdir) $(lls_suite_dir) \
$(yy_build_dir):
- $(Q) $(MKDIR_P) $@
+ @$(MKDIR_P) $@
CPPFLAGS += -DBINDIR='"$(bindir)"'
CPPFLAGS += -DCOPYRIGHT_YEAR='"$(COPYRIGHT_YEAR)"'
# To put more focus on warnings, be less verbose as default
# Use 'make V=1' to see the full commands
ifeq ("$(origin V)", "command line")
- Q :=
+ SAY =
else
- Q := @
+ SAY = @echo '$(strip $(1))'
endif
audiod_commands := $(addprefix $(lls_suite_dir)/, \
$(man_dir)/para_%.1: $(lls_suite_dir)/%.lsg.man \
$(lls_m4_dir)/copyright.m4 | $(man_dir)
- @[ -z "$(Q)" ] || echo 'LLSMAN $<'
- $(Q) cat $< $(all_commands) > $@
- $(Q) $(M4) -D COPYRIGHT_YEAR=$(COPYRIGHT_YEAR) $(lls_m4_dir)/copyright.m4 >> $@
+ $(call SAY, LLSMAN $<)
+ cat $< $(all_commands) > $@
+ $(M4) -D COPYRIGHT_YEAR=$(COPYRIGHT_YEAR) $(lls_m4_dir)/copyright.m4 >> $@
$(object_dir)/%.o: %.c | $(object_dir)
$(object_dir)/compress_filter.o: CFLAGS += -O3
$(object_dir)/%.o: %.c | $(object_dir) $(dep_dir) $(lsg_h) $(yy_h)
- @[ -z "$(Q)" ] || echo 'CC $<'
- $(Q) $(CC) -c -o $@ -MMD -MF $(dep_dir)/$(*F).d -MT $@ $(CPPFLAGS) \
+ $(call SAY, CC $<)
+ $(CC) -c -o $@ -MMD -MF $(dep_dir)/$(*F).d -MT $@ $(CPPFLAGS) \
$(STRICT_CFLAGS) $(CFLAGS) $<
para_recv para_afh para_play para_server: LDFLAGS += $(id3tag_ldflags)
$(foreach exe,$(executables),$(eval para_$(exe): $$($(exe)_objs)))
$(prefixed_executables):
- @[ -z "$(Q)" ] || echo 'LD $@'
- $(Q) $(CC) $^ -o $@ $(LDFLAGS)
+ $(call SAY, LD $@)
+ $(CC) $^ -o $@ $(LDFLAGS)
mostlyclean:
- @[ -z "$(Q)" ] || echo 'MOSTLYCLEAN'
- $(Q) rm -f para_*
- $(Q) rm -rf $(object_dir)
+ $(call SAY, MOSTLYCLEAN)
+ rm -f para_*
+ rm -rf $(object_dir)
clean: mostlyclean
- @[ -z "$(Q)" ] || echo 'CLEAN'
- $(Q) rm -rf $(build_dir)
+ $(call SAY, CLEAN)
+ rm -rf $(build_dir)
distclean: clean
- @[ -z "$(Q)" ] || echo 'DISTCLEAN'
- $(Q) rm -f Makefile autoscan.log config.status config.log
- $(Q) rm -f config.h configure config.h.in
+ $(call SAY, DISTCLEAN)
+ rm -f Makefile autoscan.log config.status config.log
+ rm -f config.h configure config.h.in
maintainer-clean: distclean
- @[ -z "$(Q)" ] || echo 'MAINTAINER-CLEAN'
- $(Q) rm -f *.tar.bz2 *.tar.xz
- $(Q) rm -f GPATH GRTAGS GSYMS GTAGS
+ $(call SAY, MAINTAINER-CLEAN)
+ rm -f *.tar.bz2 *.tar.xz
+ rm -f GPATH GRTAGS GSYMS GTAGS
INSTALL ?= install
INSTALL_PROGRAM ?= $(INSTALL)
$(MKDIR_P) $(DESTDIR)$(vardir) >/dev/null 2>&1 || true # not fatal, so don't complain
$(tarball) dist tarball:
- $(Q) rm -rf $(tarball) $(tarball_pfx)
- $(Q) git archive --format=tar --prefix=$(tarball_pfx)/ HEAD \
- | tar --delete $(tarball_delete) > $(tarball_pfx).tar
- $(Q) $(MKDIR_P) $(tarball_pfx)
- $(Q) ./GIT-VERSION-GEN > $(tarball_pfx)/VERSION
- $(Q) cp $(autocrap) $(tarball_pfx)
- $(Q) tar rf $(tarball_pfx).tar $(tarball_pfx)/*
- $(Q) xz -9 $(tarball_pfx).tar
- $(Q) ls -l $(tarball)
- $(Q) ln -sf $(tarball) paraslash-git.tar.xz
- $(Q) rm -rf $(tarball_pfx)
+ $(call SAY, DIST)
+ rm -rf $(tarball) $(tarball_pfx)
+ git archive --format=tar --prefix=$(tarball_pfx)/ HEAD \
+ | tar --delete $(tarball_delete) > $(tarball_pfx).tar
+ $(MKDIR_P) $(tarball_pfx)
+ ./GIT-VERSION-GEN > $(tarball_pfx)/VERSION
+ cp $(autocrap) $(tarball_pfx)
+ tar rf $(tarball_pfx).tar $(tarball_pfx)/*
+ xz -9 $(tarball_pfx).tar
+ ls -l $(tarball)
+ ln -sf $(tarball) paraslash-git.tar.xz
+ rm -rf $(tarball_pfx)
NEWS
====
+----------------------------------------------
+0.6.3 (to be announced) "generalized activity"
+----------------------------------------------
+
+- The ff command now accepts a negative argument to instruct the
+ virtual streaming system to jump backwards in the current audio
+ stream. The old syntax (e.g., "ff 30-") is still supported but it
+ is deprecated and no longer documented. The compatibility code is
+ sheduled for removal after 0.7.0.
+- para_afh: New option: --preserve to reset the modification time to
+ the value of the original file after meta data modification.
+- Overhaul of the compress filter code. The refined algorithm should
+ reduce clipping. The meaning of --aggressiveness has changed, see the
+ updated and extended documentation of the compress filter for details.
+- Cleanup of the audio format handler code.
+- We now build the tree using the .ONESHELL feature of GNU make,
+ which results in a significant speedup.
+- Two robustness fixes for FreeBSD.
+- para_client now supports RFC4716 private keys as generated with
+ ssh-keygen -m RFC4716. In fact, this key format has been made the
+ default, and the former PEM keys will be depreciated at some point.
+- The ogg audio format handlers learned to detect holes and now report
+ the correct duration also if ogg pages are missing in the file. This
+ affects ogg/vorbis ogg/speex and ogg/opus.
+
--------------------------------------
0.6.2 (2018-06-30) "elastic diversity"
--------------------------------------
}
static const char * const aac_suffixes[] = {"m4a", "mp4", NULL};
+
/**
- * the init function of the aac audio format handler
+ * The audio format handler for the Advanced Audio Codec.
*
- * \param afh pointer to the struct to initialize
+ * This is only compiled in if the faad library is installed.
*/
-void aac_afh_init(struct audio_format_handler *afh)
-{
- afh->get_file_info = aac_get_file_info,
- afh->suffixes = aac_suffixes;
- afh->rewrite_tags = aac_afh_rewrite_tags;
- afh->open = aac_afh_open;
- afh->get_chunk = aac_afh_get_chunk;
- afh->close = aac_afh_close;
-}
+const struct audio_format_handler aac_afh = {
+ .get_file_info = aac_get_file_info,
+ .suffixes = aac_suffixes,
+ .rewrite_tags = aac_afh_rewrite_tags,
+ .open = aac_afh_open,
+ .get_chunk = aac_afh_get_chunk,
+ .close = aac_afh_close,
+};
goto out;
}
ret = xrename(tmp_name, name);
+ if (ret < 0)
+ goto out;
+ if (OPT_GIVEN(PRESERVE)) {
+ struct timespec times[2]; /* [0]: atime, [1]: mtime */
+ times[0].tv_nsec = UTIME_OMIT;
+ times[1] = sb.st_mtim;
+ /*
+ * We might well have written a file of identical size. If we
+ * keep the mtime as well, we might fool backup applications
+ * like rsync which skip files whose size and mtime haven't
+ * changed. So we change the mtime slightly.
+ */
+ times[1].tv_sec++;
+ if (futimens(output_fd, times) < 0) {
+ ret = -ERRNO_TO_PARA_ERROR(errno);
+ goto out;
+ }
+ }
out:
if (ret < 0 && output_fd >= 0)
unlink(tmp_name); /* ignore errors */
loglevel = OPT_UINT32_VAL(LOGLEVEL);
version_handle_flag("afh", OPT_GIVEN(VERSION));
handle_help_flags();
- afh_init();
for (i = 0; i < lls_num_inputs(lpr); i++) {
int ret2;
const char *path = lls_input(i, lpr);
* in the other part of this struct.
*/
struct audio_format_handler {
- /** Name of the audio format. */
- const char *name;
- /**
- * Pointer to the audio format handler's init function.
- *
- * Must initialize all function pointers and is assumed to succeed.
- */
- void (*init)(struct audio_format_handler*);
/** Typical file endings for files that can be handled by this afh. */
const char * const *suffixes;
/**
int output_fd, const char *filename);
};
-void afh_init(void);
int guess_audio_format(const char *name);
int compute_afhi(const char *path, char *data, size_t size,
int fd, struct afh_info *afhi);
#include "string.h"
#include "afh.h"
-typedef void afh_init_func(struct audio_format_handler *);
-
-/*
- * Declaration of the audio format handler init functions.
- *
- * These symbols are referenced in the afl array below.
- *
- * Most audio format handlers depend on an external library and are not
- * compiled in if the library is not installed. Hence it is well possible that
- * not all of these functions are defined. It does not hurt to declare them
- * anyway, and this avoids another set of ifdefs.
- */
-extern afh_init_func mp3_afh_init, ogg_afh_init, aac_afh_init, wma_afh_init,
- spx_afh_init, flac_afh_init, opus_afh_init;
-
/** The list of all status items */
const char *status_item_list[] = {STATUS_ITEMS};
/**
- * The list of supported audio formats.
+ * For each audio file the number of its audio format is stored in the
+ * database. Therefore this list, in particular its order, is part of the ABI.
+ * So it's only OK to append new audio formats. All audio formats are listed
+ * here, regardless of whether the audio format handler is compiled in.
+ */
+#define ALL_AUDIO_FORMATS \
+ AUDIO_FORMAT(mp3) \
+ AUDIO_FORMAT(ogg) \
+ AUDIO_FORMAT(aac) \
+ AUDIO_FORMAT(wma) \
+ AUDIO_FORMAT(spx) \
+ AUDIO_FORMAT(flac) \
+ AUDIO_FORMAT(opus) \
+
+/** \cond audio_format_handler */
+#define AUDIO_FORMAT(_fmt) #_fmt,
+static const char * const audio_format_names[] = {ALL_AUDIO_FORMATS};
+#undef AUDIO_FORMAT
+/* Weak declarations must be public. */
+#define AUDIO_FORMAT(_fmt) \
+ struct audio_format_handler _fmt ## _afh __attribute__ ((weak)) \
+ = {.get_file_info = NULL};
+ALL_AUDIO_FORMATS
+#undef AUDIO_FORMAT
+#define AUDIO_FORMAT(_fmt) & _fmt ## _afh,
+static struct audio_format_handler *afl[] = {ALL_AUDIO_FORMATS};
+#undef AUDIO_FORMAT
+#define NUM_AUDIO_FORMATS (ARRAY_SIZE(afl))
+/** \endcond audio_format_handler */
+
+/**
+ * Get the name of the given audio format.
*
- * We always define the full array of audio formats even if some audio formats
- * were not compiled in. This is because for each audio file the number of its
- * audio format is stored in the database. We don't want these numbers to become
- * stale just because the user installed a new version of paraslash that
- * supports a different set of audio formats.
+ * \param i The audio format number.
*
- * It can still be easily detected whether an audio format is compiled in by
- * checking if the init function pointer is not \p NULL.
+ * \return This returns a pointer to statically allocated memory so it
+ * must not be freed by the caller.
*/
-static struct audio_format_handler afl[] = {
- {
- .name = "mp3",
- .init = mp3_afh_init,
- },
- {
- .name = "ogg",
-#if defined(HAVE_OGG) && defined(HAVE_VORBIS)
- .init = ogg_afh_init,
-#endif
- },
- {
- .name = "aac",
-#if defined(HAVE_FAAD)
- .init = aac_afh_init,
-#endif
- },
- {
- .name = "wma",
- .init = wma_afh_init,
- },
- {
- .name = "spx",
-#if defined(HAVE_OGG) && defined(HAVE_SPEEX)
- .init = spx_afh_init,
-#endif
- },
- {
- .name = "flac",
-#if defined(HAVE_OGG) && defined(HAVE_FLAC)
- .init = flac_afh_init,
-#endif
- },
- {
- .name = "opus",
-#if defined(HAVE_OGG) && defined(HAVE_OPUS)
- .init = opus_afh_init,
-#endif
- },
- {
- .name = NULL,
- }
-};
+const char *audio_format_name(int i)
+{
+ if (i < 0 || i >= NUM_AUDIO_FORMATS)
+ return "???";
+ return audio_format_names[i];
+}
static inline int next_audio_format(int format)
{
for (;;) {
- if (!afl[format].name)
- return format;
format++;
- if (afl[format].init)
+ if (format >= NUM_AUDIO_FORMATS)
+ return format;
+ if (afl[format]->get_file_info)
return format;
}
}
/** Iterate over each supported audio format. */
-#define FOR_EACH_AUDIO_FORMAT(i) for (i = 0; afl[i].name; i = next_audio_format(i))
-
-/**
- * Call the init function of each supported audio format handler.
- */
-void afh_init(void)
-{
- int i;
-
- PARA_NOTICE_LOG("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS);
- FOR_EACH_AUDIO_FORMAT(i) {
- PARA_INFO_LOG("initializing %s handler\n",
- audio_format_name(i));
- afl[i].init(&afl[i]);
- }
-}
+#define FOR_EACH_AUDIO_FORMAT(i) \
+ for (i = 0; i < NUM_AUDIO_FORMATS; i = next_audio_format(i))
/**
* Tell whether an audio format handler provides chunk tables.
*/
bool afh_supports_dynamic_chunks(int audio_format_id)
{
- return afl[audio_format_id].get_chunk;
+ return afl[audio_format_id]->get_chunk;
}
/**
int i,j, len = strlen(name);
FOR_EACH_AUDIO_FORMAT(i) {
- for (j = 0; afl[i].suffixes[j]; j++) {
- const char *p = afl[i].suffixes[j];
+ for (j = 0; afl[i]->suffixes[j]; j++) {
+ const char *p = afl[i]->suffixes[j];
int plen = strlen(p);
if (len < plen + 1)
continue;
return -E_AUDIO_FORMAT;
}
-/**
- * Get the name of the given audio format.
- *
- * \param i The audio format number.
- *
- * \return This returns a pointer to statically allocated memory so it
- * must not be freed by the caller.
- */
-const char *audio_format_name(int i)
-{
- if (i < 0 || i >= ARRAY_SIZE(afl) - 1)
- return "???";
- return afl[i].name;
-}
-
static int get_file_info(int format, const char *path, char *data,
size_t size, int fd, struct afh_info *afhi)
{
const char *fmt = audio_format_name(format);
memset(afhi, 0, sizeof(*afhi));
- ret = afl[format].get_file_info(data, size, fd, afhi);
+ ret = afl[format]->get_file_info(data, size, fd, afhi);
if (ret < 0) {
PARA_WARNING_LOG("%s: %s format not detected: %s\n",
path, fmt, para_strerror(-ret));
uint8_t audio_format_id, const void *map, size_t mapsize,
const char **buf, size_t *len, void **afh_context)
{
- struct audio_format_handler *afh = afl + audio_format_id;
+ struct audio_format_handler *afh = afl[audio_format_id];
if (afh_supports_dynamic_chunks(audio_format_id)) {
int ret;
if (ret < 0)
return ret;
}
- ret = afl[audio_format_id].get_chunk(chunk_num, *afh_context,
+ ret = afh->get_chunk(chunk_num, *afh_context,
buf, len);
if (ret < 0) {
afh->close(*afh_context);
*/
void afh_close(void *afh_context, uint8_t audio_format_id)
{
- struct audio_format_handler *afh = afl + audio_format_id;
+ struct audio_format_handler *afh = afl[audio_format_id];
if (!afh_supports_dynamic_chunks(audio_format_id))
return;
void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
void *map, size_t mapsize, char **buf, size_t *len)
{
- struct audio_format_handler *afh = afl + audio_format_id;
+ struct audio_format_handler *afh = afl[audio_format_id];
if (!map || !afhi || !afhi->header_len) {
*buf = NULL;
*/
void afh_free_header(char *header_buf, uint8_t audio_format_id)
{
- struct audio_format_handler *afh = afl + audio_format_id;
+ struct audio_format_handler *afh = afl[audio_format_id];
if (afh->get_header)
free(header_buf);
int afh_rewrite_tags(int audio_format_id, void *map, size_t mapsize,
struct taginfo *tags, int output_fd, const char *filename)
{
- struct audio_format_handler *afh = afl + audio_format_id;
+ struct audio_format_handler *afh = afl[audio_format_id];
if (!afh->rewrite_tags)
return -ERRNO_TO_PARA_ERROR(ENOTSUP);
return ret;
}
-/** See \ref recv_init(). */
const struct receiver lsg_recv_cmd_com_afh_user_data = {
- .init = afh_init,
.open = afh_recv_open,
.close = afh_recv_close,
.pre_select = afh_recv_pre_select,
{
uint32_t n;
- if (!afhi->chunk_table)
+ if (!afhi->chunk_table || afhi->chunks_total == 0)
return;
for (n = 0; n <= afhi->chunks_total; n++)
write_u32(buf + 4 * n, afhi->chunk_table[n]);
parse_config_or_die();
crypt_init();
daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
- recv_init();
if (daemon_init_colors_or_die(OPT_UINT32_VAL(COLOR), COLOR_AUTO,
COLOR_NO, OPT_GIVEN(LOGFILE))) {
for (i = 0; i < OPT_GIVEN(LOG_COLOR); i++)
ITEM(chunk_time) \
ITEM(num_chunks) \
ITEM(amplification) \
+ ITEM(play_time) \
/*
* Create a set of audio-file related status items with empty values. These are
static int com_ff(struct command_context *cc, struct lls_parse_result *lpr)
{
long promille;
- int ret, backwards = 0;
- unsigned i;
+ int i, ret;
char c, *errctx;
ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
send_errctx(cc, errctx);
return ret;
}
- if (!(ret = sscanf(lls_input(0, lpr), "%u%c", &i, &c)))
- return -E_COMMAND_SYNTAX;
- if (ret > 1 && c == '-')
- backwards = 1; /* jmp backwards */
+ ret = para_atoi32(lls_input(0, lpr), &i);
+ if (ret < 0) {
+ if (ret != -E_ATOI_JUNK_AT_END)
+ return ret;
+ /*
+ * Compatibility code to keep the historic syntax (ff 30-)
+ * working. This can be removed after 0.7.0.
+ */
+ ret = sscanf(lls_input(0, lpr), "%i%c", &i, &c);
+ if (ret <= 0)
+ return -E_COMMAND_SYNTAX;
+ if (ret > 1 && c == '-') {
+ PARA_WARNING_LOG("use of obsolete syntax\n");
+ i = -i;
+ }
+ }
mutex_lock(mmd_mutex);
ret = -E_NO_AUDIO_FILE;
if (!mmd->afd.afhi.chunks_total || !mmd->afd.afhi.seconds_total)
goto out;
ret = 1;
promille = (1000 * mmd->current_chunk) / mmd->afd.afhi.chunks_total;
- if (backwards)
- promille -= 1000 * i / mmd->afd.afhi.seconds_total;
- else
- promille += 1000 * i / mmd->afd.afhi.seconds_total;
+ /*
+ * We need this cast because without it the expression on the right
+ * hand side is of unsigned type.
+ */
+ promille += 1000 * i / (int)mmd->afd.afhi.seconds_total;
if (promille < 0)
promille = 0;
if (promille > 1000) {
size_t length, i;
int16_t *ip, *op;
uint32_t inertia = U32_OPTVAL(INERTIA, fn->lpr);
- unsigned gain_shift = inertia + U32_OPTVAL(DAMP, fn->lpr),
- mask = (1U << U32_OPTVAL(BLOCKSIZE, fn->lpr)) - 1U;
+ unsigned mask = (1U << U32_OPTVAL(BLOCKSIZE, fn->lpr)) - 1U;
//inplace = false;
next_buffer:
ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
neg = true;
}
sample *= pcd->current_gain;
- sample >>= gain_shift;
+ sample >>= inertia + 1;
if (sample > 32767) { /* clip */
+ PARA_WARNING_LOG("clip: %d\n", sample);
sample = 32767;
pcd->current_gain = (3 * pcd->current_gain +
(1 << inertia)) / 4;
pcd->peak = 0;
} else if (sample > pcd->peak)
pcd->peak = sample;
+ sample >>= U32_OPTVAL(DAMP, fn->lpr);
op[i] = neg? -sample : sample;
if (++pcd->num_samples & mask)
continue;
// PARA_DEBUG_LOG("gain: %u, peak: %u\n", pcd->current_gain,
// pcd->peak);
+
if (pcd->peak < U32_OPTVAL(TARGET_LEVEL, fn->lpr)) {
if (pcd->current_gain < pcd->max_gain)
pcd->current_gain++;
fn->private_data = pcd;
fn->min_iqs = 2; /* 16 bit audio */
pcd->current_gain = 1U << inertia;
- pcd->max_gain = 1U << (inertia + aggressiveness);
+ pcd->max_gain = (1U << inertia) * (1.0 + 3.0 * aggressiveness / 10.0);
+}
+
+static void *compress_setup(const struct lls_parse_result *lpr)
+{
+ uint32_t val;
+
+ val = U32_OPTVAL(BLOCKSIZE, lpr);
+ if (val == 0 || val > 31) {
+ PARA_EMERG_LOG("blocksize (%u) out of range\n", val);
+ exit(EXIT_FAILURE);
+ }
+ val = U32_OPTVAL(AGGRESSIVENESS, lpr);
+ if (val > 10) {
+ PARA_EMERG_LOG("aggressiveness (%u) out of range\n", val);
+ exit(EXIT_FAILURE);
+ }
+ val = U32_OPTVAL(INERTIA, lpr);
+ if (val == 0 || val > 14) {
+ PARA_EMERG_LOG("inertia (%u) out of range\n", val);
+ exit(EXIT_FAILURE);
+ }
+ val = U32_OPTVAL(TARGET_LEVEL, lpr);
+ if (val > 32767) {
+ PARA_EMERG_LOG("target-level (%u) out of range\n", val);
+ exit(EXIT_FAILURE);
+ }
+ val = U32_OPTVAL(DAMP, lpr);
+ if (val > 16) {
+ PARA_EMERG_LOG("damp (%u) out of range\n", val);
+ exit(EXIT_FAILURE);
+ }
+ return NULL; /* no need for a config structure */
}
const struct filter lsg_filter_cmd_com_compress_user_data = {
+ .setup = compress_setup,
.open = compress_open,
.close = compress_close,
.pre_select = generic_filter_pre_select,
AC_CHECK_LIB([crypto], [RAND_bytes], [], [HAVE_OPENSSL=no])
LIB_SUBST_FLAGS(openssl)
if test $HAVE_OPENSSL = yes; then
- AC_CHECK_LIB([crypto], [RSA_set0_key],
- AC_DEFINE([HAVE_RSA_SET0_KEY], [1], [openssl-1.1]))
+ HAVE_RSA_SET0_KEY=yes
+ AC_CHECK_DECL([RSA_set0_key], [], [], [#include <openssl/rsa.h>])
+ AC_CHECK_LIB([crypto], [RSA_set0_key], [], [])
+ if test "$ac_cv_have_decl_RSA_set0_key" != "$ac_cv_lib_crypto_RSA_set0_key"; then
+ AC_MSG_ERROR([openssl header/library mismatch])
+ fi
+ test "$ac_cv_have_decl_RSA_set0_key" = yes &&
+ AC_DEFINE([HAVE_RSA_SET0_KEY], [1], [openssl >= 1.1])
+
+ HAVE_CRYPTO_CLEANUP_ALL_EX_DATA=yes
+ AC_CHECK_DECL([CRYPTO_cleanup_all_ex_data], [],
+ [HAVE_CRYPTO_CLEANUP_ALL_EX_DATA=no],
+ [#include <openssl/rsa.h>])
+ AC_CHECK_LIB([crypto], [CRYPTO_cleanup_all_ex_data], [],
+ [HAVE_CRYPTO_CLEANUP_ALL_EX_DATA=no])
+ test $HAVE_CRYPTO_CLEANUP_ALL_EX_DATA = yes &&
+ AC_DEFINE([HAVE_CRYPTO_CLEANUP_ALL_EX_DATA], [1],
+ [not available on FreeBSD 12])
fi
UNSTASH_FLAGS
######################################################################### gcrypt
/** AES block size in bytes. */
#define AES_CRT128_BLOCK_SIZE 16
-int decode_ssh_key(const char *filename, unsigned char **blob,
+int decode_public_key(const char *filename, unsigned char **blob,
size_t *decoded_size);
+int decode_private_key(const char *key_file, unsigned char **result,
+ size_t *blob_size);
+/** Legacy PEM keys (openssh-7.7 and earlier, paraslash.0.6.2 and earlier) */
+#define PKT_PEM (0)
+/** OPENSSH keys (since openssh-7.8, paraslash.0.6.3) */
+#define PKT_OPENSSH (1)
int check_private_key_file(const char *file);
+int find_openssh_bignum_offset(const unsigned char *data, int len);
*
* \sa \ref uudecode().
*/
-int decode_ssh_key(const char *filename, unsigned char **blob,
+int decode_public_key(const char *filename, unsigned char **blob,
size_t *decoded_size)
{
int ret, ret2;
}
return 0;
}
+
+/**
+ * Check header of an openssh private key and compute bignum offset.
+ *
+ * \param data The base64-decoded key.
+ * \param len The size of the decoded key.
+ *
+ * Several assumptions are made about the key. Most notably, we only support
+ * single unencrypted keys without comments.
+ *
+ * \return The offset at which the first bignum of the private key (the public
+ * exponent n) starts. Negative error code on failure.
+ */
+int find_openssh_bignum_offset(const unsigned char *data, int len)
+{
+ /*
+ * Unencrypted keys without comments always start with the below byte
+ * sequence. See PROTOCOL.key of the openssh package.
+ */
+ static const unsigned char valid_openssh_header[] = {
+ /* string "openssh-key-v1" */
+ 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2d, 0x6b, 0x65,
+ 0x79, 0x2d, 0x76, 0x31,
+ /* length of the cipher name */
+ 0x00, 0x00, 0x00, 0x00, 0x04,
+ /* cipher name: "none" */
+ 0x6e, 0x6f, 0x6e, 0x65,
+ /* length of the kdfname (only used for encrypted keys) */
+ 0x00, 0x00, 0x00, 0x04,
+ /* kdfname: "none" */
+ 0x6e, 0x6f, 0x6e, 0x65,
+ /* length of kdfoptions */
+ 0x00, 0x00, 0x00, 0x00,
+ /* number of keys */
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ uint32_t val;
+ const unsigned char *p, *end = data + len;
+
+ if (len <= sizeof(valid_openssh_header) + 4)
+ return -E_OPENSSH_PARSE;
+ if (memcmp(data, valid_openssh_header, sizeof(valid_openssh_header)))
+ return -E_OPENSSH_PARSE;
+ p = data + sizeof(valid_openssh_header);
+ /* length of public key */
+ val = read_u32_be(p);
+ if (val > end - p - 4)
+ return -E_OPENSSH_PARSE;
+ p += val + 4;
+ /* length of private key */
+ val = read_u32_be(p);
+ if (val > end - p - 4)
+ return -E_OPENSSH_PARSE;
+ p += 4;
+ /* two equal random integers ("checkint") */
+ if (p + 8 > end)
+ return -E_OPENSSH_PARSE;
+ if (read_u32_be(p) != read_u32_be(p + 4))
+ return -E_OPENSSH_PARSE;
+ p += 8;
+ /* length of name of key type "ssh-rsa" */
+ if (p + 11 > end)
+ return -E_OPENSSH_PARSE;
+ if (read_u32_be(p) != 7)
+ return -E_OPENSSH_PARSE;
+ if (memcmp(p + 4, "ssh-rsa", 7))
+ return -E_OPENSSH_PARSE;
+ p += 11;
+ return p - data;
+}
+
+/** Private PEM keys (legacy format) start with this header. */
+#define PRIVATE_PEM_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----"
+/** Private OPENSSH keys (RFC4716) start with this header. */
+#define PRIVATE_OPENSSH_KEY_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----"
+/** Private PEM keys (legacy format) end with this footer. */
+#define PRIVATE_PEM_KEY_FOOTER "-----END RSA PRIVATE KEY-----"
+/** Private OPENSSH keys (RFC4716) end with this footer. */
+#define PRIVATE_OPENSSH_KEY_FOOTER "-----END OPENSSH PRIVATE KEY-----"
+
+/**
+ * Decode an openssh-v1 (aka RFC4716) or PEM (aka ASN.1) private key.
+ *
+ * \param key_file The private key file (usually id_rsa).
+ * \param result Pointer to base64-decoded blob is returned here.
+ * \param blob_size The size of the decoded blob.
+ *
+ * This only checks header and footer and base64-decodes the part in between.
+ * No attempt to read the decoded part is made.
+ *
+ * \return Negative on errors, PKT_PEM or PKT_OPENSSH on success, indicating
+ * the type of key.
+ */
+int decode_private_key(const char *key_file, unsigned char **result,
+ size_t *blob_size)
+{
+ int ret, ret2, i, j, key_type;
+ void *map;
+ size_t map_size, key_size;
+ unsigned char *blob = NULL;
+ char *begin, *footer, *key;
+
+ ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL);
+ if (ret < 0)
+ goto out;
+ ret = -E_KEY_MARKER;
+ if (strncmp(map, PRIVATE_PEM_KEY_HEADER,
+ strlen(PRIVATE_PEM_KEY_HEADER)) == 0) {
+ key_type = PKT_PEM;
+ begin = map + strlen(PRIVATE_PEM_KEY_HEADER);
+ footer = strstr(map, PRIVATE_PEM_KEY_FOOTER);
+ PARA_INFO_LOG("detected legacy PEM key %s\n", key_file);
+ } else if (strncmp(map, PRIVATE_OPENSSH_KEY_HEADER,
+ strlen(PRIVATE_OPENSSH_KEY_HEADER)) == 0) {
+ key_type = PKT_OPENSSH;
+ begin = map + strlen(PRIVATE_OPENSSH_KEY_HEADER);
+ footer = strstr(map, PRIVATE_OPENSSH_KEY_FOOTER);
+ PARA_INFO_LOG("detected openssh key %s\n", key_file);
+ } else
+ goto unmap;
+ if (!footer)
+ goto unmap;
+ /* skip whitespace at the beginning */
+ for (; begin < footer; begin++) {
+ if (para_isspace(*begin))
+ continue;
+ break;
+ }
+ ret = -E_KEY_MARKER;
+ if (begin >= footer)
+ goto unmap;
+
+ key_size = footer - begin;
+ key = para_malloc(key_size + 1);
+ for (i = 0, j = 0; begin + i < footer; i++) {
+ if (para_isspace(begin[i]))
+ continue;
+ key[j++] = begin[i];
+ }
+ key[j] = '\0';
+ ret = base64_decode(key, j, (char **)&blob, blob_size);
+ free(key);
+ if (ret < 0)
+ goto unmap;
+ ret = key_type;
+unmap:
+ ret2 = para_munmap(map, map_size);
+ if (ret >= 0 && ret2 < 0)
+ ret = ret2;
+ if (ret < 0) {
+ free(blob);
+ blob = NULL;
+ }
+out:
+ *result = blob;
+ return ret;
+}
+
return ret;
}
-/** See \ref recv_init(). */
const struct receiver lsg_recv_cmd_com_dccp_user_data = {
.open = dccp_recv_open,
.close = dccp_recv_close,
{
dccp_shutdown_clients();
generic_acl_deplete(&dss->acl);
+ free_sender_status(dss);
}
/** * Obtain current MPS according to RFC 4340, sec. 14. */
PARA_ERROR(OGG_PACKET_IN, "ogg_stream_packetin() failed"), \
PARA_ERROR(OGG_STREAM_FLUSH, "ogg_stream_flush() failed"), \
PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \
+ PARA_ERROR(OPENSSH_PARSE, "could not parse openssh private key"), \
PARA_ERROR(OPUS_COMMENT, "invalid or corrupted opus comment"), \
PARA_ERROR(OPUS_DECODE, "opus decode error"), \
PARA_ERROR(OPUS_HEADER, "invalid opus header"), \
*/
#define SYSTEM_ERROR_BIT 30
-/**
- * Like the SYSTEM_ERROR_BIT, but indicates an error from the osl library.
- */
+/** Like SYSTEM_ERROR_BIT, but for errors from the osl library. */
#define OSL_ERROR_BIT 29
+/** Like SYSTEM_ERROR_BIT, but for errors from the lopsub library. */
#define LLS_ERROR_BIT 28
/** Check whether the system error bit is set. */
static const char * const flac_suffixes[] = {"flac", NULL};
/**
- * The init function of the flac audio format handler.
+ * The audio format handler for flac (free lossless audio decoder).
*
- * \param afh pointer to the struct to initialize
+ * It depends on libflac and on libogg.
*/
-void flac_afh_init(struct audio_format_handler *afh)
-{
- afh->get_file_info = flac_get_file_info,
- afh->suffixes = flac_suffixes;
- afh->rewrite_tags = flac_rewrite_tags;
-}
+const struct audio_format_handler flac_afh = {
+ .get_file_info = flac_get_file_info,
+ .suffixes = flac_suffixes,
+ .rewrite_tags = flac_rewrite_tags,
+};
return gcry_strerror(gcry_err_code(gret));
}
-static int decode_key(const char *key_file, const char *header_str,
- const char *footer_str, unsigned char **result)
-{
- int ret, ret2, i, j;
- void *map;
- size_t map_size, key_size, blob_size;
- unsigned char *blob = NULL;
- char *begin, *footer, *key;
-
- ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL);
- if (ret < 0)
- goto out;
- ret = -E_KEY_MARKER;
- if (strncmp(map, header_str, strlen(header_str)))
- goto unmap;
- footer = strstr(map, footer_str);
- ret = -E_KEY_MARKER;
- if (!footer)
- goto unmap;
- begin = map + strlen(header_str);
- /* skip whitespace at the beginning */
- for (; begin < footer; begin++) {
- if (para_isspace(*begin))
- continue;
- break;
- }
- ret = -E_KEY_MARKER;
- if (begin >= footer)
- goto unmap;
-
- key_size = footer - begin;
- key = para_malloc(key_size + 1);
- for (i = 0, j = 0; begin + i < footer; i++) {
- if (para_isspace(begin[i]))
- continue;
- key[j++] = begin[i];
- }
- key[j] = '\0';
- ret = base64_decode(key, j, (char **)&blob, &blob_size);
- free(key);
- if (ret < 0)
- goto free_unmap;
- ret = blob_size;
- goto unmap;
-free_unmap:
- free(blob);
- blob = NULL;
-unmap:
- ret2 = para_munmap(map, map_size);
- if (ret >= 0 && ret2 < 0)
- ret = ret2;
- if (ret < 0) {
- free(blob);
- blob = NULL;
- }
-out:
- *result = blob;
- return ret;
-}
-
/** ASN Types and their code. */
enum asn1_types {
/** The next object is an integer. */
/*
* Returns: Number of bytes scanned. This may differ from the value returned via
- * bn_bytes because the latter does not include the ASN.1 prefix and a leading
- * zero is not considered as an additional byte for bn_bytes.
+ * bitsp because the latter does not include the ASN.1 prefix and a leading
+ * zero is not considered as an additional byte for the number of bits.
*/
-static int read_bignum(unsigned char *start, unsigned char *end, gcry_mpi_t *bn,
- int *bn_bytes)
+static int read_pem_bignum(unsigned char *start, unsigned char *end, gcry_mpi_t *bn,
+ unsigned *bitsp)
{
int i, bn_size;
gcry_error_t gret;
cp++;
bn_size--;
}
- if (bn_bytes)
- *bn_bytes = bn_size;
+ if (bitsp)
+ *bitsp = bn_size * 8;
cp += bn_size;
// unsigned char *buf;
// gcry_mpi_aprint(GCRYMPI_FMT_HEX, &buf, NULL, *bn);
return cp - start;
}
-static int find_privkey_bignum_offset(const unsigned char *data, int len)
+struct rsa_params {
+ gcry_mpi_t n, e, d, p, q, u;
+};
+
+static int read_pem_rsa_params(unsigned char *start, unsigned char *end,
+ struct rsa_params *p)
+{
+ unsigned char *cp = start;
+ unsigned bits;
+ int ret;
+
+ ret = read_pem_bignum(cp, end, &p->n, &bits);
+ if (ret < 0)
+ return ret;
+ cp += ret;
+ ret = read_pem_bignum(cp, end, &p->e, NULL);
+ if (ret < 0)
+ goto release_n;
+ cp += ret;
+ ret = read_pem_bignum(cp, end, &p->d, NULL);
+ if (ret < 0)
+ goto release_e;
+ cp += ret;
+ ret = read_pem_bignum(cp, end, &p->p, NULL);
+ if (ret < 0)
+ goto release_d;
+ cp += ret;
+ ret = read_pem_bignum(cp, end, &p->q, NULL);
+ if (ret < 0)
+ goto release_p;
+ cp += ret;
+ ret = read_pem_bignum(cp, end, &p->u, NULL);
+ if (ret < 0)
+ goto release_q;
+ return bits;
+release_q:
+ gcry_mpi_release(p->q);
+release_p:
+ gcry_mpi_release(p->p);
+release_d:
+ gcry_mpi_release(p->d);
+release_e:
+ gcry_mpi_release(p->e);
+release_n:
+ gcry_mpi_release(p->n);
+ return ret;
+}
+
+static int find_pem_bignum_offset(const unsigned char *data, int len)
{
const unsigned char *p = data, *end = data + len;
return p - data;
}
-/** Private keys start with this header. */
-#define PRIVATE_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----"
-/** Private keys end with this footer. */
-#define PRIVATE_KEY_FOOTER "-----END RSA PRIVATE KEY-----"
-
-static int get_private_key(const char *key_file, struct asymmetric_key **result)
+static int read_openssh_bignum(unsigned char *start, unsigned char *end,
+ gcry_mpi_t *bn, unsigned *bitsp)
{
- gcry_mpi_t n = NULL, e = NULL, d = NULL, p = NULL, q = NULL,
- u = NULL;
- unsigned char *blob, *cp, *end;
- int blob_size, ret, n_size;
gcry_error_t gret;
- size_t erroff;
- gcry_sexp_t sexp;
- struct asymmetric_key *key;
+ size_t nscanned;
+ unsigned bits;
- *result = NULL;
- ret = decode_key(key_file, PRIVATE_KEY_HEADER, PRIVATE_KEY_FOOTER,
- &blob);
- if (ret < 0)
- return ret;
- blob_size = ret;
- end = blob + blob_size;
- ret = find_privkey_bignum_offset(blob, blob_size);
- if (ret < 0)
- goto free_blob;
- PARA_INFO_LOG("reading RSA params at offset %d\n", ret);
- cp = blob + ret;
+ gret = gcry_mpi_scan(bn, GCRYMPI_FMT_SSH, start, end - start, &nscanned);
+ if (gret) {
+ PARA_ERROR_LOG("gcry_mpi_scan: %s\n",
+ gcry_strerror(gcry_err_code(gret)));
+ return -E_MPI_SCAN;
+ }
+ bits = (nscanned - 4 - (start[4] == '\0')) * 8;
+ if (bitsp)
+ *bitsp = bits;
+ PARA_DEBUG_LOG("scanned %u-bit bignum\n", bits);
+ return nscanned;
+}
- ret = read_bignum(cp, end, &n, &n_size);
+static int read_openssh_rsa_params(unsigned char *start, unsigned char *end,
+ struct rsa_params *p)
+{
+ unsigned char *cp = start;
+ unsigned bits;
+ int ret;
+
+ ret = read_openssh_bignum(cp, end, &p->n, &bits);
if (ret < 0)
- goto free_blob;
+ return ret;
cp += ret;
-
- ret = read_bignum(cp, end, &e, NULL);
+ ret = read_openssh_bignum(cp, end, &p->e, NULL);
if (ret < 0)
goto release_n;
cp += ret;
-
- ret = read_bignum(cp, end, &d, NULL);
+ ret = read_openssh_bignum(cp, end, &p->d, NULL);
if (ret < 0)
goto release_e;
cp += ret;
-
- ret = read_bignum(cp, end, &p, NULL);
+ ret = read_openssh_bignum(cp, end, &p->u, NULL);
if (ret < 0)
goto release_d;
cp += ret;
-
- ret = read_bignum(cp, end, &q, NULL);
+ ret = read_openssh_bignum(cp, end, &p->p, NULL);
if (ret < 0)
- goto release_p;
+ goto release_u;
cp += ret;
- ret = read_bignum(cp, end, &u, NULL);
+ ret = read_openssh_bignum(cp, end, &p->q, NULL);
if (ret < 0)
- goto release_q;
+ goto release_p;
+ return bits;
+release_p:
+ gcry_mpi_release(p->p);
+release_u:
+ gcry_mpi_release(p->u);
+release_d:
+ gcry_mpi_release(p->d);
+release_e:
+ gcry_mpi_release(p->e);
+release_n:
+ gcry_mpi_release(p->n);
+ return ret;
+}
+
+static int get_private_key(const char *key_file, struct asymmetric_key **result)
+{
+ struct rsa_params params;
+ unsigned char *blob, *end;
+ unsigned bits;
+ int ret, key_type;
+ gcry_error_t gret;
+ size_t erroff, blob_size;
+ gcry_sexp_t sexp;
+ struct asymmetric_key *key;
+
+ *result = NULL;
+ ret = decode_private_key(key_file, &blob, &blob_size);
+ if (ret < 0)
+ return ret;
+ key_type = ret;
+ end = blob + blob_size;
+ if (key_type == PKT_PEM)
+ ret = find_pem_bignum_offset(blob, blob_size);
+ else
+ ret = find_openssh_bignum_offset(blob, blob_size);
+ if (ret < 0)
+ goto free_blob;
+ PARA_INFO_LOG("reading RSA params at offset %d\n", ret);
+ if (key_type == PKT_PEM)
+ ret = read_pem_rsa_params(blob + ret, end, ¶ms);
+ else
+ ret = read_openssh_rsa_params(blob + ret, end, ¶ms);
+ if (ret < 0)
+ goto free_blob;
+ bits = ret;
/*
* OpenSSL uses slightly different parameters than gcrypt. To use these
* parameters we need to swap the values of p and q and recompute u.
*/
- if (gcry_mpi_cmp(p, q) > 0) {
- gcry_mpi_swap(p, q);
- gcry_mpi_invm(u, p, q);
+ if (gcry_mpi_cmp(params.p, params.q) > 0) {
+ gcry_mpi_swap(params.p, params.q);
+ gcry_mpi_invm(params.u, params.p, params.q);
}
- gret = gcry_sexp_build(&sexp, &erroff, RSA_PRIVKEY_SEXP,
- n, e, d, p, q, u);
+ gret = gcry_sexp_build(&sexp, &erroff, RSA_PRIVKEY_SEXP, params.n,
+ params.e, params.d, params.p, params.q, params.u);
if (gret) {
PARA_ERROR_LOG("offset %zu: %s\n", erroff,
gcry_strerror(gcry_err_code(gret)));
ret = -E_SEXP_BUILD;
- goto release_u;
+ goto free_params;
}
key = para_malloc(sizeof(*key));
key->sexp = sexp;
*result = key;
- ret = n_size * 8;
+ ret = bits;
PARA_INFO_LOG("succesfully read %d bit private key\n", ret);
-release_u:
- gcry_mpi_release(u);
-release_q:
- gcry_mpi_release(q);
-release_p:
- gcry_mpi_release(p);
-release_d:
- gcry_mpi_release(d);
-release_e:
- gcry_mpi_release(e);
-release_n:
- gcry_mpi_release(n);
+free_params:
+ gcry_mpi_release(params.n);
+ gcry_mpi_release(params.e);
+ gcry_mpi_release(params.d);
+ gcry_mpi_release(params.u);
+ gcry_mpi_release(params.p);
+ gcry_mpi_release(params.q);
+
free_blob:
free(blob);
return ret;
unsigned char *blob, *p, *end;
int ret;
gcry_error_t gret;
- size_t nr_scanned, erroff, decoded_size;
+ size_t erroff, decoded_size;
gcry_mpi_t e, n;
gcry_sexp_t sexp;
struct asymmetric_key *key;
+ unsigned bits;
- ret = decode_ssh_key(key_file, &blob, &decoded_size);
+ ret = decode_public_key(key_file, &blob, &decoded_size);
if (ret < 0)
return ret;
p = blob + ret;
end = blob + decoded_size;
PARA_DEBUG_LOG("scanning modulus and public exponent\n");
- gret = gcry_mpi_scan(&e, GCRYMPI_FMT_SSH, p, end - p, &nr_scanned);
- if (gret) {
- ret = -E_MPI_SCAN;
- PARA_CRIT_LOG("%s\n", gcry_strerror(gcry_err_code(gret)));
+ ret = read_openssh_bignum(p, end, &e, NULL);
+ if (ret < 0)
goto free_blob;
- }
- PARA_DEBUG_LOG("scanned e (%zu bytes)\n", nr_scanned);
- p += nr_scanned;
- if (p >= end)
- goto release_e;
- gret = gcry_mpi_scan(&n, GCRYMPI_FMT_SSH, p, end - p, &nr_scanned);
- if (gret) {
- ret = -E_MPI_SCAN;
- PARA_ERROR_LOG("%s\n", gcry_strerror(gcry_err_code(gret)));
+ p += ret;
+ ret = read_openssh_bignum(p, end, &n, &bits);
+ if (ret < 0)
goto release_e;
- }
- PARA_DEBUG_LOG("scanned n (%zu bytes)\n", nr_scanned);
gret = gcry_sexp_build(&sexp, &erroff, RSA_PUBKEY_SEXP, n, e);
if (gret) {
PARA_ERROR_LOG("offset %zu: %s\n", erroff,
ret = -E_SEXP_BUILD;
goto release_n;
}
- ret = ROUND_DOWN(nr_scanned, 32);
- PARA_INFO_LOG("successfully read %d bit ssh public key\n", ret * 8);
+ PARA_INFO_LOG("successfully read %u bit ssh public key\n", bits);
key = para_malloc(sizeof(*key));
key->num_bytes = ret;
key->sexp = sexp;
*result = key;
+ ret = bits;
release_n:
gcry_mpi_release(n);
release_e:
return 1;
}
-/** See \ref recv_init(). */
const struct receiver lsg_recv_cmd_com_http_user_data = {
.open = http_recv_open,
.close = http_recv_close,
{
http_shutdown_clients();
generic_acl_deplete(&hss->acl);
+ free_sender_status(hss);
}
static int queue_chunk_or_shutdown(struct sender_client *sc,
ci.argc = create_argv(ci.buffer, " ", &ci.argv);
ci.word_num = compute_word_num(ci.buffer, " ", ci.point);
+ /* determine the current word to complete */
end = ci.buffer + ci.point;
+
+ if (*end == ' ') {
+ if (ci.point == 0 || ci.buffer[ci.point - 1] == ' ') {
+ ci.word = para_strdup(NULL);
+ goto create_matches;
+ } else /* The cursor is positioned right after a word */
+ end--;
+ }
for (p = end; p > ci.buffer && *p != ' '; p--)
; /* nothing */
if (*p == ' ')
p++;
-
n = end - p + 1;
ci.word = para_malloc(n + 1);
strncpy(ci.word, p, n);
ci.word[n] = '\0';
-
+create_matches:
PARA_DEBUG_LOG("line: %s, point: %d (%c), wordnum: %d, word: %s\n",
ci.buffer, ci.point, ci.buffer[ci.point], ci.word_num, ci.word);
if (ci.word_num == 0)
#include "ipc.h"
#include <sys/types.h>
#include <sys/param.h>
-#include <sys/sysctl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
}
# if defined __FreeBSD__ || defined __NetBSD__
+#include <sys/sysctl.h>
# define SYSCTL_SHMMAX_VARIABLE "kern.ipc.shmmax"
-# elif defined __APPLE__
-# define SYSCTL_SHMMAX_VARIABLE "kern.sysv.shmmax"
# else
# undef SYSCTL_SHMMAX_VARIABLE
# endif
The backup suffix is '~'. That is, a single tilde character is appended
to the given file name.
[/help]
+ [option preserve]
+ summary = preserve modification time
+ [help]
+ If this option is given, the mtime of the modified file is set to
+ the value prior to the modification.
+ [/help]
[option year]
short_opt = y
summary = set the year tag
purpose = dynamically adjust the volume of an audio stream
[option blocksize]
short_opt = b
- summary = use blocks of size 2**bits
+ summary = adjust volume after each block of size 2**bits (1-31)
typestr = bits
arg_info = required_arg
arg_type = uint32
[/help]
[option aggressiveness]
short_opt = a
- summary = controls the maximum amount to amplify by
+ summary = controls the maximum amount to amplify by (0-10)
typestr = bits
arg_info = required_arg
arg_type = uint32
default_val = 4
+ [help]
+ This controls the maximal gain factor. Zero means to not amplify
+ at all while the value 10 corresponds to maximal gain factor which
+ results in a 4-fold increase in volume.
+ [/help]
[option inertia]
short_opt = i
- summary = how much inertia ramping has
+ summary = how much inertia ramping has (1-14)
typestr = bits
arg_info = required_arg
arg_type = uint32
default_val = 6
+ [help]
+ Larger values cause smaller volume adjustments.
+ [/help]
[option target-level]
short_opt = t
- summary = target signal level (0-32768)
+ summary = target signal level (0-32767)
typestr = level
arg_info = required_arg
arg_type = uint32
- default_val = 20000
+ default_val = 16384
+ [help]
+ If the peak of the previous block is less than the target level,
+ volume is increased slightly for the next block. Otherwise it is
+ decreased. The default value is chosen to minimize clipping. There
+ is usually no reason to change it.
+ [/help]
[option damp]
short_opt = d
- summary = if non-zero, scale down after normalizing
+ summary = if non-zero, scale down after normalizing (0-16)
typestr = bits
arg_info = required_arg
arg_type = uint32
default_val = 0
+ [help]
+ This scales down the volume of the audio stream by factor 2**bits.
+ This is mostly useful if another audio application (e.g., a video
+ game) is running in parallel and the relative volume of the audio
+ stream is too high.
+ [/help]
[subcommand fecdec]
purpose = decode a (lossy) input stream using forward error correction
[subcommand flacdec]
lls_m4_include_dir := $(lls_m4_dir)/include
$(lls_suite_dir)/%.m4d: $(lls_m4_dir)/%.suite.m4 | $(lls_suite_dir)
- @[ -z "$(Q)" ] || echo 'M4D $<'
- $(Q) $(M4) -Pg -I $(lls_m4_include_dir) -s $< \
+ $(call SAY, M4D $<)
+ $(M4) -Pg -I $(lls_m4_include_dir) -s $< \
| awk '{if ($$1 ~ /#line/) {gsub(/"/, "", $$3); if ($$3 != "$<") \
print "$(lls_suite_dir)/$(*F).suite: " $$3}}' | sort | uniq > $@
$(lls_suite_dir)/%.suite: $(lls_m4_dir)/%.suite.m4 | $(lls_suite_dir)
- @[ -z "$(Q)" ] || echo 'M4 $<'
- $(Q) $(M4) -Pg -I $(lls_m4_include_dir) -D GIT_VERSION=$(GIT_VERSION) \
+ $(call SAY, M4 $<)
+ $(M4) -Pg -I $(lls_m4_include_dir) -D GIT_VERSION=$(GIT_VERSION) \
-D COPYRIGHT_YEAR=$(COPYRIGHT_YEAR) -D LOGLEVELS=$(LOGLEVELS) \
$< > $@
$(lls_suite_dir)/%.lsg.c: $(lls_suite_dir)/%.suite
- @[ -z "$(Q)" ] || echo 'LSGC $<'
- $(Q) $(LOPSUBGEN) --gen-c --output-dir $(lls_suite_dir) < $<
+ $(call SAY, LSGC $<)
+ $(LOPSUBGEN) --gen-c --output-dir $(lls_suite_dir) < $<
$(lls_suite_dir)/%.lsg.h: $(lls_suite_dir)/%.suite
- @[ -z "$(Q)" ] || echo 'LSGH $<'
- $(Q) $(LOPSUBGEN) --gen-header --output-dir $(lls_suite_dir) < $<
+ $(call SAY, LSGH $<)
+ $(LOPSUBGEN) --gen-header --output-dir $(lls_suite_dir) < $<
$(lls_suite_dir)/%.lsg.man: $(lls_suite_dir)/%.suite
- @[ -z "$(Q)" ] || echo 'LSGM $<'
- $(Q) $(LOPSUBGEN) --gen-man --output-dir $(lls_suite_dir) < $<
+ $(call SAY, LSGM $<)
+ $(LOPSUBGEN) --gen-man --output-dir $(lls_suite_dir) < $<
$(object_dir)/%.o: $(lls_suite_dir)/%.c | $(object_dir)
- @[ -z "$(Q)" ] || echo 'CC $<'
- $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(STRICT_CFLAGS) $<
+ $(call SAY, CC $<)
+ $(CC) -c -o $@ $(CPPFLAGS) $(STRICT_CFLAGS) $<
summary = enable verbose mode
[subcommand ff]
- purpose = jump N seconds forward or backward
- synopsis = n[-]
+ purpose = jump forward or backward in the current audio file
+ synopsis = seconds
aux_info = VSS_READ | VSS_WRITE
[description]
- This sets the 'R' (reposition request) bit of the vss status flags
- which enqueues a request to jump n seconds forwards or backwards.
-
- Example:
-
- para_client ff 30-
-
- jumps 30 seconds backwards.
+ This enqueues a request to reposition the audio stream according to
+ the argument, which may be a signed or an unsigned integer. Negative
+ values correspond to backward jumps.
+
+ If a negative number is given whose absolute value exceeds the current
+ postition of the stream, a jump to the beginning of the audio file
+ is performed. If a positive amount of seconds is given which exceeds
+ the remaining time of the audio file, the next audio file is loaded.
[/description]
PARA_INFO_LOG("waketime: %d:%02d\n", tm->tm_hour, tm->tm_min);
client_cmd("stop");
sleep(1);
- if (fot && fo_mood) {
+ if (fot && fo_mood && *fo_mood) {
ret = set_initial_volume(m, h);
if (ret < 0)
return ret;
if (ret < 0)
return ret;
}
- if (sleep_mood) {
+ if (sleep_mood && *sleep_mood) {
change_afs_mode(sleep_mood);
if (!fot || !fo_mood) /* currently stopped */
client_cmd("play");
- } else if (fot && fo_mood) /* currently playing */
+ } else if (fot && fo_mood && *fo_mood) /* currently playing */
client_cmd("stop");
- if (!fit || !fi_mood) /* nothing to do */
+ if (!fit || !fi_mood || !*fi_mood) /* nothing to do */
return 1;
- change_afs_mode(fi_mood);
for (;;) {
time(&t1);
if (wake_time_epoch <= t1 + fit)
(delay % 3600) / 60);
sleep(delay);
}
- client_cmd("play");
+ change_afs_mode(fi_mood);
+ if (sleep_mood && *sleep_mood) /* currently playing */
+ client_cmd("next");
+ else /* currently stopped */
+ client_cmd("play");
ret = fade(m, h, fiv, fit);
PARA_INFO_LOG("fade complete, returning\n");
return ret;
afhi->channels = header_channels(&header);
afhi->seconds_total = (tv2ms(&total_time) + 500) / 1000;
tv_divide(afhi->chunks_total, &total_time, &afhi->chunk_tv);
- PARA_DEBUG_LOG("%" PRIu32 "chunks, each %lums\n", afhi->chunks_total,
+ PARA_DEBUG_LOG("%" PRIu32 " chunks, each %lums\n", afhi->chunks_total,
tv2ms(&afhi->chunk_tv));
set_max_chunk_size(afhi);
ret = mp3_get_id3(map, numbytes, fd, &afhi->tags);
static const char * const mp3_suffixes[] = {"mp3", NULL};
/**
- * the init function of the mp3 audio format handler
+ * The mp3 audio format handler.
*
- * \param afh pointer to the struct to initialize
+ * It does not depend on any libraries and is hence always compiled in.
*/
-void mp3_afh_init(struct audio_format_handler *afh)
-{
- afh->get_file_info = mp3_get_file_info;
- afh->suffixes = mp3_suffixes;
+const struct audio_format_handler mp3_afh = {
+ .get_file_info = mp3_get_file_info,
+ .suffixes = mp3_suffixes,
#ifdef HAVE_LIBID3TAG
- afh->rewrite_tags = mp3_rewrite_tags;
+ .rewrite_tags = mp3_rewrite_tags,
#endif /* HAVE_LIBID3TAG */
-}
+};
static const char * const ogg_suffixes[] = {"ogg", NULL};
/**
- * The init function of the ogg vorbis audio format handler.
+ * The ogg vorbis audio format handler.
*
- * \param afh Pointer to the struct to initialize.
+ * It should actually be called vorbis because the ogg container format is also
+ * employed for the speex and flac codecs. Both the ogg and the vorbis
+ * libraries must be installed to get this audio format handler.
*/
-void ogg_afh_init(struct audio_format_handler *afh)
-{
- afh->get_file_info = ogg_vorbis_get_file_info;
- afh->get_header = vorbis_get_header;
- afh->suffixes = ogg_suffixes;
- afh->rewrite_tags = vorbis_rewrite_tags;
-}
+const struct audio_format_handler ogg_afh = {
+ .get_file_info = ogg_vorbis_get_file_info,
+ .get_header = vorbis_get_header,
+ .suffixes = ogg_suffixes,
+ .rewrite_tags = vorbis_rewrite_tags
+};
void crypt_shutdown(void)
{
+#ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA
CRYPTO_cleanup_all_ex_data();
-}
-
-static int get_private_key(const char *path, RSA **rsa)
-{
- EVP_PKEY *pkey;
- BIO *bio = BIO_new(BIO_s_file());
-
- *rsa = NULL;
- if (!bio)
- return -E_PRIVATE_KEY;
- if (BIO_read_filename(bio, path) <= 0)
- goto bio_free;
- pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
- if (!pkey)
- goto bio_free;
- *rsa = EVP_PKEY_get1_RSA(pkey);
- EVP_PKEY_free(pkey);
-bio_free:
- BIO_free(bio);
- return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY;
+#endif
}
/*
return ret;
}
+static int read_pem_private_key(const char *path, RSA **rsa)
+{
+ EVP_PKEY *pkey;
+ BIO *bio = BIO_new(BIO_s_file());
+
+ *rsa = NULL;
+ if (!bio)
+ return -E_PRIVATE_KEY;
+ if (BIO_read_filename(bio, path) <= 0)
+ goto bio_free;
+ pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
+ if (!pkey)
+ goto bio_free;
+ *rsa = EVP_PKEY_get1_RSA(pkey);
+ EVP_PKEY_free(pkey);
+bio_free:
+ BIO_free(bio);
+ return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY;
+}
+
+static int read_private_rsa_params(const unsigned char *blob,
+ const unsigned char *end, RSA **result)
+{
+ int ret;
+ RSA *rsa;
+ BN_CTX *ctx;
+ BIGNUM *n, *e, *d, *iqmp, *p, *q; /* stored in the key file */
+ BIGNUM *dmp1, *dmq1; /* these will be computed */
+ BIGNUM *tmp;
+ const unsigned char *cp = blob;
+
+ rsa = RSA_new();
+ if (!rsa)
+ return -E_BIGNUM;
+ ret = -E_BIGNUM;
+ tmp = BN_new();
+ if (!tmp)
+ goto free_rsa;
+ ctx = BN_CTX_new();
+ if (!ctx)
+ goto free_tmp;
+ dmp1 = BN_new();
+ if (!dmp1)
+ goto free_ctx;
+ dmq1 = BN_new();
+ if (!dmq1)
+ goto free_dmp1;
+ ret = read_bignum(cp, end - cp, &n);
+ if (ret < 0)
+ goto free_dmq1;
+ cp += ret;
+ ret = read_bignum(cp, end - cp, &e);
+ if (ret < 0)
+ goto free_n;
+ cp += ret;
+ ret = read_bignum(cp, end - cp, &d);
+ if (ret < 0)
+ goto free_e;
+ cp += ret;
+ ret = read_bignum(cp, end - cp, &iqmp);
+ if (ret < 0)
+ goto free_d;
+ cp += ret;
+ ret = read_bignum(cp, end - cp, &p);
+ if (ret < 0)
+ goto free_iqmp;
+ cp += ret;
+ ret = read_bignum(cp, end - cp, &q);
+ if (ret < 0)
+ goto free_p;
+ ret = -E_BIGNUM;
+ if (!BN_sub(tmp, q, BN_value_one()))
+ goto free_q;
+ if (!BN_mod(dmp1, d, tmp, ctx))
+ goto free_q;
+ if (!BN_sub(tmp, q, BN_value_one()))
+ goto free_q;
+ if (!BN_mod(dmq1, d, tmp, ctx))
+ goto free_q;
+#ifdef HAVE_RSA_SET0_KEY
+ RSA_set0_key(rsa, n, e, d);
+ RSA_set0_factors(rsa, p, q);
+ RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp);
+#else
+ rsa->n = n;
+ rsa->e = e;
+ rsa->d = d;
+ rsa->p = p;
+ rsa->q = q;
+ rsa->dmp1 = dmp1;
+ rsa->dmq1 = dmq1;
+ rsa->iqmp = iqmp;
+#endif
+ *result = rsa;
+ ret = 1;
+ goto free_ctx;
+free_q:
+ BN_clear_free(q);
+free_p:
+ BN_clear_free(p);
+free_iqmp:
+ BN_clear_free(iqmp);
+free_d:
+ BN_clear_free(d);
+free_e:
+ BN_free(e);
+free_n:
+ BN_free(n);
+free_dmq1:
+ BN_clear_free(dmq1);
+free_dmp1:
+ BN_clear_free(dmp1);
+free_ctx:
+ BN_CTX_free(ctx);
+free_tmp:
+ BN_clear_free(tmp);
+free_rsa:
+ if (ret < 0)
+ RSA_free(rsa);
+ return ret;
+}
+
+static int get_private_key(const char *path, RSA **rsa)
+{
+ int ret;
+ unsigned char *blob, *end;
+ size_t blob_size;
+
+ *rsa = NULL;
+ ret = decode_private_key(path, &blob, &blob_size);
+ if (ret < 0)
+ return ret;
+ end = blob + blob_size;
+ if (ret == PKT_OPENSSH) {
+ ret = find_openssh_bignum_offset(blob, blob_size);
+ if (ret < 0)
+ goto free_blob;
+ PARA_INFO_LOG("reading RSA params at offset %d\n", ret);
+ ret = read_private_rsa_params(blob + ret, end, rsa);
+ } else
+ ret = read_pem_private_key(path, rsa);
+free_blob:
+ free(blob);
+ return ret;
+}
+
int apc_get_pubkey(const char *key_file, struct asymmetric_key **result)
{
unsigned char *blob;
int ret;
struct asymmetric_key *key = para_malloc(sizeof(*key));
- ret = decode_ssh_key(key_file, &blob, &decoded_size);
+ ret = decode_public_key(key_file, &blob, &decoded_size);
if (ret < 0)
goto out;
ret = read_rsa_bignums(blob + ret, decoded_size - ret, &key->rsa);
}
/**
- * The init function of the ogg/opus audio format handler.
+ * The audio format handler for ogg/opus.
*
- * \param afh Pointer to the struct to initialize.
+ * The opus codec was standardized by the Internet Engineering Task Force and
+ * is described in RFC 6716 (2012). The audio format handler depends on the ogg
+ * and the opus libraries.
*/
-void opus_afh_init(struct audio_format_handler *afh)
-{
- afh->get_file_info = opus_get_file_info,
- afh->get_header = opus_get_header;
- afh->suffixes = opus_suffixes;
- afh->rewrite_tags = opus_rewrite_tags;
-}
+const struct audio_format_handler opus_afh = {
+ .get_file_info = opus_get_file_info,
+ .get_header = opus_get_header,
+ .suffixes = opus_suffixes,
+ .rewrite_tags = opus_rewrite_tags,
+};
int ret;
unsigned num_inputs;
- /* needed this early to make help work */
- recv_init();
-
sched.default_timeout.tv_sec = 5;
parse_config_or_die(argc, argv);
- AFH_RECV->init();
session_open();
num_inputs = lls_num_inputs(play_lpr);
init_shuffle_map();
loglevel = OPT_UINT32_VAL(LOGLEVEL, lpr);
version_handle_flag("recv", OPT_GIVEN(VERSION, lpr));
handle_help_flag(lpr);
- recv_init();
memset(&rn, 0, sizeof(struct receiver_node));
ret = check_receiver_arg(OPT_STRING_VAL(RECEIVER, lpr), &receiver_lpr);
if (ret < 0)
* \sa \ref http_recv.c, \ref udp_recv.c.
*/
struct receiver {
- /**
- * The optional receiver init function.
- *
- * Performs any initialization needed before the receiver can be opened.
- */
- void (*init)(void);
/**
* Open one instance of the receiver.
*
/** Iterate over all available receivers. */
#define FOR_EACH_RECEIVER(i) for (i = 1; lls_cmd(i, recv_cmd_suite); i++)
-void recv_init(void);
int check_receiver_arg(const char *ra, struct lls_parse_result **lprp);
void print_receiver_helps(bool detailed);
int generic_recv_pre_select(struct sched *s, struct receiver_node *rn);
#include "recv.h"
#include "string.h"
-/**
- * Call the init function of each paraslash receiver.
- *
- * Receivers employ the user_data feature of the lopsub library: Each receiver
- * of the recv_cmd suite defines a struct receiver as its user data.
- * recv_init() obtains a pointer to this structure by calling lls_user_data().
- * If the receiver has an init function (i.e., if ->init is not NULL), ->init()
- * is called to initialize the receiver.
- */
-void recv_init(void)
-{
- int i;
-
- FOR_EACH_RECEIVER(i) {
- const struct lls_command *cmd = RECV_CMD(i);
- const struct receiver *r = lls_user_data(cmd);
- if (r && r->init)
- r->init();
- }
-}
-
/**
* Check if the given string is a valid receiver specifier.
*
/** \file send.h Sender-related defines and structures. */
+/**
+ * A little preprocessor fu helps to create the sender_subcommand enumeration
+ * below and the list of sender name strings without duplicating the commands.
+ */
#define SENDER_SUBCOMMANDS \
SENDER_SUBCOMMAND(add) /**< Add a target (udp only). */ \
SENDER_SUBCOMMAND(delete) /**< Delete a target (udp only). */ \
SENDER_SUBCOMMAND(on) /**< Activate the sender. */ \
SENDER_SUBCOMMAND(off) /**< Deactivate the sender. */ \
+/** Concatenate "SENDER_" and the given arg and append a comma. */
#define SENDER_SUBCOMMAND(_name) SENDER_ ## _name,
+
+/**
+ * Each sender subcommand gets an SENDER_xxx identifier. The identifier is
+ * passed from the sender command handler to the server process via shared
+ * memory.
+ */
enum sender_subcommand {
- SENDER_SUBCOMMANDS
+ SENDER_SUBCOMMANDS /**< List of SENDER_xxx identifiers. */
NUM_SENDER_CMDS /**< Used as array size in struct \ref sender. */
};
+
#undef SENDER_SUBCOMMAND
+
+/**
+ * Redefine it to expand to the stringified name of the sender so that
+ * SENDER_SUBCOMMANDS above now expands to the comma-separated list of sender
+ * name strings. This is used in command.c to define and initialize an array of
+ * char pointers.
+ */
#define SENDER_SUBCOMMAND(_name) #_name,
/**
const struct lls_opt_result *acl_opt_result,
const struct lls_opt_result *listen_address_opt_result,
int default_port, int max_clients, int default_deny);
+void free_sender_status(const struct sender_status *ss);
char *generic_sender_status(struct sender_status *ss, const char *name);
void generic_com_allow(struct sender_command_data *scd,
struct sender_status *ss);
ss->default_deny = default_deny;
}
+/**
+ * Deallocate all resources allocated in \ref init_sender_status().
+ *
+ * \param ss The structure whose components should be freed.
+ *
+ * This frees the dynamically allocated parts of the structure which was
+ * initialized by an earlier call to \ref init_sender_status(). It does *not*
+ * call free(ss), though.
+ */
+void free_sender_status(const struct sender_status *ss)
+{
+ int i;
+
+ free(ss->listen_fds);
+ FOR_EACH_LISTEN_FD(i, ss)
+ free(ss->listen_addresses[i]);
+ free(ss->listen_addresses);
+}
+
/**
* Return a string containing the current status of a sender.
*
init_ipc_or_die(); /* init mmd struct, mmd and log mutex */
daemon_set_start_time();
daemon_set_hooks(pre_log_hook, post_log_hook);
- PARA_NOTICE_LOG("initializing audio format handlers\n");
- afh_init();
-
/*
* Although afs uses its own signal handling we must ignore SIGUSR1
* _before_ the afs child process gets born by init_afs() below. It's
static const char * const speex_suffixes[] = {"spx", "speex", NULL};
/**
- * The init function of the ogg/speex audio format handler.
+ * The ogg/speex audio format handler.
*
- * \param afh Pointer to the struct to initialize.
+ * This codec is considered obsolete because the opus codec surpasses its
+ * performance in all areas. It is only compiled in if both the ogg and the
+ * speex library are installed.
*/
-void spx_afh_init(struct audio_format_handler *afh)
-{
- afh->get_file_info = spx_get_file_info,
- afh->suffixes = speex_suffixes;
- afh->rewrite_tags = spx_rewrite_tags;
-}
+const struct audio_format_handler spx_afh = {
+ .get_file_info = spx_get_file_info,
+ .suffixes = speex_suffixes,
+ .rewrite_tags = spx_rewrite_tags,
+};
}
/**
- * Free the content of a pointer and set it to \p NULL.
+ * Free the content of a pointer and set it to NULL.
*
- * This is equivalent to "free(*arg); *arg = NULL;".
+ * \param arg A pointer to the pointer whose content should be freed.
*
- * \param arg The pointer whose content should be freed.
+ * If arg is NULL, the function returns immediately. Otherwise it frees the
+ * memory pointed to by *arg and sets *arg to NULL. Hence callers have to pass
+ * the *address* of the pointer variable that points to the memory which should
+ * be freed.
*/
void freep(void *arg)
{
- void **ptr = (void **)arg;
- free(*ptr);
- *ptr = NULL;
+ if (arg) {
+ void **ptr = arg;
+ free(*ptr);
+ *ptr = NULL;
+ }
}
/**
test: $(tests)
$(tests): all
- $(Q) $@ $(test_options)
+ $(call SAY, $(@))
+ $@ $(test_options)
test-help:
- $(Q) for t in $(tests); do $$t $(test_options) -h; done
+ @for t in $(tests); do $$t $(test_options) -h; done
test-clean:
$(RM) -r $(results_dir)
return ret;
}
-/** See \ref recv_init(). */
const struct receiver lsg_recv_cmd_com_udp_user_data = {
.open = udp_recv_open,
.close = udp_recv_close,
if (ret < 0)
goto err;
vsst->afsss = AFS_SOCKET_READY;
- PARA_DEBUG_LOG("fd: %d, code: %u, shmid: %u\n", passed_fd, afs_code,
- afs_data);
ret = -E_NOFD;
- if (afs_code != NEXT_AUDIO_FILE)
+ if (afs_code != NEXT_AUDIO_FILE) {
+ PARA_ERROR_LOG("afs code: %u, expected: %d\n", afs_code,
+ NEXT_AUDIO_FILE);
goto err;
+ }
if (passed_fd < 0)
goto err;
shmid = afs_data;
Next, change to the "bar" account on client_host and generate the
key pair with the commands
- ssh-keygen -q -t rsa -b 2048 -N '' -m PEM
+ ssh-keygen -q -t rsa -b 2048 -N '' -m RFC4716
This generates the two files id_rsa and id_rsa.pub in ~/.ssh. Note
that para_server won't accept keys shorter than 2048 bits. Moreover,
static const char * const wma_suffixes[] = {"wma", NULL};
/**
- * The init function of the wma audio format handler.
+ * The audio format handler for Windows Media Audio.
*
- * \param afh Pointer to the struct to initialize.
+ * Only WMA version 2 is supported. This audio format handler does not depend
+ * on any third party libraries and is therefore always compiled in.
*/
-void wma_afh_init(struct audio_format_handler *afh)
-{
- afh->get_file_info = wma_get_file_info;
- afh->suffixes = wma_suffixes;
- afh->rewrite_tags = wma_rewrite_tags;
-}
+const struct audio_format_handler wma_afh = {
+ .get_file_info = wma_get_file_info,
+ .suffixes = wma_suffixes,
+ .rewrite_tags = wma_rewrite_tags,
+};