From 710570627d056bd3e4957f4863e8669065400a01 Mon Sep 17 00:00:00 2001 From: moritz Date: Sun, 25 Feb 2007 01:14:36 +0000 Subject: [PATCH] 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 --- src/ezstream.c | 463 ++++++++++++++++++++++++++++--------------------- 1 file changed, 269 insertions(+), 194 deletions(-) diff --git a/src/ezstream.c b/src/ezstream.c index dbeb454..0cc8fda 100644 --- a/src/ezstream.c +++ b/src/ezstream.c @@ -134,6 +134,8 @@ typedef struct tag_ID3Tag { char * basename(const char *); #endif int strrcmp(const char *, const char *); +int urlParse(const char *, char **, int *, char **); +int streamPlaylist(shout_t *, const char *); char * getProgname(const char *); void usage(void); void usageHelp(void); @@ -163,36 +165,43 @@ strrcmp(const char *s, const char *sub) 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 *p2; - char *p3; - char tmpPort[25] = ""; + char *p1, *p2, *p3; + char tmpPort[25] = ""; + size_t hostsiz, mountsiz; - if (strncmp(url, "http://", strlen("http://"))) { - printf("Invalid URL, must be of the form http://server:port/mountpoint\n"); - return 0; + if (hostname == NULL || port == NULL || mountname == NULL) { + printf("%s: urlParse(): Internal error: Bad arguments\n", + __progname); + exit(1); } - p1 = url + strlen("http://"); + + if (strncmp(url, "http://", strlen("http://")) != 0) + return (0); + + p1 = (char *)(url) + strlen("http://"); p2 = strchr(p1, ':'); - if (!p2) { - printf("Invalid URL, must be of the form http://server:port/mountpoint\n"); - return 0; - } - strncpy(hostname, p1, p2-p1); + if (p2 == NULL) + return (0); + hostsiz = (p2 - p1) + 1; + *hostname = xmalloc(hostsiz); + strlcpy(*hostname, p1, hostsiz); + p2++; p3 = strchr(p2, '/'); - if (!p3) { - printf("Invalid URL, must be of the form http://server:port/mountpoint\n"); - return 0; - } - memset(tmpPort, '\000', sizeof(tmpPort)); - strncpy(tmpPort, p2, p3-p2); + if (p3 == NULL || p3 - p2 >= sizeof(tmpPort)) + return (0); + + strlcpy(tmpPort, p2, (p3 - p2) + 1); *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) @@ -501,66 +510,45 @@ int streamFile(shout_t *shout, char *fileName) { return ret; } -int streamPlaylist(shout_t *shout, char *fileName) { - FILE *filep = NULL; - char streamFileName[8096] = ""; - char lastStreamFileName[8096] = ""; - int loop = 1; +int +streamPlaylist(shout_t *shout, const char *fileName) +{ + const char *song; + char lastSong[PATH_MAX + 1]; - filep = fopen(fileName, "r"); - if (filep == 0) { - printf("Cannot open %s\n", fileName); - return(0); - } - while (loop) { - while (!feof(filep)) { - memset(streamFileName, '\000', sizeof(streamFileName)); - fgets(streamFileName, sizeof(streamFileName), filep); - 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; - } - } - } + /* + * XXX: This preserves traditional behavior, however, rereading the + * playlist after each walkthrough seems a bit more logical. + */ + if (playlist == NULL) { + if ((playlist = playlist_read(fileName)) == NULL) + return (0); + } else + playlist_rewind(playlist); - } + 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 */ @@ -603,210 +591,297 @@ usageHelp(void) } int -main(int argc, char **argv) +main(int argc, char *argv[]) { - char c; - char *configFile = NULL; - char *host = NULL; - int port = 0; - char *mount = NULL; - shout_t *shout; - + char c; + char *configFile = NULL; + char *host = NULL; + int port = 0; + char *mount = NULL; + shout_t *shout; + extern char *optarg; + extern int optind; + __progname = getProgname(argv[0]); 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 - shout_init(); - - while ((c = getopt(argc, argv, "hc:")) != -1) { + while ((c = getopt(argc, argv, "c:hqv")) != -1) { switch (c) { - case 'c': - configFile = optarg; - break; - case 'h': + case 'c': + if (configFile != NULL) { + printf("Error: multiple -c arguments given.\n"); usage(); - usageHelp(); - return (0); - default: - break; + return (2); + } + configFile = xstrdup(optarg); + 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) { - printf("You must supply a config file\n"); + if (configFile == NULL) { + printf("You must supply a config file with the -c argument.\n"); usage(); - } - else { - parseConfig(configFile); + return (2); + } else { + /* + * 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) { - host = (char *)malloc(strlen(pezConfig->URL) +1); - memset(host, '\000', strlen(pezConfig->URL) +1); - mount = (char *)malloc(strlen(pezConfig->URL) +1); - memset(mount, '\000', strlen(pezConfig->URL) +1); - if (!urlParse(pezConfig->URL, host, &port, mount)) { - exit(0); - } + if (!parseConfig(configFile)) + return (2); + + shout_init(); + playlist_init(); + + if (pezConfig->URL == NULL) { + printf("%s: Error: Missing \n", configFile); + return (2); + } + if (!urlParse(pezConfig->URL, &host, &port, &mount)) { + printf("%s: Error: Invalid :\n", configFile); + printf("Must be of the form ``http://server:port/mountpoint''.\n"); + return (2); } if ((host == NULL)) { - printf("server is required\n"); - usage(); + printf("%s: Error: Invalid : Missing server:\n", configFile); + printf("Must be of the form ``http://server:port/mountpoint''.\n"); + return (2); } - if ((port == 0)) { - printf("port is required\n"); - usage(); - } - if ((pezConfig->password == NULL)) { - printf("-p password is required\n"); - usage(); + if ((port < 1 || port > 65535)) { + printf("%s: Error: Invalid : Missing or invalid port:\n", configFile); + printf("Must be of the form ``http://server:port/mountpoint''.\n"); + return (2); } if ((mount == NULL)) { - printf("mountpoint is required\n"); - usage(); + printf("%s: Error: Invalid : Missing mountpoint:\n", configFile); + printf("Must be of the form ``http://server:port/mountpoint''.\n"); + return (2); + } + if ((pezConfig->password == NULL)) { + printf("%s: Error: Missing \n", configFile); + return (2); } if ((pezConfig->fileName == NULL)) { - printf("-f fileName is required\n"); - usage(); + printf("%s: Error: Missing \n", configFile); + return (2); } - if (pezConfig->format == 0) { - printf("You must specify a format type of MP3, VORBIS, or THEORA\n"); + if (pezConfig->format == NULL) { + printf("%s: Warning: Missing :\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"); - return 1; + + xfree(configFile); + + if ((shout = shout_new()) == NULL) { + printf("%s: shout_new(): %s", __progname, strerror(ENOMEM)); + return (1); } - if (shout_set_host(shout, host) != SHOUTERR_SUCCESS) { - printf("Error setting hostname: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_host(): %s\n", __progname, + shout_get_error(shout)); + return (1); } - if (shout_set_protocol(shout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) { - printf("Error setting protocol: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_protocol(): %s\n", __progname, + shout_get_error(shout)); + return (1); } - if (shout_set_port(shout, port) != SHOUTERR_SUCCESS) { - printf("Error setting port: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_port: %s\n", __progname, + shout_get_error(shout)); + return (1); } - if (shout_set_password(shout, pezConfig->password) != SHOUTERR_SUCCESS) { - printf("Error setting password: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_password(): %s\n", __progname, + shout_get_error(shout)); + return (1); } if (shout_set_mount(shout, mount) != SHOUTERR_SUCCESS) { - printf("Error setting mount: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_mount(): %s\n", __progname, + shout_get_error(shout)); + return (1); } - if (shout_set_user(shout, "source") != SHOUTERR_SUCCESS) { - printf("Error setting user: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_user(): %s\n", __progname, + shout_get_error(shout)); + return (1); } if (!strcmp(pezConfig->format, MP3_FORMAT)) { if (shout_set_format(shout, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS) { - printf("Error setting user: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_format(MP3): %s\n", + __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) { - printf("Error setting user: %s\n", 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; + printf("%s: shout_set_format(OGG): %s\n", + __progname, shout_get_error(shout)); + return (1); } } if (pezConfig->serverName) { if (shout_set_name(shout, pezConfig->serverName) != SHOUTERR_SUCCESS) { - printf("Error setting server name: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_name(): %s\n", + __progname, shout_get_error(shout)); + return (1); } } if (pezConfig->serverURL) { if (shout_set_url(shout, pezConfig->serverURL) != SHOUTERR_SUCCESS) { - printf("Error setting server url: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_url(): %s\n", + __progname, shout_get_error(shout)); + return (1); } } if (pezConfig->serverGenre) { if (shout_set_genre(shout, pezConfig->serverGenre) != SHOUTERR_SUCCESS) { - printf("Error setting server genre: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_genre(): %s\n", + __progname, shout_get_error(shout)); + return (1); } } if (pezConfig->serverDescription) { if (shout_set_description(shout, pezConfig->serverDescription) != SHOUTERR_SUCCESS) { - printf("Error setting server description: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_description(): %s\n", + __progname, shout_get_error(shout)); + return (1); } } if (pezConfig->serverBitrate) { if (shout_set_audio_info(shout, SHOUT_AI_BITRATE, pezConfig->serverBitrate) != SHOUTERR_SUCCESS) { - printf("Error setting server bitrate: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_audio_info(AI_BITRATE): %s\n", + __progname, shout_get_error(shout)); + return (1); } } if (pezConfig->serverChannels) { if (shout_set_audio_info(shout, SHOUT_AI_CHANNELS, pezConfig->serverChannels) != SHOUTERR_SUCCESS) { - printf("Error setting server channels: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_audio_info(AI_CHANNELS): %s\n", + __progname, shout_get_error(shout)); + return (1); } } if (pezConfig->serverSamplerate) { if (shout_set_audio_info(shout, SHOUT_AI_SAMPLERATE, pezConfig->serverSamplerate) != SHOUTERR_SUCCESS) { - printf("Error setting server samplerate: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_audio_info(AI_SAMPLERATE): %s\n", + __progname, shout_get_error(shout)); + return (1); } } if (pezConfig->serverQuality) { if (shout_set_audio_info(shout, SHOUT_AI_QUALITY, pezConfig->serverQuality) != SHOUTERR_SUCCESS) { - printf("Error setting server quality: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_audio_info(AI_QUALITY): %s\n", + __progname, shout_get_error(shout)); + return (1); } } if (shout_set_public(shout, pezConfig->serverPublic) != SHOUTERR_SUCCESS) { - printf("Error setting server public flag: %s\n", shout_get_error(shout)); - return 1; + printf("%s: shout_set_public(): %s\n", + __progname, shout_get_error(shout)); + return (1); } - printf("Connecting to %s...", pezConfig->URL); - if (shout_open(shout) == SHOUTERR_SUCCESS) { - printf("SUCCESS.\n"); - while (1) { - if (!strrcmp(pezConfig->fileName, ".m3u")) { - streamPlaylist(shout, pezConfig->fileName); - } - else { - streamFile(shout, pezConfig->fileName); - } +#ifdef HAVE_SIGNALS + signal(SIGHUP, sig_handler); + signal(SIGUSR1, sig_handler); +#endif /* HAVE_SIGNALS */ + + if (qFlag) { + int fd; + + 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); + playlist_free(playlist); + playlist_shutdown(); + shout_shutdown(); - if (host) { - free(host); - } - if (mount) { - free(mount); - } + xfree(host); + xfree(mount); return 0; }