1
0
mirror of https://gitlab.xiph.org/xiph/ezstream.git synced 2024-11-03 04:17:18 -05:00

Merge changes to main(), urlParse() and streamPlaylist(). In main():

* Install the signal handler as late as possible.
 * Add new command line options: -v (verbose, use twice for even more verbose
   output) and -q (quiet, redirect standard error output from external de-/
   encoders to /dev/null.)
 * It is now an error to supply more than one -c parameter. This prevents
   unexpected results.
 * Add a stern warning when ezstream is running as root. Just Don't Do It.
   Leaving the configfile writeable to others by accident could mean instant
   root compromise.
 * Before handing the config file over to libxml, try to open it ourselves
   first. The error message from strerror() is a lot more helpful than the
   cryption I/O error printed by libxml.
 * Don't preallocate memory for urlParse().
 * Fix command line error messages, they seem to be for a different program
   than Ezstream.
 * More terse libshout error messages, just print which function failed.
   I consider these errors of questionable value for an end user, but at least
   a knowledgeable one will know instantly what went wrong.
 * Case insensitive matching of playlist file extensions.
 * Print the address, port and mountpoint that Ezstream is actually trying to
   connect to, instead of what the user supplied. That should make it easier
   to spot reasons for connect failures (e.g. typos.)
Changes in urlParse():
 * Let urlParse() allocate memory for hostname and mountpoint, as it knows
   how much memory is actually required.
 * Fix a buffer overflow of the tmpPort buffer by adding checks and using safe
   string functions.
 * Let the caller print an error message, instead of having the same printf()
   twice in urlParse().
The streamPlaylist() function has been rewritten to use the new playlist_*()
routines. Apart from the added playlist shuffle feature no functional change.


git-svn-id: https://svn.xiph.org/trunk/ezstream@12552 0101bb08-14d6-0310-b084-bc0e0c8e3800
This commit is contained in:
moritz 2007-02-25 01:14:36 +00:00
parent b59da7de0c
commit 710570627d

View File

@ -134,6 +134,8 @@ typedef struct tag_ID3Tag {
char * basename(const char *); char * basename(const char *);
#endif #endif
int strrcmp(const char *, const char *); int strrcmp(const char *, const char *);
int urlParse(const char *, char **, int *, char **);
int streamPlaylist(shout_t *, const char *);
char * getProgname(const char *); char * getProgname(const char *);
void usage(void); void usage(void);
void usageHelp(void); void usageHelp(void);
@ -163,36 +165,43 @@ strrcmp(const char *s, const char *sub)
return (memcmp(s + slen - sublen, sub, sublen)); return (memcmp(s + slen - sublen, sub, sublen));
} }
int urlParse(char *url, char *hostname, int *port, char *mountname) int
urlParse(const char *url, char **hostname, int *port, char **mountname)
{ {
char *p1; char *p1, *p2, *p3;
char *p2; char tmpPort[25] = "";
char *p3; size_t hostsiz, mountsiz;
char tmpPort[25] = "";
if (strncmp(url, "http://", strlen("http://"))) { if (hostname == NULL || port == NULL || mountname == NULL) {
printf("Invalid URL, must be of the form http://server:port/mountpoint\n"); printf("%s: urlParse(): Internal error: Bad arguments\n",
return 0; __progname);
exit(1);
} }
p1 = url + strlen("http://");
if (strncmp(url, "http://", strlen("http://")) != 0)
return (0);
p1 = (char *)(url) + strlen("http://");
p2 = strchr(p1, ':'); p2 = strchr(p1, ':');
if (!p2) { if (p2 == NULL)
printf("Invalid URL, must be of the form http://server:port/mountpoint\n"); return (0);
return 0; hostsiz = (p2 - p1) + 1;
} *hostname = xmalloc(hostsiz);
strncpy(hostname, p1, p2-p1); strlcpy(*hostname, p1, hostsiz);
p2++; p2++;
p3 = strchr(p2, '/'); p3 = strchr(p2, '/');
if (!p3) { if (p3 == NULL || p3 - p2 >= sizeof(tmpPort))
printf("Invalid URL, must be of the form http://server:port/mountpoint\n"); return (0);
return 0;
} strlcpy(tmpPort, p2, (p3 - p2) + 1);
memset(tmpPort, '\000', sizeof(tmpPort));
strncpy(tmpPort, p2, p3-p2);
*port = atoi(tmpPort); *port = atoi(tmpPort);
strcpy(mountname, p3);
return 1; mountsiz = strlen(p3) + 1;
*mountname = xmalloc(mountsiz);
strlcpy(*mountname, p3, mountsiz);
return (1);
} }
void replaceString(char *source, char *dest, char *from, char *to) void replaceString(char *source, char *dest, char *from, char *to)
@ -501,66 +510,45 @@ int streamFile(shout_t *shout, char *fileName) {
return ret; return ret;
} }
int streamPlaylist(shout_t *shout, char *fileName) { int
FILE *filep = NULL; streamPlaylist(shout_t *shout, const char *fileName)
char streamFileName[8096] = ""; {
char lastStreamFileName[8096] = ""; const char *song;
int loop = 1; char lastSong[PATH_MAX + 1];
filep = fopen(fileName, "r"); /*
if (filep == 0) { * XXX: This preserves traditional behavior, however, rereading the
printf("Cannot open %s\n", fileName); * playlist after each walkthrough seems a bit more logical.
return(0); */
} if (playlist == NULL) {
while (loop) { if ((playlist = playlist_read(fileName)) == NULL)
while (!feof(filep)) { return (0);
memset(streamFileName, '\000', sizeof(streamFileName)); } else
fgets(streamFileName, sizeof(streamFileName), filep); playlist_rewind(playlist);
streamFileName[strlen(streamFileName)-1] = '\000';
if (strlen(streamFileName) > 0) {
memset(lastStreamFileName, '\000', sizeof(lastStreamFileName));
strcpy(lastStreamFileName, streamFileName);
/* Skip entries that begin with a # */
if (strncmp(streamFileName, "#", 1)) {
streamFile(shout, streamFileName);
}
}
if (rereadPlaylist) {
rereadPlaylist = 0;
fclose(filep);
printf("Reopening playlist\n");
filep = fopen(fileName, "r");
if (filep == 0) {
printf("Cannot open %s\n", fileName);
return(0);
}
else {
int loop2 = 1;
printf("Repositioning to (%s)\n", lastStreamFileName);
while (loop2) {
/* If we reach the end before finding
our last spot, we will start over at the
beginning */
if (feof(filep)) {
loop2 = 0;
}
else {
memset(streamFileName, '\000', sizeof(streamFileName));
fgets(streamFileName, sizeof(streamFileName), filep);
streamFileName[strlen(streamFileName)-1] = '\000';
if (!strcmp(streamFileName, lastStreamFileName)) {
/* If we found our last position, then bump out of the loop */
loop2 = 0;
}
}
}
} if (pezConfig->shuffle)
playlist_shuffle(playlist);
while ((song = playlist_get_next(playlist)) != NULL) {
strlcpy(lastSong, song, sizeof(lastSong));
if (!streamFile(shout, song))
return (0);
if (rereadPlaylist) {
rereadPlaylist = rereadPlaylist_notify = 0;
printf("%s: Rereading playlist\n", __progname);
if (!playlist_reread(&playlist))
return (0);
if (pezConfig->shuffle)
playlist_shuffle(playlist);
else {
playlist_goto_entry(playlist, lastSong);
playlist_skip_next(playlist);
} }
continue;
} }
rewind(filep);
} }
return(1);
return (1);
} }
/* Borrowed from OpenNTPd-portable's compat-openbsd/bsd-misc.c */ /* Borrowed from OpenNTPd-portable's compat-openbsd/bsd-misc.c */
@ -603,210 +591,297 @@ usageHelp(void)
} }
int int
main(int argc, char **argv) main(int argc, char *argv[])
{ {
char c; char c;
char *configFile = NULL; char *configFile = NULL;
char *host = NULL; char *host = NULL;
int port = 0; int port = 0;
char *mount = NULL; char *mount = NULL;
shout_t *shout; shout_t *shout;
extern char *optarg;
extern int optind;
__progname = getProgname(argv[0]);
pezConfig = getEZConfig(); pezConfig = getEZConfig();
#ifndef WIN32
signal(SIGHUP, sig_handler); qFlag = 0;
vFlag = 0;
#ifdef HAVE_GETEUID
if (geteuid() == 0) {
printf("WARNING: You should not run %s as root. It can run other programs, which\n",
__progname);
printf(" may cause serious security problems.\n");
}
#endif #endif
shout_init(); while ((c = getopt(argc, argv, "c:hqv")) != -1) {
while ((c = getopt(argc, argv, "hc:")) != -1) {
switch (c) { switch (c) {
case 'c': case 'c':
configFile = optarg; if (configFile != NULL) {
break; printf("Error: multiple -c arguments given.\n");
case 'h':
usage(); usage();
usageHelp(); return (2);
return (0); }
default: configFile = xstrdup(optarg);
break; break;
case 'h':
usage();
usageHelp();
return (0);
case 'q':
qFlag = 1;
break;
case 'v':
vFlag++;
break;
case '?':
usage();
return (2);
default:
break;
} }
} }
argc -= optind;
argv += optind;
if (!configFile) { if (configFile == NULL) {
printf("You must supply a config file\n"); printf("You must supply a config file with the -c argument.\n");
usage(); usage();
} return (2);
else { } else {
parseConfig(configFile); /*
* Attempt to open configFile here for a more meaningful error
* message.
*/
FILE *tmp;
if ((tmp = fopen(configFile, "r")) == NULL) {
printf("%s: %s\n", configFile, strerror(errno));
usage();
return (2);
} else
fclose(tmp);
} }
if (pezConfig->URL) { if (!parseConfig(configFile))
host = (char *)malloc(strlen(pezConfig->URL) +1); return (2);
memset(host, '\000', strlen(pezConfig->URL) +1);
mount = (char *)malloc(strlen(pezConfig->URL) +1); shout_init();
memset(mount, '\000', strlen(pezConfig->URL) +1); playlist_init();
if (!urlParse(pezConfig->URL, host, &port, mount)) {
exit(0); if (pezConfig->URL == NULL) {
} printf("%s: Error: Missing <url>\n", configFile);
return (2);
}
if (!urlParse(pezConfig->URL, &host, &port, &mount)) {
printf("%s: Error: Invalid <url>:\n", configFile);
printf("Must be of the form ``http://server:port/mountpoint''.\n");
return (2);
} }
if ((host == NULL)) { if ((host == NULL)) {
printf("server is required\n"); printf("%s: Error: Invalid <url>: Missing server:\n", configFile);
usage(); printf("Must be of the form ``http://server:port/mountpoint''.\n");
return (2);
} }
if ((port == 0)) { if ((port < 1 || port > 65535)) {
printf("port is required\n"); printf("%s: Error: Invalid <url>: Missing or invalid port:\n", configFile);
usage(); printf("Must be of the form ``http://server:port/mountpoint''.\n");
} return (2);
if ((pezConfig->password == NULL)) {
printf("-p password is required\n");
usage();
} }
if ((mount == NULL)) { if ((mount == NULL)) {
printf("mountpoint is required\n"); printf("%s: Error: Invalid <url>: Missing mountpoint:\n", configFile);
usage(); printf("Must be of the form ``http://server:port/mountpoint''.\n");
return (2);
}
if ((pezConfig->password == NULL)) {
printf("%s: Error: Missing <sourcepassword>\n", configFile);
return (2);
} }
if ((pezConfig->fileName == NULL)) { if ((pezConfig->fileName == NULL)) {
printf("-f fileName is required\n"); printf("%s: Error: Missing <filename>\n", configFile);
usage(); return (2);
} }
if (pezConfig->format == 0) { if (pezConfig->format == NULL) {
printf("You must specify a format type of MP3, VORBIS, or THEORA\n"); printf("%s: Warning: Missing <format>:\n", configFile);
printf("Specify a stream format of either MP3, VORBIS or THEORA.\n");
} }
if (!(shout = shout_new())) {
printf("Could not allocate shout_t\n"); xfree(configFile);
return 1;
if ((shout = shout_new()) == NULL) {
printf("%s: shout_new(): %s", __progname, strerror(ENOMEM));
return (1);
} }
if (shout_set_host(shout, host) != SHOUTERR_SUCCESS) { if (shout_set_host(shout, host) != SHOUTERR_SUCCESS) {
printf("Error setting hostname: %s\n", shout_get_error(shout)); printf("%s: shout_set_host(): %s\n", __progname,
return 1; shout_get_error(shout));
return (1);
} }
if (shout_set_protocol(shout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) { if (shout_set_protocol(shout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) {
printf("Error setting protocol: %s\n", shout_get_error(shout)); printf("%s: shout_set_protocol(): %s\n", __progname,
return 1; shout_get_error(shout));
return (1);
} }
if (shout_set_port(shout, port) != SHOUTERR_SUCCESS) { if (shout_set_port(shout, port) != SHOUTERR_SUCCESS) {
printf("Error setting port: %s\n", shout_get_error(shout)); printf("%s: shout_set_port: %s\n", __progname,
return 1; shout_get_error(shout));
return (1);
} }
if (shout_set_password(shout, pezConfig->password) != SHOUTERR_SUCCESS) { if (shout_set_password(shout, pezConfig->password) != SHOUTERR_SUCCESS) {
printf("Error setting password: %s\n", shout_get_error(shout)); printf("%s: shout_set_password(): %s\n", __progname,
return 1; shout_get_error(shout));
return (1);
} }
if (shout_set_mount(shout, mount) != SHOUTERR_SUCCESS) { if (shout_set_mount(shout, mount) != SHOUTERR_SUCCESS) {
printf("Error setting mount: %s\n", shout_get_error(shout)); printf("%s: shout_set_mount(): %s\n", __progname,
return 1; shout_get_error(shout));
return (1);
} }
if (shout_set_user(shout, "source") != SHOUTERR_SUCCESS) { if (shout_set_user(shout, "source") != SHOUTERR_SUCCESS) {
printf("Error setting user: %s\n", shout_get_error(shout)); printf("%s: shout_set_user(): %s\n", __progname,
return 1; shout_get_error(shout));
return (1);
} }
if (!strcmp(pezConfig->format, MP3_FORMAT)) { if (!strcmp(pezConfig->format, MP3_FORMAT)) {
if (shout_set_format(shout, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS) { if (shout_set_format(shout, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS) {
printf("Error setting user: %s\n", shout_get_error(shout)); printf("%s: shout_set_format(MP3): %s\n",
return 1; __progname, shout_get_error(shout));
return (1);
} }
} }
if (!strcmp(pezConfig->format, VORBIS_FORMAT)) { if (!strcmp(pezConfig->format, VORBIS_FORMAT) ||
!strcmp(pezConfig->format, THEORA_FORMAT)) {
if (shout_set_format(shout, SHOUT_FORMAT_OGG) != SHOUTERR_SUCCESS) { if (shout_set_format(shout, SHOUT_FORMAT_OGG) != SHOUTERR_SUCCESS) {
printf("Error setting user: %s\n", shout_get_error(shout)); printf("%s: shout_set_format(OGG): %s\n",
return 1; __progname, shout_get_error(shout));
} return (1);
}
if (!strcmp(pezConfig->format, THEORA_FORMAT)) {
if (shout_set_format(shout, SHOUT_FORMAT_OGG) != SHOUTERR_SUCCESS) {
printf("Error setting user: %s\n", shout_get_error(shout));
return 1;
} }
} }
if (pezConfig->serverName) { if (pezConfig->serverName) {
if (shout_set_name(shout, pezConfig->serverName) != SHOUTERR_SUCCESS) { if (shout_set_name(shout, pezConfig->serverName) != SHOUTERR_SUCCESS) {
printf("Error setting server name: %s\n", shout_get_error(shout)); printf("%s: shout_set_name(): %s\n",
return 1; __progname, shout_get_error(shout));
return (1);
} }
} }
if (pezConfig->serverURL) { if (pezConfig->serverURL) {
if (shout_set_url(shout, pezConfig->serverURL) != SHOUTERR_SUCCESS) { if (shout_set_url(shout, pezConfig->serverURL) != SHOUTERR_SUCCESS) {
printf("Error setting server url: %s\n", shout_get_error(shout)); printf("%s: shout_set_url(): %s\n",
return 1; __progname, shout_get_error(shout));
return (1);
} }
} }
if (pezConfig->serverGenre) { if (pezConfig->serverGenre) {
if (shout_set_genre(shout, pezConfig->serverGenre) != SHOUTERR_SUCCESS) { if (shout_set_genre(shout, pezConfig->serverGenre) != SHOUTERR_SUCCESS) {
printf("Error setting server genre: %s\n", shout_get_error(shout)); printf("%s: shout_set_genre(): %s\n",
return 1; __progname, shout_get_error(shout));
return (1);
} }
} }
if (pezConfig->serverDescription) { if (pezConfig->serverDescription) {
if (shout_set_description(shout, pezConfig->serverDescription) != SHOUTERR_SUCCESS) { if (shout_set_description(shout, pezConfig->serverDescription) != SHOUTERR_SUCCESS) {
printf("Error setting server description: %s\n", shout_get_error(shout)); printf("%s: shout_set_description(): %s\n",
return 1; __progname, shout_get_error(shout));
return (1);
} }
} }
if (pezConfig->serverBitrate) { if (pezConfig->serverBitrate) {
if (shout_set_audio_info(shout, SHOUT_AI_BITRATE, pezConfig->serverBitrate) != SHOUTERR_SUCCESS) { if (shout_set_audio_info(shout, SHOUT_AI_BITRATE, pezConfig->serverBitrate) != SHOUTERR_SUCCESS) {
printf("Error setting server bitrate: %s\n", shout_get_error(shout)); printf("%s: shout_set_audio_info(AI_BITRATE): %s\n",
return 1; __progname, shout_get_error(shout));
return (1);
} }
} }
if (pezConfig->serverChannels) { if (pezConfig->serverChannels) {
if (shout_set_audio_info(shout, SHOUT_AI_CHANNELS, pezConfig->serverChannels) != SHOUTERR_SUCCESS) { if (shout_set_audio_info(shout, SHOUT_AI_CHANNELS, pezConfig->serverChannels) != SHOUTERR_SUCCESS) {
printf("Error setting server channels: %s\n", shout_get_error(shout)); printf("%s: shout_set_audio_info(AI_CHANNELS): %s\n",
return 1; __progname, shout_get_error(shout));
return (1);
} }
} }
if (pezConfig->serverSamplerate) { if (pezConfig->serverSamplerate) {
if (shout_set_audio_info(shout, SHOUT_AI_SAMPLERATE, pezConfig->serverSamplerate) != SHOUTERR_SUCCESS) { if (shout_set_audio_info(shout, SHOUT_AI_SAMPLERATE, pezConfig->serverSamplerate) != SHOUTERR_SUCCESS) {
printf("Error setting server samplerate: %s\n", shout_get_error(shout)); printf("%s: shout_set_audio_info(AI_SAMPLERATE): %s\n",
return 1; __progname, shout_get_error(shout));
return (1);
} }
} }
if (pezConfig->serverQuality) { if (pezConfig->serverQuality) {
if (shout_set_audio_info(shout, SHOUT_AI_QUALITY, pezConfig->serverQuality) != SHOUTERR_SUCCESS) { if (shout_set_audio_info(shout, SHOUT_AI_QUALITY, pezConfig->serverQuality) != SHOUTERR_SUCCESS) {
printf("Error setting server quality: %s\n", shout_get_error(shout)); printf("%s: shout_set_audio_info(AI_QUALITY): %s\n",
return 1; __progname, shout_get_error(shout));
return (1);
} }
} }
if (shout_set_public(shout, pezConfig->serverPublic) != SHOUTERR_SUCCESS) { if (shout_set_public(shout, pezConfig->serverPublic) != SHOUTERR_SUCCESS) {
printf("Error setting server public flag: %s\n", shout_get_error(shout)); printf("%s: shout_set_public(): %s\n",
return 1; __progname, shout_get_error(shout));
return (1);
} }
printf("Connecting to %s...", pezConfig->URL); #ifdef HAVE_SIGNALS
if (shout_open(shout) == SHOUTERR_SUCCESS) { signal(SIGHUP, sig_handler);
printf("SUCCESS.\n"); signal(SIGUSR1, sig_handler);
while (1) { #endif /* HAVE_SIGNALS */
if (!strrcmp(pezConfig->fileName, ".m3u")) {
streamPlaylist(shout, pezConfig->fileName); if (qFlag) {
} int fd;
else {
streamFile(shout, pezConfig->fileName); if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1) {
} printf("%s: Cannot open %s for redirecting STDERR output: %s\n",
__progname, _PATH_DEVNULL, strerror(errno));
return (1);
} }
} else {
printf("FAILED: %s\n", shout_get_error(shout)); dup2(fd, STDERR_FILENO);
if (fd > 2)
close(fd);
} }
if (shout_open(shout) == SHOUTERR_SUCCESS) {
int ret;
char *tmpFileName, *p;
printf("%s: Connected to http://%s:%d%s\n", __progname,
host, port, mount);
tmpFileName = xstrdup(pezConfig->fileName);
for (p = tmpFileName; *p != '\0'; p++)
*p = tolower((int)*p);
if (strrcmp(tmpFileName, ".m3u") == 0 ||
strrcmp(tmpFileName, ".txt") == 0)
playlistMode = 1;
else
playlistMode = 0;
xfree(tmpFileName);
ret = 1;
do {
if (playlistMode)
ret = streamPlaylist(shout,
pezConfig->fileName);
else
ret = streamFile(shout, pezConfig->fileName);
} while (ret);
} else
printf("%s: Connection to http://%s:%d%s failed: %s\n", __progname,
host, port, mount, shout_get_error(shout));
shout_close(shout); shout_close(shout);
playlist_free(playlist);
playlist_shutdown();
shout_shutdown(); shout_shutdown();
if (host) { xfree(host);
free(host); xfree(mount);
}
if (mount) {
free(mount);
}
return 0; return 0;
} }