diff options
Diffstat (limited to 'src/mmsrip/mms.c')
-rw-r--r-- | src/mmsrip/mms.c | 1239 |
1 files changed, 1239 insertions, 0 deletions
diff --git a/src/mmsrip/mms.c b/src/mmsrip/mms.c new file mode 100644 index 0000000..022f7ae --- /dev/null +++ b/src/mmsrip/mms.c @@ -0,0 +1,1239 @@ +/* + * $RCSfile: mms.c,v $ + * $Date: 2006/01/23 20:30:43 $ - $Revision: 1.33 $ + * + * This file is distributed as a part of MMSRIP ( MMS Ripper ). + * Copyright (c) 2005-2006 Nicolas BENOIT + * + * It is highly based on the work of SDP Multimedia and Major MMS. + * They deserve all the credits for it. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#define _GNU_SOURCE + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <unistd.h> +#include <strings.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> + +#include "common.h" +#include "mms.h" +#include "error.h" + + +/* + * Put 32 bits inside a command buffer. + */ +static void +mms_put_32 ( MMS_PACKET *pak, + uint32_t value ) +{ + pak->buf[pak->num_bytes ] = value % 256; + value = value >> 8; + pak->buf[pak->num_bytes+1] = value % 256 ; + value = value >> 8; + pak->buf[pak->num_bytes+2] = value % 256 ; + value = value >> 8; + pak->buf[pak->num_bytes+3] = value % 256 ; + + pak->num_bytes += 4; + return; +} + + +/* + * Returns the 32 bits located at specified offset. + */ +static uint32_t +mms_get_32 ( const unsigned char *buf, + const int offset ) +{ + return ( (((uint32_t)buf[offset+0]) << 0) | + (((uint32_t)buf[offset+1]) << 8) | + (((uint32_t)buf[offset+2]) << 16) | + (((uint32_t)buf[offset+3]) << 24) ); +} + + +/* + * Returns the 64 bits located at specified offset. + */ +static uint64_t +mms_get_64 ( const unsigned char *buf, + const int offset ) +{ + return ( (((uint64_t)buf[offset+0]) << 0) | + (((uint64_t)buf[offset+1]) << 8) | + (((uint64_t)buf[offset+2]) << 16) | + (((uint64_t)buf[offset+3]) << 24) | + (((uint64_t)buf[offset+4]) << 32) | + (((uint64_t)buf[offset+5]) << 40) | + (((uint64_t)buf[offset+6]) << 48) | + (((uint64_t)buf[offset+7]) << 56) ); +} + + +/* + * Converts a string into a UTF-16 string. + */ +static void +mms_string_utf16 ( uint8_t *dest, + unsigned char *src, + const ssize_t len ) +{ + ssize_t i; + memset ( dest, 0, len ); + + for ( i=0; i<len; ++i ) + { + dest[i*2] = src[i]; + dest[i*2+1] = 0; + } + + dest[i*2] = 0; + dest[i*2+1] = 0; + return; +} + + +/* + * Checks a URL for a correct MMS protocol. + * Returns the host name index if the protocol is valid, else it returns MMS_RET_ERROR. + */ +static int +mms_check_protocol ( const char *url ) +{ + static const char *proto[] = { "mms://", "mmsu://", "mmst://", "mmsh://", NULL }; + static const int proto_len[] = { 6, 7, 7, 7, 0 }; + register const char *p = proto[0]; + int i = 0; + + while ( p != NULL ) + { + if ( strncmp ( url, p, proto_len[i] ) == 0 ) + return proto_len[i]; + + p = proto[++i]; + } + + return MMS_RET_ERROR; +} + + +/* + * Prints out a command packet (debug purpose). + */ +static void +mms_print_packet ( FILE *stddebug, + const MMS_PACKET *pak, + const ssize_t length, + const int orig ) +{ + ssize_t i; + unsigned char c; + + fprintf ( stddebug, "\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n" ); + + if ( orig == MMS_SERVER ) + fprintf ( stddebug, " command from server (%d bytes)\n", length ); + else + fprintf ( stddebug, " command from client (%d bytes)\n", length ); + + fprintf ( stddebug, " start sequence: 0x%08x\n", mms_get_32(pak->buf, 0) ); + fprintf ( stddebug, " command id: 0x%08x\n", mms_get_32(pak->buf, 4) ); + fprintf ( stddebug, " length: 0x%8x \n", mms_get_32(pak->buf, 8) ); + fprintf ( stddebug, " len8: 0x%8x \n", mms_get_32(pak->buf, 16) ); + fprintf ( stddebug, " sequence #: 0x%08x\n", mms_get_32(pak->buf, 20) ); + fprintf ( stddebug, " len8 (II): 0x%8x \n", mms_get_32(pak->buf, 32) ); + fprintf ( stddebug, " dir | comm: 0x%08x\n", mms_get_32(pak->buf, 36) ); + fprintf ( stddebug, " switches: 0x%08x\n", mms_get_32(pak->buf, 40) ); + + fprintf ( stddebug, "\nascii contents:\n" ); + + for ( i=48; i<length; i+=2 ) + { + c = pak->buf[i]; + + if ( ( c>=32 ) && ( c<=128 ) ) + fprintf ( stddebug, "%c", c ); + else + fprintf ( stddebug, "." ); + } + + fprintf ( stddebug, "\n\npackage hexdump:\n " ); + + i = 0; + + while ( 1 ) + { + c = pak->buf[i]; + fprintf ( stddebug, "%02x", c ); + + ++i; + + if ( i < length ) + { + if ( (i % 16) == 0 ) + fprintf ( stddebug, "\n" ); + + if ( (i % 2) == 0 ) + fprintf ( stddebug, " " ); + } + else + break; + } + + fprintf ( stddebug, "\n\n" ); + fflush ( stddebug ); + return; +} + + +/* + * Sends a command packet to the server. + * Returns MMS_RET_SUCCESS if success, MMS_RET_ERROR either. + */ +static int +mms_send_packet ( MMS *mms, + const int command, + const uint32_t switches, + const uint32_t extra, + const ssize_t length, + const uint8_t *data ) +{ + MMS_PACKET pak; + ssize_t written_len; + + const ssize_t len8 = ( length + (length%8) ) / 8; + + pak.num_bytes = 0; + + mms_put_32 ( &pak, 0x00000001 ); /* start sequence */ + mms_put_32 ( &pak, 0xB00BFACE ); /* #-)) */ + mms_put_32 ( &pak, length + 32 ); + mms_put_32 ( &pak, 0x20534d4d ); /* protocol type "MMS " */ + mms_put_32 ( &pak, len8 + 4 ); + mms_put_32 ( &pak, mms->seq_num++ ); + mms_put_32 ( &pak, 0x0 ); /* unknown */ + mms_put_32 ( &pak, 0x0 ); + mms_put_32 ( &pak, 2+len8 ); + mms_put_32 ( &pak, 0x00030000 | command ); /* dir | command */ + mms_put_32 ( &pak, switches ); + mms_put_32 ( &pak, extra ); + + memcpy ( &pak.buf[48], data, length ); + + written_len = write ( mms->socket, pak.buf, length+48 ); + + if ( written_len == -1 ) + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_send_packet", "write() said: %s", strerror(errno) ); +#else + error ( "mms_send_packet", "write() failed" ); +#endif + + return MMS_RET_ERROR; + } + else if ( written_len != (length+48) ) + { + if ( !mms->quiet ) + error ( "mms_send_packet", "did not write enough bytes" ); + + return MMS_RET_ERROR; + } + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &pak, length+48, MMS_CLIENT ); + + return MMS_RET_SUCCESS; +} + + +/* + * Reads some bytes from the socket. + * This is blocking until we don't have the required amount of bytes. + * Returns MMS_RET_SUCCESS if success, MMS_RET_ERROR either. + */ +static int +mms_recv_packet ( const int s, + MMS_PACKET *pak, + size_t count, + const int quiet ) +{ + ssize_t read_len, total; + total = 0; + + if ( MMS_BUF_SIZE < count ) + { + if ( !quiet ) + warning ( "mms_recv_packet", "caller is too greedy" ); + + count = MMS_BUF_SIZE; + } + + while ( total < count ) + { + read_len = read ( s, &pak->buf[total], count-total ); + + if ( read_len == -1 ) + { + if ( !quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_recv_packet", "read() said: %s", strerror(errno) ); +#else + error ( "mms_recv_packet", "read() failed" ); +#endif + + return MMS_RET_ERROR; + } + + total += read_len; + } + + return MMS_RET_SUCCESS; +} + + +/* + * Gets server's command. + * The first 8 bytes can be skipped setting offset to 8. + * It writes the packet length in the provided storage. + * Returns command ID, MMS_CMD_INVALID if error. + */ +static int +mms_recv_cmd_packet ( const int s, + MMS_PACKET *pak, + ssize_t *packet_len, + const int offset, + const int quiet ) +{ + MMS_PACKET tmp; + + if ( ( offset > 8 ) || ( offset < 0 ) ) + { + if ( !quiet ) + error ( "mms_recv_cmd_packet", "provided offset is invalid" ); + + return MMS_RET_ERROR; + } + + if ( mms_recv_packet ( s, &tmp, 12-offset, quiet ) != 0 ) + { + if ( !quiet ) + error ( "mms_recv_cmd_packet", "unable to get packet header" ); + + return MMS_RET_ERROR; + } + + memcpy ( &pak->buf[offset], tmp.buf, 12-offset ); + + if ( offset == 0 ) + { + if ( mms_get_32(pak->buf, 4) != 0xb00bface ) /* probably a coincidence... */ + { + if ( !quiet ) + error ( "mms_recv_cmd_packet", "answer should have been a cmd packet" ); + + return MMS_RET_ERROR; + } + } + + memcpy ( packet_len, &pak->buf[8], 4 ); + *packet_len = mms_get_32 ( (unsigned char *) packet_len, 0 ) + 4; + + if ( ( *packet_len + 12 ) > MMS_BUF_SIZE ) + { + if ( !quiet ) + error ( "mms_recv_cmd_packet", "incoming packet is too big for me" ); + + return MMS_RET_ERROR; + } + + if ( mms_recv_packet ( s, &tmp, *packet_len, quiet ) != 0 ) + { + if ( !quiet ) + error ( "mms_recv_cmd_packet", "unable to get packet body" ); + + return MMS_RET_ERROR; + } + + memcpy ( &pak->buf[12], tmp.buf, *packet_len ); + + return ( mms_get_32(pak->buf, 36) & 0xFFFF ); +} + + +/* + * Gets the header of the ASF/WMV stream/file. + * Returns the amount of bytes received, MMS_RET_ERROR if we encounter an error. + */ +static ssize_t +mms_recv_header_packet ( MMS *mms, + MMS_PACKET *pak ) +{ + MMS_PACKET pre_header; + MMS_PACKET tmp; + ssize_t header_len; + ssize_t packet_len; + + header_len = 0; + + while ( 1 ) + { + if ( mms_recv_packet ( mms->socket, &pre_header, 8, mms->quiet ) != 0 ) + { + if ( !mms->quiet ) + error ( "mms_recv_header_packet", "unable to get pre-header" ); + + return MMS_RET_ERROR; + } + + if ( pre_header.buf[4] == 0x02 ) + { + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n" ); + fprintf ( mms->stddebug, " getting header packet from server\n\n" ); + } + + if ( mms->stddebug != NULL ) + { + static int i; /* static, so we don't recreate it for each call */ + + for ( i=0; i<8; ++i ) + fprintf ( mms->stddebug, "pre_header.buf[%d] = 0x%02x (%d)\n", i, pre_header.buf[i], pre_header.buf[i] ); + } + + packet_len = ( pre_header.buf[7] << 8 | pre_header.buf[6] ) - 8; + + if ( mms->stddebug != NULL ) + fprintf ( mms->stddebug, "\nASF Header Packet (%d bytes)\n", packet_len ); + + if ( mms_recv_packet ( mms->socket, &tmp, packet_len, mms->quiet ) != 0 ) + { + if ( !mms->quiet ) + error ( "mms_recv_header_packet", "unable to get header" ); + + return MMS_RET_ERROR; + } + + if ( ( header_len + packet_len ) > MMS_BUF_SIZE ) + { + if ( !mms->quiet ) + error ( "mms_recv_header_packet", "total header length is too big for me" ); + + return MMS_RET_ERROR; + } + + memcpy ( &pak->buf[header_len], tmp.buf, packet_len ); + + header_len += packet_len; + + if ( mms->stddebug != NULL ) + fprintf ( mms->stddebug, "\n" ); + + /* test if end of header is reached */ + if ( ( pak->buf[header_len-1] == 1) && + ( pak->buf[header_len-2] == 1 ) ) + return header_len; + } + else /* we might receive an ack or an error */ + { + int command = mms_recv_cmd_packet ( mms->socket, &tmp, &packet_len, 8, mms->quiet ); + + if ( command == -1 ) + { + if ( !mms->quiet ) + error ( "mms_recv_header_packet", "unable to get cmd packet" ); + + return MMS_RET_ERROR; + } + + memcpy ( tmp.buf, pre_header.buf, 8 ); + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &tmp, packet_len, MMS_SERVER ); + + if ( command == MMS_CMD_PING ) + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, tmp.buf ); + else if ( command != MMS_CMD_HEADER_DATA ) + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_recv_header_packet", "unknown command 0x%02x\n", command ); +#else + error ( "mms_recv_header_packet", "unknown command\n" ); +#endif + + return MMS_RET_ERROR; + } + } + } +} + + +/* + * Interprates the header (media packet size, etc.). + * Returns the size of a media packet (should be != 0 ). + */ +static ssize_t +mms_interp_header ( MMS *mms, + const uint8_t *header, + const ssize_t header_len ) +{ + int i; + ssize_t packet_length = 0; + + mms->expected_file_size = header_len; + + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n" ); + fprintf ( mms->stddebug, " header interpretation\n\n" ); + } + + /* parse header */ + mms->num_stream_ids = 0; + i = 30; + + while ( i < header_len ) + { + uint64_t guid_1, guid_2, length; + + if ( mms->stddebug != NULL ) + { + /* this is MS GUID format */ + fprintf ( mms->stddebug, "{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n", + header[i+3], header[i+2], header[i+1], header[i+0], + header[i+5], header[i+4], header[i+7], header[i+6], + header[i+8], header[i+9], header[i+10], header[i+11], + header[i+12], header[i+13], header[i+14], header[i+15] ); + } + + guid_2 = mms_get_64 ( header, i ); + i += 8; + + guid_1 = mms_get_64 ( header, i ); + i += 8; + + length = mms_get_64 ( header, i ); + i += 8; + + if ( ( guid_1 == 0x6553200cc000e48eULL ) && ( guid_2 == 0x11cfa9478cabdca1ULL ) ) + { + packet_length = mms_get_32 ( header, i+92-24 ); + + if ( packet_length != mms_get_32(header, i+72) ) + warning ( NULL, "size of media packets is not constant, it should not happen" ); + + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "File Properties Object (%lld bytes)\n", length ); + fprintf ( mms->stddebug, " -> broadcast bit: %d\n", mms_get_32(header,i+64)&1 ); + fprintf ( mms->stddebug, " -> min packet length: %d\n", packet_length ); + fprintf ( mms->stddebug, " -> max packet length: %d\n", mms_get_32(header, i+72) ); + fprintf ( mms->stddebug, " -> number of media packets: %d\n\n", mms_get_32(header, i+32) ); + } + + if ( mms_get_32(header,i+64)&1 ) + { + if ( ( !mms->quiet ) && ( !mms->is_live ) ) + warning ( NULL, "stream seems to be live, an error may occur" ); + + mms->is_live = MMS_LIVE; + } + } + else if ( ( guid_1 == 0x6553200cc000e68eULL ) && ( guid_2 == 0x11cfa9b7b7dc0791ULL ) ) + { + int stream_id = header[i+48] | header[i+49] << 8; + + if ( mms->num_stream_ids < 20 ) + { + mms->stream_ids[mms->num_stream_ids] = stream_id; + ++mms->num_stream_ids; + } + else + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + warning ( "mms_interp_header", "too many streams, stream \'%d\' skipped", stream_id ); +#else + warning ( "mms_interp_header", "too many streams, skipping.." ); +#endif + } + + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "Stream Object (%lld bytes)\n", length ); + fprintf ( mms->stddebug, " -> stream id: %d\n\n", stream_id ); + } + } + else if ( ( guid_1 == 0x6cce6200aa00d9a6ULL ) && ( guid_2 == 0x11cf668e75b22636ULL ) ) + { + mms->expected_file_size += ( length - 50 ); /* valid values are at least 50 bytes (why?) */ + + if ( mms->stddebug != NULL ) + fprintf ( mms->stddebug, "Data Object (%lld bytes)\n\n", length ); + } + else if ( mms->stddebug != NULL ) + { + if ( ( guid_1 == 0x6cce6200aa00d9a6ULL ) && ( guid_2 == 0x11cf668e75b22630ULL ) ) + fprintf ( mms->stddebug, "Header Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0xcb4903c9a000f489ULL ) && ( guid_2 == 0x11cfe5b133000890ULL) ) + fprintf ( mms->stddebug, "Simple Index Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0xbe4903c9a0003490ULL ) && ( guid_2 == 0x11d135dad6e229d3ULL) ) + fprintf ( mms->stddebug, "Index Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0x6553200cc000e38eULL ) && ( guid_2 == 0x11cfa92e5fbf03b5ULL) ) + fprintf ( mms->stddebug, "Header Extension Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0x6cce6200aa00d9a6ULL ) && ( guid_2 == 0x11cf668e75b22633ULL) ) + fprintf ( mms->stddebug, "Content Description Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0x50a85ec9a000f097ULL ) && ( guid_2 == 0x11d2e307d2d0a440ULL) ) + fprintf ( mms->stddebug, "Extended Content Description Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0xf64803c9a000a4a3ULL ) && ( guid_2 == 0x11d0311d86d15240ULL) ) + fprintf ( mms->stddebug, "Codec List Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0xb2a2c9976000828dULL ) && ( guid_2 == 0x11d1468d7bf875ceULL) ) + fprintf ( mms->stddebug, "Stream Bitrate Properties Object (%lld bytes)\n\n", length ); + else + fprintf ( mms->stddebug, "Unknown Object (%lld bytes)\n\n", length ); + } + + i += length-24; + } + + return packet_length; +} + + +/* + * Gets a media packet. + * Returns the amount of bytes in the media packet (0 if EOF), MMS_RET_ACKED if ACKed, + * MMS_RET_NO_AUTH if authorization failed, MMS_RET_ERROR either. + */ +static ssize_t +mms_recv_media_packet ( MMS *mms, + MMS_PACKET *pak ) +{ + MMS_PACKET pre_header; + ssize_t packet_len; + + if ( mms_recv_packet ( mms->socket, &pre_header, 8, mms->quiet ) != 0 ) + { + if ( !mms->quiet ) + error ( "mms_recv_media_packet", "unable to get pre-header" ); + + return MMS_RET_ERROR; + } + + if ( pre_header.buf[4] == 0x04 ) + { + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n" ); + fprintf ( mms->stddebug, " getting media packet from server\n\n" ); + } + + if ( mms->stddebug != NULL ) + { + static int i; /* static, so we don't recreate it for each call */ + + for ( i=0; i<8; i++ ) + fprintf ( mms->stddebug, "pre_header[%d] = 0x%02x (%d)\n", i, pre_header.buf[i], pre_header.buf[i] ); + } + + packet_len = (pre_header.buf[7] << 8 | pre_header.buf[6]) - 8; + + if ( mms->stddebug != NULL ) + fprintf ( mms->stddebug, "\nASF Media Packet (%d bytes)\n", packet_len ); + + memset ( pak->buf, 0, mms->media_packet_len ); + + if ( mms_recv_packet (mms->socket, pak, packet_len, mms->quiet) != 0 ) + { + if ( !mms->quiet ) + error ( "mms_recv_media_packet", "unable to get media packet" ); + + return MMS_RET_ERROR; + } + } + else + { + int command = mms_recv_cmd_packet ( mms->socket, pak, &packet_len, 8, mms->quiet ); + + if ( command == MMS_RET_ERROR ) + { + if ( !mms->quiet ) + error ( "mms_recv_media_packet", "unable to get cmd packet" ); + + return MMS_RET_ERROR; + } + + memcpy ( pak->buf, pre_header.buf, 8 ); + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, pak, packet_len, MMS_SERVER ); + + if ( command == MMS_CMD_PING ) + { + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, pak->buf ); + return MMS_RET_ACKED; + } + else if ( command == MMS_CMD_END_OF_STREAM ) + return 0; + else if ( command == MMS_CMD_STREAM_SELECT_ACK ) /* it happens sometimes */ + return MMS_RET_ACKED; + else if ( command == MMS_CMD_READY_TO_STREAM ) /* it always happen before the first media packet */ + { + /* this happens on some server for some unknown reason */ + if ( mms_get_32(pak->buf, 40) == 0x80070005 ) + { + if ( !mms->quiet ) + error ( "mms_recv_media_packet", "streaming denied (read manpage & retry later)" ); + + return MMS_RET_NO_AUTH; + } + + return MMS_RET_ACKED; + } + else + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_recv_media_packet", "unknown command 0x%02x\n", command ); +#else + error ( "mms_recv_media_packet", "unknown command\n" ); +#endif + + return MMS_RET_ERROR; + } + } + + if ( mms->stddebug != NULL ) + fprintf ( mms->stddebug, "\n" ); + + return packet_len; +} + + +/* + * Creates An MMS Struct + * Returns an initialized MMS Struct if successful, NULL if an error occurs. + * url must start with mms:// or mmst:// (mmsu:// and mmsh:// are OK too, but we won't use UDP or do MMS over HTTP) + * trick must be MMS_TRICK_DISABLED or MMS_TRICK_ENABLED + */ +MMS * +mms_create ( const char *url, + FILE *out, + FILE *stddebug, + const int trick, + const int quiet ) +{ + MMS *mms; + register const char *sep; + register const int host_idx = mms_check_protocol ( url ); + register const int url_len = strlen ( url ); + + if ( stddebug != NULL ) + { + fprintf ( stddebug, "\n\n********************************************************************************\n\n" ); + fprintf ( stddebug, "Url -> \'%s\'\n", url ); + } + + if ( host_idx == MMS_RET_ERROR ) + { + if ( !quiet ) + error ( "mms_create", "bad protocol (mms:// was expected)" ); + + return NULL; + } + + sep = strchr ( &url[host_idx], '/' ); + + if ( sep == NULL ) + { + if ( !quiet ) + error ( "mms_create", "url seems to be malformed" ); + + return NULL; + } + + if ( ( mms = ( MMS * ) malloc ( sizeof(MMS) ) ) == NULL ) + { + if ( !quiet ) + error ( "mms_create", "unable to allocate memory" ); + + return NULL; + } + + mms->host = malloc ( ( sep - &url[host_idx] ) + 1 ); + strncpy ( mms->host, &url[host_idx], (sep - &url[host_idx]) ); + mms->host[sep-&url[host_idx]] = '\0'; /* strndup */ + + mms->path = strdup ( sep+1 ); + mms->out = out; + mms->seq_num = 0; + mms->expected_file_size = 0; + mms->is_live = MMS_NO_LIVE; + + /* we try to guess stream type with the filename extension */ + if ( ( sep = strchr(sep,'?') ) != NULL ) + { + if ( ( *(sep-4) == '.' ) && /* this will never segfault because */ + ( *(sep-3) == 'w' ) && /* the url is at least 6 chars long (mms://) */ + ( *(sep-2) == 'm' ) && + ( *(sep-1) == 'v' ) ) + mms->stream_type = MMS_WMV; + else + mms->stream_type = MMS_ASF; + } + else + { + if ( ( url[url_len-4] == '.' ) && + ( url[url_len-3] == 'w' ) && + ( url[url_len-2] == 'm' ) && + ( url[url_len-1] == 'v' ) ) + mms->stream_type = MMS_WMV; + else + mms->stream_type = MMS_ASF; + } + + mms->stddebug = stddebug; + mms->quiet = quiet; + mms->trick = ( ((trick==MMS_TRICK_DISABLED)||(trick==MMS_TRICK_ENABLED)) ? trick : MMS_TRICK_DISABLED ); + + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "Host -> \'%s\'\nPath -> \'%s\'\n", mms->host, mms->path ); + fprintf ( mms->stddebug, "Stream type -> %s\n", (mms->stream_type==MMS_WMV)?"MMS_WMV":"MMS_ASF" ); + } + + return mms; +} + + +/* + * Establishes a connection with the MMS server. + * Returns MMS_RET_SUCCESS if success, MMS_RET_ERROR either. + */ +int +mms_connect ( MMS* mms ) +{ + struct sockaddr_in sa; + struct hostent *hp; + + if ( mms == NULL ) + return -1; + + if ( ( hp = gethostbyname(mms->host) ) == NULL ) + { + if ( !mms->quiet ) + error ( "mms_connect", "unable to resolve host name" ); + + return MMS_RET_ERROR; + } + + bcopy ( (char *) hp->h_addr, (char *) &sa.sin_addr, hp->h_length ); + sa.sin_family = hp->h_addrtype; + sa.sin_port = htons ( 1755 ); + + if ( ( mms->socket = socket(hp->h_addrtype, SOCK_STREAM, 0) ) == -1 ) + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_connect", "socket() said: %s", strerror(errno) ); +#else + error ( "mms_connect", "socket() failed" ); +#endif + + return MMS_RET_ERROR; + } + + if ( connect ( mms->socket, (struct sockaddr *) &sa, sizeof sa ) != 0 ) + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_connect", "connect() said: %s", strerror(errno) ); +#else + error ( "mms_connect", "connect failed" ); +#endif + + return MMS_RET_ERROR; + } + + return MMS_RET_SUCCESS; +} + + +/* + * Handshake with the MMS server. + * ( we don't care about server's answers ) + */ +int +mms_handshake ( MMS *mms ) +{ + MMS_PACKET pak; + int cmd = MMS_CMD_PING; + ssize_t packet_len; + char str [ 1024 ]; + uint8_t data [ 2092 ]; + + if ( mms == NULL ) + return MMS_RET_ERROR; + + /* who we are */ + memset ( data, 0, sizeof (data) ); + + if ( mms->trick == MMS_TRICK_DISABLED ) + { +#ifdef HAVE_SNPRINTF + snprintf ( str, sizeof(str), "\034\003NSPlayer/7.0.0.1956; {3300AD50-2C39-46c0-AE0A-60181587CBA}; Host: %s", mms->host ); +#else + if ( (strlen("\034\003NSPlayer/7.0.0.1956; {3300AD50-2C39-46c0-AE0A-60181587CBA}; Host: ")+strlen(mms->host)) < sizeof(str) ) + sprintf ( str, "\034\003NSPlayer/7.0.0.1956; {3300AD50-2C39-46c0-AE0A-60181587CBA}; Host: %s", mms->host ); + else + { + error ( "mms_handshake", "host name is too long" ); + return MMS_RET_ERROR; + } +#endif + } + else + strcpy ( str, "\034\003NSPlayer/4.1.0.3928; {3300AD50-2C39-46c0-AE0A-60181587CBA}" ); + + mms_string_utf16 ( data, (unsigned char *) str, strlen(str)+2 ); + mms_send_packet ( mms, MMS_CMD_HELLO, 0, 0x0004000b, strlen(str)*2 + 6, data ); + + cmd = MMS_CMD_PING; + while ( cmd == MMS_CMD_PING ) + { + cmd = mms_recv_cmd_packet ( mms->socket, &pak, &packet_len, 0, mms->quiet ); + + if ( cmd == MMS_CMD_PING ) + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, pak.buf ); + } + + if ( cmd == MMS_CMD_INVALID ) + { + if ( !mms->quiet ) + error ( "mms_handshake", "unable to get cmd packet" ); + + return MMS_RET_ERROR; + } + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &pak, packet_len, MMS_SERVER ); + + /* transport protocol selection */ + memset ( &data, 0, sizeof(data) ); + mms_string_utf16 ( &data[8], (unsigned char *) "\002\000\\\\192.168.0.129\\TCP\\1037\0000", 28 ); + mms_send_packet ( mms, MMS_CMD_PROTOCOL_SELECT, 0, 0, 28*2+8, data ); + + cmd = MMS_CMD_PING; + while ( cmd == MMS_CMD_PING ) + { + cmd = mms_recv_cmd_packet ( mms->socket, &pak, &packet_len, 0, mms->quiet ); + + if ( cmd == MMS_CMD_PING ) + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, pak.buf ); + } + + if ( cmd == MMS_CMD_INVALID ) + { + if ( !mms->quiet ) + error ( "mms_handshake", "unable to get cmd packet" ); + + return MMS_RET_ERROR; + } + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &pak, packet_len, MMS_SERVER ); + + /* requested file */ + mms_string_utf16 ( &data[8], (unsigned char *) mms->path, strlen(mms->path) ); + memset ( data, 0, 8 ); + mms_send_packet ( mms, MMS_CMD_FILE_REQUEST, 0, 0, strlen(mms->path)*2+12, data ); + + cmd = MMS_CMD_PING; + while ( cmd == MMS_CMD_PING ) + { + cmd = mms_recv_cmd_packet ( mms->socket, &pak, &packet_len, 0, mms->quiet ); + + if ( cmd == MMS_CMD_PING ) + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, pak.buf ); + } + + if ( cmd == MMS_CMD_INVALID ) + { + if ( !mms->quiet ) + error ( "mms_handshake", "unable to get cmd packet" ); + + return MMS_RET_ERROR; + } + + if ( cmd == MMS_CMD_STREAM_INFOS ) + { + if ( mms_get_32 ( pak.buf, 48 ) == -1 ) + { + if ( !mms->quiet ) + error ( "mms_handshake", "stream infos are not available" ); + + return MMS_RET_ERROR; + } + + if ( ( mms_get_32(pak.buf,72) == 0 ) || ( mms_get_32(pak.buf,72) == 0xFFFFFFFF ) ) + { + mms->is_live = MMS_LIVE; + + if ( !mms->quiet ) + warning ( NULL, "stream seems to be live, an error may occur" ); + } + } + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &pak, packet_len, MMS_SERVER ); + + return MMS_RET_SUCCESS; +} + + +/* + * Gets ASF/WMV stream/file header + */ +ssize_t +mms_write_stream_header ( MMS *mms ) +{ + MMS_PACKET pak; + ssize_t len; + + if ( mms == NULL ) + return MMS_RET_ERROR; + + /* give me the stream header please... */ + memset ( pak.buf, 0, 40 ); + pak.buf[32] = 2; + mms_send_packet ( mms, MMS_CMD_HEADER_REQUEST, 1, 0, 40, pak.buf ); + + len = mms_recv_header_packet ( mms, &pak ); + + if ( len == -1 ) + { + if ( !mms->quiet ) + error ( "mms_get_stream_header", "unable to get stream header\n" ); + + return MMS_RET_ERROR; + } + else if ( len == 0 ) + { + if ( !mms->quiet ) + error ( "mms_get_stream_header", "no stream header available, file may not exist" ); + + return MMS_RET_ERROR; + } + + fwrite ( pak.buf, len, 1, mms->out ); + + mms->media_packet_len = mms_interp_header ( mms, pak.buf, len ); + + if ( mms->media_packet_len > MMS_BUF_SIZE ) + { + if ( !mms->quiet ) + error ( "mms_get_stream_header", "media packet length is too big for me" ); + + return MMS_RET_ERROR; + } + + return len; +} + + +/* + * Tells the server we're ready to rip the stream from the beginning (don't know what happens if it's a live) + * Returns 0 if success, -1 either. + */ +int +mms_begin_rip ( MMS *mms ) +{ + int i; + uint8_t data [ 1024 ]; + MMS_PACKET pak; + ssize_t packet_len; + int cmd; + + if ( mms == NULL ) + return MMS_RET_ERROR; + + /* media stream selection */ + memset ( data, 0, 40 ); + + for ( i=1; i<mms->num_stream_ids; ++i ) + { + data [ (i-1) * 6 + 2 ] = 0xFF; + data [ (i-1) * 6 + 3 ] = 0xFF; + data [ (i-1) * 6 + 4 ] = mms->stream_ids[i]; + data [ (i-1) * 6 + 5 ] = 0x00; + } + + /* we add the final bytes of the stream selector, necessary for ASF streams only */ + if ( mms->stream_type == MMS_ASF ) + { + data [ (mms->num_stream_ids-1) * 6 + 0 ] = 0x00; + data [ (mms->num_stream_ids-1) * 6 + 1 ] = 0x00; + data [ (mms->num_stream_ids-1) * 6 + 2 ] = 0x00; + data [ (mms->num_stream_ids-1) * 6 + 3 ] = 0x20; + data [ (mms->num_stream_ids-1) * 6 + 4 ] = 0xac; + data [ (mms->num_stream_ids-1) * 6 + 5 ] = 0x40; + data [ (mms->num_stream_ids-1) * 6 + 6 ] = 0x02; + + mms_send_packet ( mms, MMS_CMD_STREAM_SELECT, mms->num_stream_ids, 0xFFFF | mms->stream_ids[0] << 16, (mms->num_stream_ids-1)*6+2+4, data ); + } + else + { + mms_send_packet ( mms, MMS_CMD_STREAM_SELECT, mms->num_stream_ids, 0xFFFF | mms->stream_ids[0] << 16, (mms->num_stream_ids-1)*6+2, data ); + } + + cmd = MMS_CMD_PING; + while ( cmd == MMS_CMD_PING ) + { + cmd = mms_recv_cmd_packet ( mms->socket, &pak, &packet_len, 0, mms->quiet ); + + if ( cmd == MMS_CMD_PING ) + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, pak.buf ); + } + + if ( cmd == MMS_CMD_INVALID ) + { + if ( !mms->quiet ) + error ( "mms_begin_rip", "unable to get server\'s confirmation" ); + + return MMS_RET_ERROR; + } + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &pak, packet_len, MMS_SERVER ); + + + /* ready... */ + memset ( data, 0, 40 ); + + for ( i=8; i<16; ++i ) + data[i] = 0xFF; + + data[20] = 0x04; + + mms_send_packet ( mms, MMS_CMD_START_PACKET, 1, 0xFFFF | mms->stream_ids[0] << 16, 24, data ); + + return MMS_RET_SUCCESS; +} + + +/* + * Writes a media packet. + * Returns the amount of bytes written, 0 if EOF, MMS_RET_NO_AUTH if streaming is denied, MMS_RET_ERROR either. + */ +ssize_t +mms_write_stream_data ( MMS *mms ) +{ + ssize_t len; + MMS_PACKET pak; + + if ( mms == NULL ) + return 0; + + do + { + len = mms_recv_media_packet ( mms, &pak ); + } + while ( len == MMS_RET_ACKED ); /* while we receive ACK packets */ + + if ( len == 0 ) + return 0; + else if ( len == -1 ) + { + if ( !mms->quiet ) + error ( "mms_write_stream_data", "mms_recv_media_packet failed" ); + + return MMS_RET_ERROR; + } + else if ( len == MMS_RET_NO_AUTH ) + { + if ( !mms->quiet ) + error ( "mms_write_stream_data", "mms_recv_media_packet failed" ); + + return MMS_RET_NO_AUTH; + } + + fwrite ( pak.buf, mms->media_packet_len, 1, mms->out ); /* we might have read less than the arbitrary media packet size */ + + return mms->media_packet_len; +} + + +/* + * Closes the connection. + */ +void +mms_disconnect ( MMS *mms ) +{ + uint8_t data [ 1024 ]; + + if ( mms == NULL ) + return; + + mms_send_packet ( mms, MMS_CMD_BYE_BYE, 0, 0, 0, data ); + + if ( ( shutdown(mms->socket, SHUT_RDWR) | close(mms->socket)) != 0 ) + if ( !mms->quiet ) + warning ( "mms_disconnect", "unable to close the socket properly" ); + + return; +} + + +/* + * Destroys An MMS Struct + */ +void +mms_destroy ( MMS *mms ) +{ + if ( mms == NULL ) + return; + + if ( mms->host != NULL ) + free ( mms->host ); + + if ( mms->path != NULL ) + free ( mms->path ); + + free ( mms ); + + return; +} |