From: Andre Noll Date: Sun, 30 Mar 2014 16:31:57 +0000 (+0200) Subject: Merge branch 't/misc' X-Git-Tag: v0.5.2~6 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=42ddd68159d7eff0f3e7c225665c97f9abd59425;hp=448c8f8e8c1c1eeb88ae468c0c3ab25bed6f2ac8 Merge branch 't/misc' Various fixes, improvements, cleanups. Cooking since 2014-02-22. * t/misc: (29 commits) build: Don't link with -lreadline if readline was not found. audiod: Skip NULL pointer check in compute_time_diff(). audiod: Make compute_time_diff() return void. com_stat(): Remove pointless uptime variable. gcrypt: Fix gcc warning on Ubuntu Lucid. flac: Try to link also without -logg. version.c: Fix comment of version_single_line(). doxygen: Expand all macros, in particular config.h. recv_common.c: Improve documentation of check_receiver_arg(). audiod: get_time_string() comment fix. configure: Really print opus audio file handler if opus lib was found. Overhaul doxygen main page. afs.h: Don't try to list all supported audio formats. Change copyright year to 2014. Add link to sideband.h in doxygen main page. Doxify error2.c and add GPL header. Add -Wdeclaration-after-statement. Add some missing includes. Makefile.real: Add clean2 to the list of phony targets. mood.c: Fix a trivial whitespace issue. ... --- diff --git a/NEWS b/NEWS index 221be43e..ab53a176 100644 --- a/NEWS +++ b/NEWS @@ -1,10 +1,29 @@ NEWS ==== ---------------------------------- -0.5.2 (???) "orthogonal interior" ---------------------------------- +--------------------------------------------- +0.5.2 (to be announced) "orthogonal interior" +--------------------------------------------- +The new sync filter, the AES_CTR128 stream cipher and the overhauled +network code are the highlights of this release. It also includes a +fair number of smaller fixes and improvements not mentioned here. + + - The new sync filter synchronizes playback between multiple + clients. + - Connections between para_server and para_client are now + encrypted by means of AES rather than RC4 if both sides + support it. RC4 is still available as a fallback. This + feature is fully transparent, i.e. no command line options + are necessary, and a client linked against openssl can + speak with a server linked against libgcrypt and vice versa. + - Major cleanup of the networking subsystem. + - Improvements to para_fade: the new set mode, multi-channel + initial volumes, better error logging. + - The man pages of para_audiod, para_filter, para_recv, and + para_write contain the relevant options for receivers, filters, + writers. This broke in 0.5.0. + - Improved user manual. - Minor fixes to avoid clang warnings. ------------------------------------------ diff --git a/acl.c b/acl.c index 794a4fa2..e1415050 100644 --- a/acl.c +++ b/acl.c @@ -6,7 +6,12 @@ /** \file acl.c Access control lists for paraslash senders. */ +#include +#include #include +#include +#include +#include #include "para.h" #include "error.h" diff --git a/afs.c b/afs.c index d0208266..fa4f4326 100644 --- a/afs.c +++ b/afs.c @@ -6,10 +6,15 @@ /** \file afs.c Paraslash's audio file selector. */ +#include +#include #include #include #include #include +#include +#include +#include #include "server.cmdline.h" #include "para.h" diff --git a/afs.cmd b/afs.cmd index 82b6dc48..91da48cb 100644 --- a/afs.cmd +++ b/afs.cmd @@ -22,7 +22,7 @@ H: database, skip this file. This operation is really cheap. H: Use it when adding large directories if only a few files H: where added. H: -H:-f Force adding/updating. Recompute the audio format handler data +H: -f Force adding/updating. Recompute the audio format handler data H: even if a file with the same path and the same hash value exists. H: H: -v Verbose mode. Print what is being done. @@ -44,17 +44,12 @@ H: Options: H: H: -l Change listing mode. Defaults to short listing if not given. H: -H: -ls: short listing mode -H: -H: -ll: long listing mode (equivalent to -l) -H: -H: -lv: verbose listing mode -H: -H: -lp: parser-friendly mode -H: -H: -lm: mbox listing mode -H: -H: -lc: chunk-table listing mode +H: -ls: short listing mode +H: -ll: long listing mode (equivalent to -l) +H: -lv: verbose listing mode +H: -lp: parser-friendly mode +H: -lm: mbox listing mode +H: -lc: chunk-table listing mode H: H: -p List full path of audio file. If not specified, only the basename H: of each file is printed. @@ -68,27 +63,17 @@ H: -d Print dates as seconds after the epoch. H: H: -s Change sort order. Defaults to alphabetical path sort if not given. H: -H: -sp: sort by path. -H: -H: -sl: sort by last played time. -H: -H: -ss: sort by score (implies -a). -H: -H: -sn: sort by num played count. -H: -H: -sf: sort by frequency. -H: -H: -sc: sort by number of channels. -H: -H: -si: sort by image id. -H: -H: -sy: sort by lyrics id. -H: -H: -sb: sort by bit rate. -H: -H: -sd: sort by duration. -H: -H: -sa: sort by audio format. +H: -sp: by path +H: -sl: by last played time +H: -ss: by score (implies -a) +H: -sn: by num played count +H: -sf: by frequency +H: -sc: by number of channels +H: -si: by image id +H: -sy: by lyrics id +H: -sb: by bit rate +H: -sd: by duration +H: -sa: by audio format --- N: lsatt P: AFS_READ diff --git a/alsa_mix.c b/alsa_mix.c index 1a19de29..be38e887 100644 --- a/alsa_mix.c +++ b/alsa_mix.c @@ -141,19 +141,19 @@ static int alsa_mix_set_channel(struct mixer_handle *h, PARA_NOTICE_LOG("unable to find simple control '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid)); - return -E_ALSA_MIX_BAD_ELEM; + return -E_BAD_CHANNEL; } ret = snd_mixer_selem_get_playback_volume_range(h->elem, &h->pmin, &h->pmax); if (ret < 0) { PARA_NOTICE_LOG("unable to get %s range (%s): %s\n", mixer_channel, h->card, snd_strerror(ret)); - return -E_ALSA_MIX_BAD_ELEM; + return -E_ALSA_MIX_RANGE; } if (h->pmin < 0 || h->pmax < 0 || h->pmin >= h->pmax) { PARA_NOTICE_LOG("alsa reported %s range %ld-%ld (%s)\n", mixer_channel, h->pmin, h->pmax, h->card); - return -E_ALSA_MIX_BAD_ELEM; + return -E_ALSA_MIX_RANGE; } return 1; } diff --git a/audioc.c b/audioc.c index 597a5f58..5f6b5ae1 100644 --- a/audioc.c +++ b/audioc.c @@ -6,8 +6,13 @@ /** \file audioc.c The client program used to connect to para_audiod. */ +#include +#include #include #include +#include +#include +#include #include #include diff --git a/audiod.c b/audiod.c index e207c758..d815c4aa 100644 --- a/audiod.c +++ b/audiod.c @@ -5,8 +5,14 @@ */ /** \file audiod.c The paraslash's audio daemon. */ + +#include +#include #include #include +#include +#include +#include #include #include "para.h" diff --git a/audiod.cmd b/audiod.cmd index 6c99d4c7..ad8f67d0 100644 --- a/audiod.cmd +++ b/audiod.cmd @@ -19,17 +19,13 @@ H: Options: H: H: -m Change grab mode. Defaults to sloppy grab if not given. H: -H: -ms: sloppy grab +H: -ms: sloppy grab +H: -mp: pedantic grab +H: -ma: aggressive grab H: -H: -mp: pedantic grab -H: -H: -ma: aggressive grab -H: -H: The various grab modes only differ in what happens if the -H: file descriptor to write the grabbed audio data to is not -H: ready for writing (i.e. would block). Sloppy mode ignores -H: the write, pedantic mode aborts and aggressive mode tries -H: to write anyway. +H: The various grab modes only differ in what happens if an attempt to +H: write the grabbed audio data would block. Sloppy mode ignores the +H: write, pedantic mode aborts and aggressive mode tries to write anyway. H: H: -p Grab output of node PARENT of the buffer tree. H: diff --git a/audiod_command.c b/audiod_command.c index 03020674..4485d9e9 100644 --- a/audiod_command.c +++ b/audiod_command.c @@ -6,8 +6,13 @@ /** \file audiod_command.c Commands for para_audiod. */ +#include +#include #include #include +#include +#include +#include #include "para.h" #include "audiod.cmdline.h" diff --git a/client_common.c b/client_common.c index c426d6ed..900d3653 100644 --- a/client_common.c +++ b/client_common.c @@ -6,8 +6,13 @@ /** \file client_common.c Common functions of para_client and para_audiod. */ +#include +#include #include #include +#include +#include +#include #include "para.h" #include "error.h" @@ -331,7 +336,8 @@ static int client_post_select(struct sched *s, struct task *t) case CL_RECEIVED_WELCOME: /* send auth command */ if (!FD_ISSET(ct->scc.fd, &s->wfds)) return 0; - sprintf(buf, AUTH_REQUEST_MSG "%s sideband", ct->user); + sprintf(buf, AUTH_REQUEST_MSG "%s sideband%s", ct->user, + has_feature("aes_ctr128", ct)? ",aes_ctr128" : ""); PARA_INFO_LOG("--> %s\n", buf); ret = write_buffer(ct->scc.fd, buf); if (ret < 0) @@ -347,6 +353,7 @@ static int client_post_select(struct sched *s, struct task *t) /* decrypted challenge/session key buffer */ unsigned char crypt_buf[1024]; struct sb_buffer sbb; + bool use_aes; ret = recv_sb(ct, &s->rfds, &sbb); if (ret <= 0) @@ -365,9 +372,10 @@ static int client_post_select(struct sched *s, struct task *t) goto out; ct->challenge_hash = para_malloc(HASH_SIZE); hash_function((char *)crypt_buf, CHALLENGE_SIZE, ct->challenge_hash); - ct->scc.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN); + use_aes = has_feature("aes_ctr128", ct); + ct->scc.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN, use_aes); ct->scc.recv = sc_new(crypt_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, - SESSION_KEY_LEN); + SESSION_KEY_LEN, use_aes); hash_to_asc(ct->challenge_hash, buf); PARA_INFO_LOG("--> %s\n", buf); ct->status = CL_RECEIVED_CHALLENGE; diff --git a/command.c b/command.c index a8d479e9..eb15875c 100644 --- a/command.c +++ b/command.c @@ -6,10 +6,15 @@ /** \file command.c Client authentication and server commands. */ +#include +#include #include #include #include #include +#include +#include +#include #include "para.h" #include "error.h" @@ -779,14 +784,20 @@ static void reset_signals(void) para_sigaction(SIGHUP, SIG_DFL); } -static int parse_auth_request(char *buf, int len, struct user **u) +struct connection_features { + bool sideband_requested; + bool aes_ctr128_requested; +}; + +static int parse_auth_request(char *buf, int len, struct user **u, + struct connection_features *cf) { int ret; char *p, *username, **features = NULL; size_t auth_rq_len = strlen(AUTH_REQUEST_MSG); - bool sideband_requested = false; *u = NULL; + memset(cf, 0, sizeof(*cf)); if (len < auth_rq_len + 2) return -E_AUTH_REQUEST; if (strncmp(buf, AUTH_REQUEST_MSG, auth_rq_len) != 0) @@ -802,18 +813,15 @@ static int parse_auth_request(char *buf, int len, struct user **u) create_argv(p, ",", &features); for (i = 0; features[i]; i++) { if (strcmp(features[i], "sideband") == 0) - sideband_requested = true; + cf->sideband_requested = true; + else if (strcmp(features[i], "aes_ctr128") == 0) + cf->aes_ctr128_requested = true; else { ret = -E_BAD_FEATURE; goto out; } } } - if (sideband_requested == false) { /* sideband is mandatory */ - PARA_ERROR_LOG("client did not request sideband\n"); - ret = -E_BAD_FEATURE; - goto out; - } PARA_DEBUG_LOG("received auth request for user %s\n", username); *u = lookup_user(username); ret = 1; @@ -888,10 +896,11 @@ __noreturn void handle_connect(int fd, const char *peername) int ret; unsigned char rand_buf[CHALLENGE_SIZE + 2 * SESSION_KEY_LEN]; unsigned char challenge_hash[HASH_SIZE]; - char *p, *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */; + char *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */; size_t numbytes; struct command_context cc_struct = {.peer = peername}, *cc = &cc_struct; struct iovec iov; + struct connection_features cf; cc->scc.fd = fd; reset_signals(); @@ -902,7 +911,7 @@ __noreturn void handle_connect(int fd, const char *peername) /* send Welcome message */ ret = write_va_buffer(fd, "This is para_server, version " PACKAGE_VERSION ".\n" - "Features: sideband\n" + "Features: sideband,aes_ctr128\n" ); if (ret < 0) goto net_err; @@ -910,12 +919,14 @@ __noreturn void handle_connect(int fd, const char *peername) ret = recv_buffer(fd, buf, HANDSHAKE_BUFSIZE); if (ret < 0) goto net_err; - ret = parse_auth_request(buf, ret, &cc->u); + ret = parse_auth_request(buf, ret, &cc->u, &cf); if (ret < 0) goto net_err; - p = buf + strlen(AUTH_REQUEST_MSG); - PARA_DEBUG_LOG("received auth request for user %s\n", p); - cc->u = lookup_user(p); + if (!cf.sideband_requested) { /* sideband is mandatory */ + PARA_ERROR_LOG("client did not request sideband\n"); + ret = -E_BAD_FEATURE; + goto net_err; + } if (cc->u) { get_random_bytes_or_die(rand_buf, sizeof(rand_buf)); ret = pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf), @@ -932,7 +943,7 @@ __noreturn void handle_connect(int fd, const char *peername) numbytes = 256; get_random_bytes_or_die((unsigned char *)buf, numbytes); } - PARA_DEBUG_LOG("sending %u byte challenge + rc4 keys (%zu bytes)\n", + PARA_DEBUG_LOG("sending %u byte challenge + session key (%zu bytes)\n", CHALLENGE_SIZE, numbytes); ret = send_sb(&cc->scc, buf, numbytes, SBD_CHALLENGE, false); buf = NULL; @@ -962,8 +973,10 @@ __noreturn void handle_connect(int fd, const char *peername) alarm(0); PARA_INFO_LOG("good auth for %s\n", cc->u->name); /* init stream cipher keys with the second part of the random buffer */ - cc->scc.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN); - cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, SESSION_KEY_LEN); + cc->scc.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN, + cf.aes_ctr128_requested); + cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, + SESSION_KEY_LEN, cf.aes_ctr128_requested); ret = send_sb(&cc->scc, NULL, 0, SBD_PROCEED, false); if (ret < 0) goto net_err; diff --git a/configure.ac b/configure.ac index 75194ffc..07ecffdd 100644 --- a/configure.ac +++ b/configure.ac @@ -930,6 +930,7 @@ if test "$have_openssl" = "yes" -o "$have_gcrypt" = "yes"; then amp_filter udp_recv prebuffer_filter + sync_filter " audiod_errlist_objs="$audiod_errlist_objs audiod @@ -967,6 +968,7 @@ if test "$have_openssl" = "yes" -o "$have_gcrypt" = "yes"; then wma_common wmadec_filter buffer_tree + sync_filter " if test "$have_openssl" = "yes"; then audiod_errlist_objs="$audiod_errlist_objs crypt" @@ -1111,6 +1113,7 @@ filters=" fecdec wmadec prebuffer + sync " filter_errlist_objs=" filter_common @@ -1135,12 +1138,14 @@ filter_errlist_objs=" wmadec_filter buffer_tree net + sync_filter " filter_cmdline_objs=" filter compress_filter amp_filter prebuffer_filter + sync_filter " if test "$have_vorbis" = "yes"; then @@ -1321,6 +1326,7 @@ play_errlist_objs=" write_common file_write version + sync_filter " play_cmdline_objs=" http_recv @@ -1332,6 +1338,7 @@ play_cmdline_objs=" prebuffer_filter file_write play + sync_filter " if test "$have_core_audio" = "yes"; then play_errlist_objs="$play_errlist_objs osx_write ipc" diff --git a/crypt.c b/crypt.c index 9790c8ce..0137e004 100644 --- a/crypt.c +++ b/crypt.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "para.h" #include "error.h" @@ -259,14 +260,40 @@ int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, return ret < 0? -E_ENCRYPT : ret; } +struct aes_ctr_128_context { + AES_KEY key; + unsigned char ivec[AES_CRT128_BLOCK_SIZE]; + unsigned char ecount[AES_CRT128_BLOCK_SIZE]; + unsigned int num; +}; + struct stream_cipher { - RC4_KEY key; + bool use_aes; + union { + RC4_KEY rc4_key; + struct aes_ctr_128_context aes; + } context; }; -struct stream_cipher *sc_new(const unsigned char *data, int len) +struct stream_cipher *sc_new(const unsigned char *data, int len, + bool use_aes) { + int ret; struct stream_cipher *sc = para_malloc(sizeof(*sc)); - RC4_set_key(&sc->key, len, data); + struct aes_ctr_128_context *aes; + + sc->use_aes = use_aes; + if (!use_aes) { + RC4_set_key(&sc->context.rc4_key, len, data); + return sc; + } + assert(len >= 2 * AES_CRT128_BLOCK_SIZE); + aes = &sc->context.aes; + ret = AES_set_encrypt_key(data, AES_CRT128_BLOCK_SIZE * 8 /* bits */, + &aes->key); + assert(ret == 0); + memcpy(aes->ivec, data + AES_CRT128_BLOCK_SIZE, AES_CRT128_BLOCK_SIZE); + aes->num = 0; return sc; } @@ -282,10 +309,9 @@ void sc_free(struct stream_cipher *sc) */ #define RC4_ALIGN 8 -void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) +static void rc4_crypt(RC4_KEY *key, struct iovec *src, struct iovec *dst) { size_t len = src->iov_len, l1, l2; - RC4_KEY *key = &sc->key; assert(len > 0); assert(len < ((typeof(src->iov_len))-1) / 2); @@ -306,6 +332,28 @@ void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) ((char *)dst->iov_base)[len] = '\0'; } +static void aes_ctr128_crypt(struct aes_ctr_128_context *aes, struct iovec *src, + struct iovec *dst) +{ + size_t len = src->iov_len; + + *dst = (typeof(*dst)) { + /* Add one for the terminating zero byte. */ + .iov_base = para_malloc(len + 1), + .iov_len = len + }; + AES_ctr128_encrypt(src->iov_base, dst->iov_base, len, + &aes->key, aes->ivec, aes->ecount, &aes->num); + ((char *)dst->iov_base)[len] = '\0'; +} + +void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) +{ + if (sc->use_aes) + return aes_ctr128_crypt(&sc->context.aes, src, dst); + return rc4_crypt(&sc->context.rc4_key, src, dst); +} + void hash_function(const char *data, unsigned long len, unsigned char *hash) { SHA_CTX c; diff --git a/crypt.h b/crypt.h index d3d4f35a..324a87b3 100644 --- a/crypt.h +++ b/crypt.h @@ -123,10 +123,12 @@ struct stream_cipher_context { * * \param data The key. * \param len The size of the key. + * \param use_aes True: Use the aes_ctr128 stream cipher, false: Use RC4. * * \return A new stream cipher structure. */ -struct stream_cipher *sc_new(const unsigned char *data, int len); +struct stream_cipher *sc_new(const unsigned char *data, int len, + bool use_aes); /** * Encrypt or decrypt a buffer using a stream cipher. diff --git a/crypt_backend.h b/crypt_backend.h index b74bef93..ddebe62e 100644 --- a/crypt_backend.h +++ b/crypt_backend.h @@ -8,6 +8,9 @@ /* This should only be incuded from files which provide crypto functions. */ +/** AES block size in bytes. */ +#define AES_CRT128_BLOCK_SIZE 16 + size_t is_ssh_rsa_key(char *data, size_t size); uint32_t read_ssh_u32(const void *vp); int uudecode(const char *src, unsigned char *target, size_t targsize); diff --git a/dccp_recv.c b/dccp_recv.c index 4cf8f5ae..796a7e6b 100644 --- a/dccp_recv.c +++ b/dccp_recv.c @@ -11,8 +11,13 @@ * (C) 2005 Ian McDonald */ +#include +#include #include #include +#include +#include +#include #include "para.h" #include "error.h" @@ -53,6 +58,7 @@ static int dccp_recv_open(struct receiver_node *rn) } fd = makesock(IPPROTO_DCCP, 0, conf->host_arg, conf->port_arg, fo); + flowopt_cleanup(fo); free(ccids); if (fd < 0) return fd; diff --git a/dccp_send.c b/dccp_send.c index df0dfce1..1e95aac6 100644 --- a/dccp_send.c +++ b/dccp_send.c @@ -11,9 +11,14 @@ * (C) 2005 Ian McDonald */ +#include +#include #include #include #include +#include +#include +#include #include "para.h" #include "error.h" diff --git a/error.h b/error.h index 11562d7b..301e2ca5 100644 --- a/error.h +++ b/error.h @@ -28,7 +28,7 @@ DEFINE_ERRLIST_OBJECT_ENUM; #define GGO_ERRORS #define COLOR_ERRORS #define SIGNAL_ERRORS -#define FADE_ERRORS +#define OSS_MIX_ERRORS #define STDOUT_ERRORS #define FILE_WRITE_ERRORS #define STDIN_ERRORS @@ -37,17 +37,18 @@ DEFINE_ERRLIST_OBJECT_ENUM; #define VERSION_ERRORS #define SCHED_ERRORS -extern const char **para_errlist[]; -#define OSS_MIX_ERRORS \ - PARA_ERROR(OSS_MIXER_CHANNEL, "invalid mixer channel"), \ +extern const char **para_errlist[]; +#define SYNC_FILTER_ERRORS\ + PARA_ERROR(SYNC_COMPLETE, "all buddies in sync"), \ + PARA_ERROR(SYNC_LISTEN_FD, "no fd to listen on"), \ #define ALSA_MIX_ERRORS \ PARA_ERROR(ALSA_MIX_OPEN, "could not open mixer"), \ - PARA_ERROR(ALSA_MIX_BAD_ELEM, "invalid/unsupported control element"), \ PARA_ERROR(ALSA_MIX_GET_VAL, "could not read control element state"), \ PARA_ERROR(ALSA_MIX_SET_VAL, "could not set control element state"), \ + PARA_ERROR(ALSA_MIX_RANGE, "value control element out of range"), \ #define RESAMPLE_FILTER_ERRORS \ @@ -78,6 +79,8 @@ extern const char **para_errlist[]; PARA_ERROR(NO_VALID_FILES, "no valid file found in playlist"), \ PARA_ERROR(BAD_PLAY_CMD, "invalid command"), \ +#define FADE_ERRORS \ + PARA_ERROR(BAD_CHANNEL, "invalid channel"), \ #define FLACDEC_FILTER_ERRORS \ PARA_ERROR(FLACDEC_DECODER_ALLOC, "could not allocate stream decoder"), \ diff --git a/fade.c b/fade.c index c1d15d72..543a666d 100644 --- a/fade.c +++ b/fade.c @@ -37,13 +37,21 @@ static __printf_2_3 void date_log(int ll, const char *fmt, ...) return; time(&t1); tm = localtime(&t1); - printf("%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec); + fprintf(stderr, "%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec); va_start(argp, fmt); vprintf(fmt, argp); va_end(argp); } __printf_2_3 void (*para_log)(int, const char*, ...) = date_log; +static int set_channel(struct mixer *m, struct mixer_handle *h, const char *channel) +{ + + PARA_NOTICE_LOG("using %s mixer channel\n", channel? + channel : "default"); + return m->set_channel(h, channel); +} + /* Fade to new volume in fade_time seconds. */ static int fade(struct mixer *m, struct mixer_handle *h, int new_vol, int fade_time) { @@ -55,11 +63,12 @@ static int fade(struct mixer *m, struct mixer_handle *h, int new_vol, int fade_t if (fade_time <= 0) return m->set(h, new_vol); secs = fade_time; - PARA_NOTICE_LOG("fading to %d in %d seconds\n", new_vol, secs); ret = m->get(h); if (ret < 0) goto out; vol = ret; + PARA_NOTICE_LOG("fading %s from %d to %d in %d seconds\n", + conf.mixer_channel_arg, vol, new_vol, secs); diff = new_vol - vol; if (!diff) { sleep(secs); @@ -112,7 +121,7 @@ fail: exit(EXIT_FAILURE); } -static void change_afs_mode_and_play(char *afs_mode) +static void change_afs_mode(char *afs_mode) { char *cmd; @@ -122,7 +131,44 @@ static void change_afs_mode_and_play(char *afs_mode) cmd = make_message("select %s", afs_mode); client_cmd(cmd); free(cmd); - client_cmd("play"); +} + +static int set_initial_volume(struct mixer *m, struct mixer_handle *h) +{ + int i, ret; + + for (i = 0; i < conf.ivol_given; i++) { + char *p, *ch, *arg = para_strdup(conf.ivol_arg[i]); + int32_t iv; + p = strchr(arg, ':'); + if (p) { + *p = '\0'; + p++; + ch = arg; + } else { + p = arg; + ch = NULL; + } + ret = para_atoi32(p, &iv); + if (ret < 0) { + free(arg); + return ret; + } + ret = set_channel(m, h, ch); + if (!ch) + ch = "default"; + if (ret < 0) { + PARA_WARNING_LOG("ignoring channel %s\n", ch); + ret = 0; + } else { + PARA_INFO_LOG("initial volume %s: %d\n", ch, iv); + ret = m->set(h, iv); + } + free(arg); + if (ret < 0) + return ret; + } + return 1; } static int sweet_dreams(struct mixer *m, struct mixer_handle *h) @@ -138,7 +184,6 @@ static int sweet_dreams(struct mixer *m, struct mixer_handle *h) int fot = conf.fo_time_arg; int fiv = conf.fi_vol_arg; int fov = conf.fo_vol_arg; - int iv = conf.ivol_arg; /* calculate wake time */ time(&t1); @@ -162,11 +207,14 @@ static int sweet_dreams(struct mixer *m, struct mixer_handle *h) client_cmd("stop"); sleep(1); if (fot) { - PARA_INFO_LOG("initial volume: %d\n", iv); - ret = m->set(h, iv); + ret = set_initial_volume(m, h); + if (ret < 0) + return ret; + change_afs_mode(fo_mood); + client_cmd("play"); + ret = set_channel(m, h, conf.mixer_channel_arg); if (ret < 0) return ret; - change_afs_mode_and_play(fo_mood); ret = fade(m, h, fov, fot); if (ret < 0) return ret; @@ -175,12 +223,14 @@ static int sweet_dreams(struct mixer *m, struct mixer_handle *h) if (ret < 0) return ret; } - if (conf.sleep_mood_given) - change_afs_mode_and_play(sleep_mood); - else + if (conf.sleep_mood_given) { + change_afs_mode(sleep_mood); + client_cmd("play"); + } else client_cmd("stop"); if (!fit) return 1; + change_afs_mode(fi_mood); for (;;) { time(&t1); if (wake_time_epoch <= t1 + fit) @@ -191,7 +241,7 @@ static int sweet_dreams(struct mixer *m, struct mixer_handle *h) (delay % 3600) / 60); sleep(delay); } - change_afs_mode_and_play(fi_mood); + client_cmd("play"); ret = fade(m, h, fiv, fit); PARA_INFO_LOG("fade complete, returning\n"); return ret; @@ -246,22 +296,9 @@ static void init_mixers(void) } } -static int set_channel(struct mixer *m, struct mixer_handle *h) +static int set_val(struct mixer *m, struct mixer_handle *h) { - char *channels; - int ret; - - ret = m->set_channel(h, conf.mixer_channel_arg); - if (ret >= 0) { - PARA_NOTICE_LOG("using %s mixer channel\n", - conf.mixer_channel_arg? conf.mixer_channel_arg - : "default"); - return ret; - } - channels = m->get_channels(h); - printf("Available channels: %s\n", channels); - free(channels); - return ret; + return m->set(h, conf.val_arg); } static struct mixer *get_mixer_or_die(void) @@ -330,7 +367,12 @@ int main(int argc, char *argv[]) ret = m->open(conf.mixer_device_arg, &h); if (ret < 0) goto out; - ret = set_channel(m, h); + ret = set_channel(m, h, conf.mixer_channel_arg); + if (ret == -E_BAD_CHANNEL) { + char *channels = m->get_channels(h); + printf("Available channels: %s\n", channels); + free(channels); + } if (ret < 0) goto out; switch (conf.mode_arg) { @@ -340,6 +382,9 @@ int main(int argc, char *argv[]) case mode_arg_snooze: ret = snooze(m, h); break; + case mode_arg_set: + ret = set_val(m, h); + break; default: /* sleep mode */ ret = sweet_dreams(m, h); break; diff --git a/gcrypt.c b/gcrypt.c index 1cfd1096..751c1a4a 100644 --- a/gcrypt.c +++ b/gcrypt.c @@ -912,11 +912,25 @@ struct stream_cipher { gcry_cipher_hd_t handle; }; -struct stream_cipher *sc_new(const unsigned char *data, int len) +struct stream_cipher *sc_new(const unsigned char *data, int len, + bool use_aes) { gcry_error_t gret; - struct stream_cipher *sc = para_malloc(sizeof(*sc)); + + if (use_aes) { + assert(len >= 2 * AES_CRT128_BLOCK_SIZE); + gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_CTR, 0); + assert(gret == 0); + gret = gcry_cipher_setkey(sc->handle, data, + AES_CRT128_BLOCK_SIZE); + assert(gret == 0); + gret = gcry_cipher_setctr(sc->handle, + data + AES_CRT128_BLOCK_SIZE, AES_CRT128_BLOCK_SIZE); + assert(gret == 0); + return sc; + } gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM, 0); if (gret) { diff --git a/http_recv.c b/http_recv.c index c7ef8629..1f02e48d 100644 --- a/http_recv.c +++ b/http_recv.c @@ -7,7 +7,12 @@ /** \file http_recv.c paraslash's http receiver */ #include +#include +#include #include +#include +#include +#include #include "para.h" #include "error.h" diff --git a/http_send.c b/http_send.c index 1c4b26ea..3e9c9539 100644 --- a/http_send.c +++ b/http_send.c @@ -6,9 +6,14 @@ /** \file http_send.c paraslash's http sender */ +#include +#include #include #include #include +#include +#include +#include #include "para.h" #include "error.h" diff --git a/m4/gengetopt/fade.m4 b/m4/gengetopt/fade.m4 index 5a71cc6b..59389ffe 100644 --- a/m4/gengetopt/fade.m4 +++ b/m4/gengetopt/fade.m4 @@ -17,11 +17,11 @@ option "mode" o #~~~~~~~~~~~~~~ "how to fade volume" enum typestr = "mode" - values = "sleep", "snooze", "fade" + values = "sleep", "fade", "set", "snooze" default = "sleep" optional details=" - para_fade knows three different fading modes: + para_fade knows the following modes: sleep mode: Change to the initial volume and select the initial afs mood/playlist. Then fade out until @@ -33,6 +33,8 @@ option "mode" o fade: Fade the volume to the given value in the given time. + set: Just set the value and exit. + snooze: Fade out, sleep a bit and fade in. " @@ -86,12 +88,15 @@ section "Options for sleep mode" option "ivol" - #~~~~~~~~~~~~~~ "set initial volume" - int typestr = "volume" + string typestr = "[channel:]volume" default = "60" optional + multiple details = " Used as the start volume, before fading out to the - fade out volume. + fade out volume. The channel part may be omitted, in + which case the default channel is used. This option + may be given multiple times. " option "fo-mood" - @@ -228,4 +233,14 @@ option "fade-time" t int typestr = "seconds" default = "5" optional + +section "Options for set mode" +############################## + +option "val" - +"value to set" + int typestr = "value" + default = "0" + optional + diff --git a/m4/gengetopt/sync_filter.m4 b/m4/gengetopt/sync_filter.m4 new file mode 100644 index 00000000..1e6f5f82 --- /dev/null +++ b/m4/gengetopt/sync_filter.m4 @@ -0,0 +1,45 @@ +args "--no-version --no-help" + +purpose "Synchronize playback between multiple clients." + +option "buddy" b +#~~~~~~~~~~~~~~~ +"host to synchronize with" +multiple +string typestr = "url" +optional +details = " + This option may be given multiple times, one per buddy. Each + value may be given as a host, port pair in either IPv4 or + IPv6 form, with port being optional. If no port was specified + the listening port (as specified with --port, see below) + is used to send the synchronization packet to this buddy. +" + +option "port" p +#~~~~~~~~~~~~~~ +"UDP port for incoming synchronization packets" +int typestr = "portnumber" +default = "29900" +optional +details = " + The sync filter receives incoming synchronization packets on + this UDP port. +" + +option "timeout" t +#~~~~~~~~~~~~~~~~~ +"how long to wait for other clients" +int typestr = "milliseconds" +default = "2000" +optional +details = " + Once the sync filter receives its first chunk of input, a + synchronization period of the given number of milliseconds + begins. Playback is deferred until a synchronization packet + has been received from each defined buddy, or until the end + of the period. Buddies which did not send a synchronization + packet in time are temporarily disabled and are not waited for + during subsequent synchronization periods. They are re-enabled + automatically when another synchronization packet arrives. +" diff --git a/net.c b/net.c index 503e4de9..c11f67c4 100644 --- a/net.c +++ b/net.c @@ -12,6 +12,11 @@ */ #define _GNU_SOURCE +#include +#include +#include +#include +#include #include /* At least NetBSD needs these. */ @@ -142,9 +147,9 @@ static bool host_string_ok(const char *host) * \param hostlen The maximum length of \a host. * \param port To return the port number (if any) of \a url. * - * \return Pointer to \a host, or NULL if failed. - * If NULL is returned, \a host and \a portnum are undefined. If no - * port number was present in \a url, \a portnum is set to -1. + * \return Pointer to \a host, or \p NULL if failed. If \p NULL is returned, + * \a host and \a port are undefined. If no port number was present in \a url, + * \a port is set to -1. * * \sa RFC 3986, 3.2.2/3.2.3 */ @@ -168,16 +173,16 @@ char *parse_url(const char *url, if (*o++ != ']' || (*o != '\0' && *o != ':')) goto failed; } else { - for (; (*c = *o == ':'? '\0' : *o); c++, o++) - if (c == end) + for (; (*c = *o == ':'? '\0' : *o); c++, o++) { + if (c == end && o[1]) goto failed; + } } if (*o == ':') if (para_atoi32(++o, port) < 0 || *port < 0 || *port > 0xffff) goto failed; - if (host_string_ok(host)) return host; failed: @@ -328,7 +333,14 @@ static void flowopt_setopts(int sockfd, struct flowopts *fo) } } -static void flowopt_cleanup(struct flowopts *fo) +/** + * Deallocate all resources of a flowopts structure. + * + * \param fo A pointer as returned from flowopt_new(). + * + * It's OK to pass \p NULL here in which case the function does nothing. + */ +void flowopt_cleanup(struct flowopts *fo) { struct pre_conn_opt *cur, *next; @@ -344,137 +356,148 @@ static void flowopt_cleanup(struct flowopts *fo) } /** - * Resolve IPv4/IPv6 address and create a ready-to-use active or passive socket. + * Resolve an IPv4/IPv6 address. * * \param l4type The layer-4 type (\p IPPROTO_xxx). - * \param passive Whether this is a passive (1) or active (0) socket. + * \param passive Whether \p AI_PASSIVE should be included as hint. * \param host Remote or local hostname or IPv/6 address string. - * \param port_number Decimal port number. - * \param fo Socket options to be set before making the connection. + * \param port_number Used to set the port in each returned address structure. + * \param result addrinfo structures are returned here. * - * This creates a ready-made IPv4/v6 socket structure after looking up the - * necessary parameters. The interpretation of \a host depends on the value of - * \a passive: - * - on a passive socket host is interpreted as an interface IPv4/6 address - * (can be left NULL); - * - on an active socket, \a host is the peer DNS name or IPv4/6 address - * to connect to; - * - \a port_number is in either case the numeric port number (not service - * string). - * - * Furthermore, bind(2) is called on passive sockets, and connect(2) on active - * sockets. The algorithm tries all possible address combinations until it - * succeeds. If \a fo is supplied, options are set and cleanup is performed. - * - * \return This function returns 1 on success and \a -E_ADDRESS_LOOKUP when no - * matching connection could be set up (with details in the error log). - * - * \sa ipv6(7), getaddrinfo(3), bind(2), connect(2). - */ -int makesock(unsigned l4type, bool passive, - const char *host, uint16_t port_number, - struct flowopts *fo) -{ - struct addrinfo *local = NULL, *src = NULL, *remote = NULL, - *dst = NULL, hints; - unsigned int l3type = AF_UNSPEC; - int rc, on = 1, sockfd = -1, - socktype = sock_type(l4type); + * The interpretation of \a host depends on the value of \a passive. On a + * passive socket host is interpreted as an interface IPv4/6 address (can be + * left NULL). On an active socket, \a host is the peer DNS name or IPv4/6 + * address to connect to. + * + * \return Standard. + * + * \sa getaddrinfo(3). + */ +int lookup_address(unsigned l4type, bool passive, const char *host, + int port_number, struct addrinfo **result) +{ + int ret; char port[6]; /* port number has at most 5 digits */ + struct addrinfo *addr = NULL, hints; - sprintf(port, "%u", port_number); + *result = NULL; + sprintf(port, "%u", port_number & 0xffff); /* Set up address hint structure */ memset(&hints, 0, sizeof(hints)); - hints.ai_family = l3type; - hints.ai_socktype = socktype; - /* + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = sock_type(l4type); + /* * getaddrinfo does not support SOCK_DCCP, so for the sake of lookup * (and only then) pretend to be UDP. */ if (l4type == IPPROTO_DCCP) hints.ai_socktype = SOCK_DGRAM; - /* only use addresses available on the host */ hints.ai_flags = AI_ADDRCONFIG; - if (l3type == AF_INET6) - /* use v4-mapped-v6 if no v6 addresses found */ - hints.ai_flags |= AI_V4MAPPED | AI_ALL; - if (passive && host == NULL) hints.ai_flags |= AI_PASSIVE; - /* Obtain local/remote address information */ - if ((rc = getaddrinfo(host, port, &hints, passive ? &local : &remote))) { - PARA_ERROR_LOG("can not resolve %s address %s#%s: %s.\n", - layer4_name(l4type), - host? host : (passive? "[loopback]" : "[localhost]"), - port, gai_strerror(rc)); - rc = -E_ADDRESS_LOOKUP; - goto out; + ret = getaddrinfo(host, port, &hints, &addr); + if (ret != 0) { + PARA_ERROR_LOG("can not resolve %s address %s#%s: %s\n", + layer4_name(l4type), + host? host : (passive? "[loopback]" : "[localhost]"), + port, gai_strerror(ret)); + return -E_ADDRESS_LOOKUP; } + *result = addr; + return 1; +} - /* Iterate over all src/dst combination, exhausting dst first */ - for (src = local, dst = remote; src != NULL || dst != NULL; /* no op */ ) { - if (src && dst && src->ai_family == AF_INET - && dst->ai_family == AF_INET6) - goto get_next_dst; /* v4 -> v6 is not possible */ - - sockfd = socket(src ? src->ai_family : dst->ai_family, - socktype, l4type); - if (sockfd < 0) - goto get_next_dst; +/** + * Create an active or passive socket. + * + * \param l4type \p IPPROTO_TCP, \p IPPROTO_UDP, or \p IPPROTO_DCCP. + * \param passive Whether to call bind(2) or connect(2). + * \param ai Address information as obtained from \ref lookup_address(). + * \param fo Socket options to be set before making the connection. + * + * bind(2) is called on passive sockets, and connect(2) on active sockets. The + * algorithm tries all possible address combinations until it succeeds. If \a + * fo is supplied, options are set but cleanup must be performed in the caller. + * + * \return File descriptor on success, \p E_MAKESOCK on errors. + * + * \sa \ref lookup_address(), \ref makesock(), ip(7), ipv6(7), bind(2), + * connect(2). + */ +int makesock_addrinfo(unsigned l4type, bool passive, struct addrinfo *ai, + struct flowopts *fo) +{ + int ret = -E_MAKESOCK, on = 1; + for (; ai; ai = ai->ai_next) { + int fd; + ret = socket(ai->ai_family, sock_type(l4type), l4type); + if (ret < 0) + continue; + fd = ret; + flowopt_setopts(fd, fo); + if (!passive) { + if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) + return fd; + close(fd); + continue; + } /* * Reuse the address on passive sockets to avoid failure on * restart (protocols using listen()) and when creating * multiple listener instances (UDP multicast). */ - if (passive && setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, - &on, sizeof(on)) == -1) { - rc = errno; - close(sockfd); - PARA_ERROR_LOG("can not set SO_REUSEADDR: %s\n", - strerror(rc)); - rc = -ERRNO_TO_PARA_ERROR(rc); - break; - } - flowopt_setopts(sockfd, fo); - - if (src) { - if (bind(sockfd, src->ai_addr, src->ai_addrlen) < 0) { - close(sockfd); - goto get_next_src; - } - if (!dst) /* bind-only completed successfully */ - break; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, + sizeof(on)) == -1) { + close(fd); + continue; } - - if (dst && connect(sockfd, dst->ai_addr, dst->ai_addrlen) == 0) - break; /* connection completed successfully */ - close(sockfd); -get_next_dst: - if (dst && (dst = dst->ai_next)) + if (bind(fd, ai->ai_addr, ai->ai_addrlen) < 0) { + close(fd); continue; -get_next_src: - if (src && (src = src->ai_next)) /* restart inner loop */ - dst = remote; + } + return fd; } -out: - if (local) - freeaddrinfo(local); - if (remote) - freeaddrinfo(remote); - flowopt_cleanup(fo); - - if (src == NULL && dst == NULL) { - if (rc >= 0) - rc = -E_MAKESOCK; - PARA_ERROR_LOG("can not create %s socket %s#%s.\n", - layer4_name(l4type), host? host : (passive? - "[loopback]" : "[localhost]"), port); - return rc; + return -E_MAKESOCK; +} + +/** + * Resolve IPv4/IPv6 address and create a ready-to-use active or passive socket. + * + * \param l4type The layer-4 type (\p IPPROTO_xxx). + * \param passive Whether this is a passive or active socket. + * \param host Passed to \ref lookup_address(). + * \param port_number Passed to \ref lookup_address(). + * \param fo Passed to \ref makesock_addrinfo(). + * + * This creates a ready-made IPv4/v6 socket structure after looking up the + * necessary parameters. The function first calls \ref lookup_address() and + * passes the address information to makesock_addrinfo() to create and + * initialize the socket. + * + * \return The newly created file descriptor on success, a negative error code + * on failure. + * + * \sa \ref lookup_address(), \ref makesock_addrinfo(). + */ +int makesock(unsigned l4type, bool passive, const char *host, uint16_t port_number, + struct flowopts *fo) +{ + struct addrinfo *ai; + int ret = lookup_address(l4type, passive, host, port_number, &ai); + + if (ret >= 0) + ret = makesock_addrinfo(l4type, passive, ai, fo); + if (ai) + freeaddrinfo(ai); + if (ret < 0) { + PARA_ERROR_LOG("can not create %s socket %s#%d.\n", + layer4_name(l4type), host? host : (passive? + "[loopback]" : "[localhost]"), port_number); } - return sockfd; + return ret; } /** @@ -687,6 +710,32 @@ void extract_v4_addr(const struct sockaddr_storage *ss, struct in_addr *ia) *ia = ((struct sockaddr_in *)sa)->sin_addr; } +/** + * Compare the address part of IPv4/6 addresses. + * + * \param sa1 First address. + * \param sa2 Second address. + * + * \return True iff the IP address of \a sa1 and \a sa2 match. + */ +bool sockaddr_equal(const struct sockaddr *sa1, const struct sockaddr *sa2) +{ + if (!sa1 || !sa2) + return false; + if (sa1->sa_family != sa2->sa_family) + return false; + if (sa1->sa_family == AF_INET) { + struct sockaddr_in *a1 = (typeof(a1))sa1, + *a2 = (typeof (a2))sa2; + return a1->sin_addr.s_addr == a2->sin_addr.s_addr; + } else if (sa1->sa_family == AF_INET6) { + struct sockaddr_in6 *a1 = (typeof(a1))sa1, + *a2 = (typeof (a2))sa2; + return !memcmp(a1, a2, sizeof(*a1)); + } else + return false; +} + /** * Receive data from a file descriptor. * diff --git a/net.h b/net.h index 9cbca0f4..877d1cbb 100644 --- a/net.h +++ b/net.h @@ -61,6 +61,7 @@ struct flowopts; extern struct flowopts *flowopt_new(void); extern void flowopt_add(struct flowopts *fo, int level, int opt, const char *name, const void *val, int len); +void flowopt_cleanup(struct flowopts *fo); /** Flowopt shortcut macros */ #define OPT_ADD(fo, lev, opt, val, len) flowopt_add(fo, lev, opt, #opt, val, len) @@ -101,12 +102,17 @@ _static_inline_ bool is_valid_ipv6_address(const char *address) return inet_pton(AF_INET6, address, &test_it) != 0; } +int lookup_address(unsigned l4type, bool passive, const char *host, + int port_number, struct addrinfo **result); + /** * Generic socket creation (passive and active sockets). */ -extern int makesock(unsigned l4type, bool passive, - const char *host, uint16_t port_number, - struct flowopts *fo); +int makesock(unsigned l4type, bool passive, const char *host, + uint16_t port_number, struct flowopts *fo); + +int makesock_addrinfo(unsigned l4type, bool passive, struct addrinfo *ai, + struct flowopts *fo); static inline int para_connect_simple(unsigned l4type, const char *host, uint16_t port) @@ -115,6 +121,7 @@ static inline int para_connect_simple(unsigned l4type, } void extract_v4_addr(const struct sockaddr_storage *ss, struct in_addr *ia); +bool sockaddr_equal(const struct sockaddr *sa1, const struct sockaddr *sa2); /** * Functions to support listening sockets. diff --git a/oss_mix.c b/oss_mix.c index c325329f..5fbfea08 100644 --- a/oss_mix.c +++ b/oss_mix.c @@ -98,7 +98,7 @@ static int oss_mix_set_channel(struct mixer_handle *handle, handle->id = i; return 1; } - return -E_OSS_MIXER_CHANNEL; + return -E_BAD_CHANNEL; } static int oss_mix_get(struct mixer_handle *handle) diff --git a/para.h b/para.h index 870656b1..6bd048d8 100644 --- a/para.h +++ b/para.h @@ -19,13 +19,11 @@ #include #include #include -#include -#include -#include -#include /* needed by create_pf_socket */ #include #include #include +#include +#include #include "gcc-compat.h" /** used in various contexts */ diff --git a/send_common.c b/send_common.c index 77cdabae..0baac3a6 100644 --- a/send_common.c +++ b/send_common.c @@ -6,8 +6,13 @@ /** \file send_common.c Functions used by more than one paraslash sender. */ +#include +#include #include #include +#include +#include +#include #include "para.h" #include "error.h" diff --git a/server.c b/server.c index 342946ab..fa19ce42 100644 --- a/server.c +++ b/server.c @@ -34,9 +34,15 @@ * - Doubly linked lists: \ref list.h. */ +#include +#include #include #include #include +#include +#include +#include +#include #include "para.h" #include "error.h" diff --git a/sync_filter.c b/sync_filter.c new file mode 100644 index 00000000..379b54b1 --- /dev/null +++ b/sync_filter.c @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2013 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file sync_filter.c Playback synchronization filter. */ + +#include +#include +#include +#include +#include +#include +#include + +#include "para.h" +#include "sync_filter.cmdline.h" +#include "list.h" +#include "net.h" +#include "sched.h" +#include "ggo.h" +#include "buffer_tree.h" +#include "filter.h" +#include "string.h" +#include "fd.h" +#include "error.h" + +struct sync_buddy_info { + const char *url; + char *host; + int port; + struct addrinfo *ai; + bool disabled; +}; + +/* per open/close data */ +struct sync_buddy { + int fd; + struct sync_buddy_info *sbi; + bool ping_received; + struct list_head node; +}; + +struct sync_filter_context { + int listen_fd; + struct list_head buddies; + struct timeval timeout; + bool ping_sent; +}; + +struct sync_filter_config { + struct sync_filter_args_info *conf; + struct sync_buddy_info *buddy_info; +}; + +#define FOR_EACH_BUDDY(_buddy, _list) \ + list_for_each_entry(_buddy, _list, node) +#define FOR_EACH_BUDDY_SAFE(_buddy, _tmp_buddy, _list) \ + list_for_each_entry_safe(_buddy, _tmp_buddy, _list, node) + +static void sync_close_buddy(struct sync_buddy *buddy) +{ + if (buddy->fd < 0) + return; + PARA_DEBUG_LOG("closing %s\n", buddy->sbi->url); + close(buddy->fd); + buddy->fd = -1; +} + +static void sync_close_buddies(struct sync_filter_context *ctx) +{ + struct sync_buddy *buddy; + + FOR_EACH_BUDDY(buddy, &ctx->buddies) + sync_close_buddy(buddy); +} + +static void sync_close(struct filter_node *fn) +{ + struct sync_filter_context *ctx = fn->private_data; + + sync_close_buddies(ctx); + if (ctx->listen_fd >= 0) { + close(ctx->listen_fd); + ctx->listen_fd = -1; + } + free(ctx); + fn->private_data = NULL; +} + +static void sync_free_config(void *conf) +{ + struct sync_filter_config *sfc = conf; + int i; + + for (i = 0; i < sfc->conf->buddy_given; i++) { + free(sfc->buddy_info[i].host); + freeaddrinfo(sfc->buddy_info[i].ai); + } + sync_filter_cmdline_parser_free(sfc->conf); + free(sfc); +} + +static void sync_open(struct filter_node *fn) +{ + int i, ret; + struct sync_filter_config *sfc = fn->conf; + struct sync_buddy *buddy; + struct sync_filter_context *ctx; + + assert(sfc); + + ctx = fn->private_data = para_calloc(sizeof(*ctx)); + INIT_LIST_HEAD(&ctx->buddies); + ctx->listen_fd = -1; + + /* create socket to listen for incoming packets */ + ret = makesock( + IPPROTO_UDP, + true /* passive */, + NULL /* no host required */, + sfc->conf->port_arg, + NULL /* no flowopts */ + ); + if (ret < 0) { + PARA_ERROR_LOG("could not create UDP listening socket %d\n", + sfc->conf->port_arg); + return; + } + ctx->listen_fd = ret; + PARA_INFO_LOG("listening on fd %d\n", ctx->listen_fd); + + for (i = 0; i < sfc->conf->buddy_given; i++) { + struct sync_buddy_info *sbi = sfc->buddy_info + i; + const char *url = sfc->conf->buddy_arg[i]; + int fd; + + /* make buddy udp socket from address info */ + assert(sbi->ai); + ret = makesock_addrinfo( + IPPROTO_UDP, + false /* not passive */, + sbi->ai, + NULL /* no flowopts */ + ); + if (ret < 0) { + PARA_WARNING_LOG("could not make socket for %s\n", + url); + goto fail; + } + fd = ret; + ret = mark_fd_nonblocking(fd); + if (ret < 0) { + PARA_ERROR_LOG("unable to set nonblock mode for %s\n", + url); + close(fd); + goto fail; + } + buddy = para_malloc(sizeof(*buddy)); + buddy->fd = fd; + buddy->sbi = sbi; + buddy->ping_received = false; + para_list_add(&buddy->node, &ctx->buddies); + + PARA_INFO_LOG("opened buddy %s on fd %d\n", url, fd); + continue; +fail: + PARA_WARNING_LOG("%s\n", para_strerror(-ret)); + } +} + +static int sync_parse_config(int argc, char **argv, void **result) +{ + int i, ret, n; + struct sync_filter_config *sfc; + struct sync_filter_args_info *conf = para_malloc(sizeof(*conf)); + + sync_filter_cmdline_parser(argc, argv, conf); /* exits on error */ + sfc = para_calloc(sizeof(*sfc)); + sfc->conf = conf; + n = conf->buddy_given; + sfc->buddy_info = para_malloc((n + 1) * sizeof(*sfc->buddy_info)); + PARA_INFO_LOG("initializing buddy info array of length %d\n", n); + for (i = 0; i < n; i++) { + const char *url = conf->buddy_arg[i]; + size_t len = strlen(url); + char *host = para_malloc(len + 1); + int port; + struct addrinfo *ai; + struct sync_buddy_info *sbi = sfc->buddy_info + i; + + if (!parse_url(url, host, len, &port)) { + free(host); + PARA_ERROR_LOG("could not parse url %s\n", url); + ret = -ERRNO_TO_PARA_ERROR(EINVAL); + goto fail; + } + if (port < 0) + port = conf->port_arg; + ret = lookup_address(IPPROTO_UDP, false /* not passive */, + host, port, &ai); + if (ret < 0) { + PARA_ERROR_LOG("host lookup failure for %s\n", url); + free(host); + goto fail; + } + sbi->url = url; + sbi->host = host; + sbi->port = port; + sbi->ai = ai; + sbi->disabled = false; + PARA_DEBUG_LOG("buddy #%d: %s\n", i, url); + } + *result = sfc; + return 1; +fail: + assert(ret < 0); + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + sync_free_config(sfc); + return ret; +} + +/* + * True if we sent a packet to all budies and received a packet from each + * enabled buddy. + */ +static bool sync_complete(struct sync_filter_context *ctx) +{ + struct sync_buddy *buddy; + + if (!ctx->ping_sent) + return false; + FOR_EACH_BUDDY(buddy, &ctx->buddies) + if (!buddy->sbi->disabled && !buddy->ping_received) + return false; + return true; +} + +static void sync_disable_active_buddies(struct sync_filter_context *ctx) +{ + struct sync_buddy *buddy; + + FOR_EACH_BUDDY(buddy, &ctx->buddies) { + if (buddy->sbi->disabled) + continue; + PARA_NOTICE_LOG("disabling %s\n", buddy->sbi->url); + buddy->sbi->disabled = true; + } +} + +static void sync_set_timeout(struct sync_filter_context *ctx, + struct sync_filter_config *sfc) +{ + struct timeval to; + + ms2tv(sfc->conf->timeout_arg, &to); + tv_add(now, &to, &ctx->timeout); +} + +static void sync_pre_select(struct sched *s, struct task *t) +{ + int ret; + struct filter_node *fn = container_of(t, struct filter_node, task); + struct sync_filter_context *ctx = fn->private_data; + struct sync_filter_config *sfc = fn->conf; + + if (list_empty(&ctx->buddies)) + return sched_min_delay(s); + if (ctx->listen_fd < 0) + return sched_min_delay(s); + ret = btr_node_status(fn->btrn, 0, BTR_NT_INTERNAL); + if (ret < 0) + return sched_min_delay(s); + para_fd_set(ctx->listen_fd, &s->rfds, &s->max_fileno); + if (ret == 0) + return; + if (ctx->timeout.tv_sec == 0) { /* must ping buddies */ + sync_set_timeout(ctx, sfc); + return sched_min_delay(s); + } + if (sync_complete(ctx)) /* push down what we have */ + return sched_min_delay(s); + sched_request_barrier_or_min_delay(&ctx->timeout, s); +} + +static struct sync_buddy *sync_find_buddy(struct sockaddr *addr, + struct list_head *list) +{ + struct sync_buddy *buddy; + + FOR_EACH_BUDDY(buddy, list) + if (sockaddr_equal(buddy->sbi->ai->ai_addr, addr)) + return buddy; + return NULL; +} + +static int sync_post_select(__a_unused struct sched *s, struct task *t) +{ + int ret; + struct filter_node *fn = container_of(t, struct filter_node, task); + struct sync_filter_context *ctx = fn->private_data; + struct sync_filter_config *sfc = fn->conf; + struct sync_buddy *buddy, *tmp; + + if (list_empty(&ctx->buddies)) + goto success; + ret = -E_SYNC_LISTEN_FD; + if (ctx->listen_fd < 0) + goto fail; + ret = btr_node_status(fn->btrn, 0, BTR_NT_INTERNAL); + if (ret < 0) + goto fail; + if (ret == 0) + return 0; + if (ctx->timeout.tv_sec == 0) + sync_set_timeout(ctx, sfc); + else { + if (tv_diff(&ctx->timeout, now, NULL) < 0) { + sync_disable_active_buddies(ctx); + goto success; + } + } + if (!ctx->ping_sent) { + FOR_EACH_BUDDY_SAFE(buddy, tmp, &ctx->buddies) { + char c = '\0'; + PARA_INFO_LOG("pinging %s (%s)\n", + buddy->sbi->url, buddy->sbi->disabled? + "disabled" : "enabled"); + ret = xwrite(buddy->fd, &c, 1); + sync_close_buddy(buddy); + if (ret < 0) { + PARA_WARNING_LOG("failed to write to %s: %s\n", + buddy->sbi->url, para_strerror(-ret)); + list_del(&buddy->node); + } + } + ctx->ping_sent = true; + } + if (FD_ISSET(ctx->listen_fd, &s->rfds)) { + char c; + for (;;) { + struct sockaddr src_addr; + socklen_t len = sizeof(src_addr); + ret = recvfrom(ctx->listen_fd, &c, 1, MSG_DONTWAIT, + &src_addr, &len); + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; + ret = -ERRNO_TO_PARA_ERROR(errno); + goto fail; + } + buddy = sync_find_buddy(&src_addr, &ctx->buddies); + if (!buddy) { + PARA_NOTICE_LOG("pinged by unknown\n"); + continue; + } + PARA_DEBUG_LOG("pinged by %s\n", buddy->sbi->url); + if (buddy->sbi->disabled) { + PARA_NOTICE_LOG("enabling %s\n", + buddy->sbi->url); + buddy->sbi->disabled = false; + } + list_del(&buddy->node); + } + } + if (!sync_complete(ctx)) + return 1; + /* + * Although all enabled buddies are in sync we do not splice out + * ourselves immediately. We rather wait until the timout expires, + * or the buddy list has become empty. This opens a time window + * for disabled buddies to become enabled by sending us a packet. + */ + btr_pushdown(fn->btrn); + return 1; +success: + ret = -E_SYNC_COMPLETE; /* success */ + goto out; +fail: + PARA_WARNING_LOG("%s\n", para_strerror(-ret)); +out: + sync_close_buddies(ctx); + btr_splice_out_node(&fn->btrn); + assert(ret < 0); + return ret; +} + +/** + * The synchronization filter. + * + * \param f Pointer to the struct to initialize. + */ +void sync_filter_init(struct filter *f) +{ + struct sync_filter_args_info dummy; + + sync_filter_cmdline_parser_init(&dummy); + f->open = sync_open; + f->close = sync_close; + f->pre_select = sync_pre_select; + f->post_select = sync_post_select; + f->parse_config = sync_parse_config; + f->free_config = sync_free_config; + f->help = (struct ggo_help)DEFINE_GGO_HELP(sync_filter); +} diff --git a/udp_recv.c b/udp_recv.c index ca0cfc33..15cf73eb 100644 --- a/udp_recv.c +++ b/udp_recv.c @@ -5,9 +5,14 @@ */ /** \file udp_recv.c Paraslash's udp receiver */ +#include #include #include #include +#include +#include +#include +#include #include "para.h" #include "error.h" diff --git a/udp_send.c b/udp_send.c index a74b6348..6ed5026a 100644 --- a/udp_send.c +++ b/udp_send.c @@ -6,13 +6,17 @@ /** \file udp_send.c Para_server's udp sender. */ - +#include +#include #include #include #include #include #include +#include +#include #include +#include #include "server.cmdline.h" #include "para.h" diff --git a/vss.c b/vss.c index d9e276c8..3ace49e9 100644 --- a/vss.c +++ b/vss.c @@ -11,8 +11,14 @@ * senders. */ +#include +#include #include #include +#include +#include +#include +#include #include "para.h" #include "error.h" diff --git a/web/manual.m4 b/web/manual.m4 index 62ebf3a8..e809c8b2 100644 --- a/web/manual.m4 +++ b/web/manual.m4 @@ -112,7 +112,7 @@ can be used by any scripting language to produce user interfaces with little programming effort. All connections between para_server and para_client are encrypted -with a symmetric RC4 session key. For each user of paraslash you must +with a symmetric session key. For each user of paraslash you must create a public/secret RSA key pair for authentication. If para_client is started without non-option arguments, an interactive @@ -505,9 +505,9 @@ User management para_server uses a challenge-response mechanism to authenticate requests from incoming connections, similar to ssh's public key authentication method. Authenticated connections are encrypted using -the RC4 stream cipher. +a stream cipher, either RC4 or AES in integer counter mode. -In this chapter we briefly describe RSA and RC4 and sketch the +In this chapter we briefly describe RSA, RC4 and AES, and sketch the REFERENCE(Client-server authentication, authentication handshake) between para_client and para_server. User management is discussed in the section on REFERENCE(The user_list file, the user_list file). @@ -517,8 +517,8 @@ in a REFERENCE(Connecting para_audiod, separate section). -RSA and RC4 -~~~~~~~~~~~ +RSA, RC4, AES +~~~~~~~~~~~~~ RSA is an asymmetric block cipher which is used in many applications, including ssh and gpg. An RSA key consists in fact of two keys, @@ -537,6 +537,15 @@ strong encryption by today's standards. Since the same key must never be used twice, a different, randomly-generated key is used for every new connection. +AES, the advanced encryption standard, is a well-known symmetric block +cipher, i.e. a transformation operating on fixed-length blocks which +is determined by a single key for both encryption and decryption. Any +block cipher can be turned into a stream cipher by generating +a pseudo-random key stream by encrypting successive values of a +counter. The AES_CTR128 stream cipher used in paraslash is obtained +in this way from the AES block cipher with a 128 bit block size. + + Client-server authentication ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -557,7 +566,7 @@ as follows: fixed-length buffer with random bytes, encrypts that buffer using the public key and sends the encrypted buffer to the client. The first part of the buffer is the challenge which - is used for authentication while the second part is the RC4 + is used for authentication while the second part is the session key. - para_client receives the encrypted buffer and decrypts it @@ -574,12 +583,12 @@ as follows: - Otherwise the user is considered authenticated and the client is allowed to proceed by sending a command to be executed. From - this point on the communication is encrypted using the RC4 - stream cipher with the session key known to both peers. + this point on the communication is encrypted using the stream + cipher with 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 RC4 crypto routines and on the +the implementation of the RSA, RC4 and AES crypto routines and on the infeasibility to invert the SHA1 function. Neither para_server or para_client create RSA keys on their own. This @@ -1071,17 +1080,18 @@ It is possible to change the behaviour of the add command by using the Troubleshooting ~~~~~~~~~~~~~~~ -Use the debug loglevel (option -l debug for most commands) to show -debugging info. Almost all paraslash executables have a brief online -help which is displayed by using the -h switch. The --detailed-help -option prints the full help text. +Use the debug loglevel (-l debug) to show debugging info. All paraslash +executables have a brief online help which is displayed when -h is +given. The --detailed-help option prints the full help text. If para_server crashed or was killed by SIGKILL (signal 9), it 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. It might be necessary to use --force (even if your name -isn't Luke). However, make sure para_server isn't running before -executing oslfsck --force. +database: + + oslfsck -fd ~/.paraslash/afs_database-0.4 + +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. @@ -1099,6 +1109,14 @@ care about the table contents. To check for invalid table contents, use This prints out references to missing audio files as well as invalid playlists and mood definitions. +Similarly, para_audiod refuses to start if its socket file exists, since +this indicates that another instance of para_audiod is running. After +a crash a stale socket file might remain and you must run + + para_audiod --force + +once to fix it up. + --------------------------------------- Audio formats and audio format handlers --------------------------------------- @@ -1135,7 +1153,17 @@ Excited Linear Prediction) coding. It is designed for voice over IP applications, has modest complexity and a small memory footprint. Wideband and narrowband (telephone quality) speech are supported. As for Vorbis audio, Speex bit-streams are often stored -in OGG files. +in OGG files. As of 2012 this codec is considered obsolete since the +Oppus codec, described below, surpasses its performance in all areas. + +*OGG/Opus* + +Opus is a lossy audio compression format standardized through RFC +6716 in 2012. It combines the speech-oriented SILK codec and the +low-latency CELT (Constrained Energy Lapped Transform) codec. Like +OGG/Vorbis and OGG/Speex, Opus data is usually encapsulated in OGG +containers. All known software patents which cover Opus are licensed +under royalty-free terms. *AAC* @@ -1218,9 +1246,7 @@ chunk table and reads the meta data. The audio format handler code is linked into para_server and executed via the _add_ command. The same code is also available as a stand-alone tool, para_afh, which can be used to print the technical data, the -chunk table and the meta data of a file. Furthermore, one can use -para_afh to cut an audio file, i.e. to select some of its chunks to -produce a new file containing only these chunks. +chunk table and the meta data of a file. ---------- Networking @@ -1506,10 +1532,10 @@ the output to STDOUT, the filter modules of para_audiod are always connected to a receiver which produces the input stream and a writer which absorbs the output stream. -Some filters depend on a specific library being installed and are -not compiled in if this library was not found at compile time. To -see the list of supported filters, run para_filter and para_audiod -with the --help option. The output looks similar to the following: +Some filters depend on a specific library and are not compiled in +if this library was not found at compile time. To see the list of +supported filters, run para_filter and para_audiod with the --help +option. The output looks similar to the following: Available filters: compress wav amp fecdec wmadec prebuffer oggdec aacdec mp3dec @@ -2181,6 +2207,8 @@ RFCs Congestion Control ID 2: TCP-like Congestion Control - XREFERENCE(http://www.ietf.org/rfc/rfc4342.txt, RFC 4342) (2006): Congestion Control ID 3: TCP-Friendly Rate Control (TFRC) + - XREFERENCE(http://www.ietf.org/rfc/rfc6716.txt, RFC 6716) (2012): + Definition of the Opus Audio Codec Application web pages ~~~~~~~~~~~~~~~~~~~~~