From 30183d97b26baea7a55f0966f90c0134444c6890 Mon Sep 17 00:00:00 2001 From: Ron Pedde Date: Thu, 30 Oct 2003 22:41:56 +0000 Subject: [PATCH] Initial checkin --- src/daap-proto.c | 389 +++++++++++++++++++++++++++++++ src/daap-proto.h | 49 ++++ src/daap.c | 300 ++++++++++++++++++++++++ src/daap.h | 33 +++ src/rend-posix.c | 591 +++++++++++++++++++++++++++++++++++++++++++++++ src/rend.h | 11 + 6 files changed, 1373 insertions(+) create mode 100644 src/daap-proto.c create mode 100644 src/daap-proto.h create mode 100644 src/daap.c create mode 100644 src/daap.h create mode 100644 src/rend-posix.c create mode 100644 src/rend.h diff --git a/src/daap-proto.c b/src/daap-proto.c new file mode 100644 index 00000000..d0c8d5b5 --- /dev/null +++ b/src/daap-proto.c @@ -0,0 +1,389 @@ +/* + * $Id$ + * Helper functions for formatting a daap message + * + * Copyright (C) 2003 Ron Pedde (ron@corbey.com) + * + * 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 of the License, 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, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "daap-proto.h" +#include "err.h" +#include "restart.h" + +/* Forwards */ +DAAP_BLOCK *daap_get_new(void); +DAAP_BLOCK *daap_add_formatted(DAAP_BLOCK *parent, char *tag, + int len, char *value); +int daap_serialmem(DAAP_BLOCK *root, char *where); +int daap_compress(char *input, long in_len, char *output, long *out_len); + + +/* + * daap_get_new + * + * Initialize a new daap struct + */ +DAAP_BLOCK *daap_get_new(void) { + DAAP_BLOCK *pnew; + + pnew=(DAAP_BLOCK*)malloc(sizeof(DAAP_BLOCK)); + if(!pnew) { + DPRINTF(ERR_WARN,"Error mallocing a daap block\n"); + return NULL; + } + + pnew->value=NULL; + pnew->parent=NULL; + pnew->children=NULL; + pnew->next=NULL; + + return pnew; +} + +/* + * daap_add_formatted + * + * add a block exactly as formatted in value. + * + * Note that value WILL be freed later in daap_free, so + * the value paramater must have been malloced + */ +DAAP_BLOCK *daap_add_formatted(DAAP_BLOCK *parent, char *tag, + int size, char *value) { + DAAP_BLOCK *current,*last; + DAAP_BLOCK *pnew; + + DPRINTF(ERR_DEBUG,"Adding daap tag %s\n",tag); + pnew = daap_get_new(); + if(!pnew) + return NULL; + + pnew->reported_size=size; + pnew->parent=parent; + pnew->size=size; + memcpy(pnew->tag,tag,4); + + if((size <= 4) && (size > 0)) { /* we can just put it in svalue */ + memcpy(pnew->svalue,value,size); + pnew->free=0; + } else { + pnew->value=value; + pnew->free=1; + } + pnew->next=NULL; + + /* walk the child list and put it at the end */ + if(parent) { + current=last=parent->children; + while(current) { + last=current; + current=current->next; + } + + if(last) { /* some there already */ + last->next=pnew; + } else { + parent->children=pnew; + } + } + + /* now, walk the chain and update sizes */ + current=pnew->parent; + while(current) { + current->reported_size += (8 + pnew->reported_size); + current=current->parent; + } + + return pnew; +} + +/* + * daap_add_int + * + * Add an int block to a specific parent + */ +DAAP_BLOCK *daap_add_long(DAAP_BLOCK *parent, char *tag, int v1, int v2) { + char *ivalue; + ivalue=(char*)malloc(8); + if(!ivalue) + return NULL; + + ivalue[0]=(v1 >> 24) & 0xFF; + ivalue[1]=(v1 >> 16) & 0xFF; + ivalue[2]=(v1 >> 8) & 0xFF; + ivalue[3]=v1 & 0xFF; + + ivalue[4]=(v1 >> 24) & 0xFF; + ivalue[5]=(v1 >> 16) & 0xFF; + ivalue[6]=(v1 >> 8) & 0xFF; + ivalue[7]=v1 & 0xFF; + + return daap_add_formatted(parent,tag,8,ivalue); +} + +/* + * daap_add_int + * + * Add an int block to a specific parent + */ +DAAP_BLOCK *daap_add_int(DAAP_BLOCK *parent, char *tag, int value) { + char ivalue[4]; + + ivalue[0]=(value >> 24) & 0xFF; + ivalue[1]=(value >> 16) & 0xFF; + ivalue[2]=(value >> 8) & 0xFF; + ivalue[3]=value & 0xFF; + + return daap_add_formatted(parent,tag,4,ivalue); +} + +/* + * daap_add_short + * + * Add an int block to a specific parent + */ +DAAP_BLOCK *daap_add_short(DAAP_BLOCK *parent, char *tag, short int value) { + char ivalue[2]; + + ivalue[0]=(value >> 8) & 0xFF; + ivalue[1]=value & 0xFF; + + return daap_add_formatted(parent,tag,2,ivalue); +} + +/* + * daap_add_char + * + * Add a single char + */ +DAAP_BLOCK *daap_add_char(DAAP_BLOCK *parent, char *tag, char value) { + return daap_add_formatted(parent,tag,1,&value); +} + +/* + * daap_add_data + * + * Add unstructured data to a specific parent + */ +DAAP_BLOCK *daap_add_data(DAAP_BLOCK *parent, char *tag, + int len, void *value) { + void *pvalue; + + if(len > 4) { + pvalue=(void*)malloc(len); + if(!pvalue) + return NULL; + + memcpy(pvalue,value,len); + + return daap_add_formatted(parent,tag,len,pvalue); + } + return daap_add_formatted(parent,tag,len,value); +} + +/* + * daap_add_string + * + * Add a string element to a specific parent + */ +DAAP_BLOCK *daap_add_string(DAAP_BLOCK *parent, char *tag, char *value) { + char *newvalue; + + if(strlen(value) > 4) { + newvalue=strdup(value); + + if(!newvalue) + return NULL; + + return daap_add_formatted(parent,tag,strlen(newvalue),newvalue); + } + return daap_add_formatted(parent,tag,strlen(value),value); +} + +/* + * daap_add_empty + * + * add a tag whose only value is to act as an aggregator + */ +DAAP_BLOCK *daap_add_empty(DAAP_BLOCK *parent, char *tag) { + return daap_add_formatted(parent,tag,0,NULL); +} + +/* + * daap_serialmem + * + * Serialize a daap tree to memory, so it can be + * gzipped + */ +int daap_serialmem(DAAP_BLOCK *root, char *where) { + DAAP_BLOCK *current; + char size[4]; + + if(!root) + return 0; + + DPRINTF(ERR_DEBUG,"Serializing %c%c%c%c\n",root->tag[0],root->tag[1], + root->tag[2],root->tag[3]); + + + memcpy(where,root->tag,4); + where+=4; + + size[0] = (root->reported_size >> 24) & 0xFF; + size[1] = (root->reported_size >> 16) & 0xFF; + size[2] = (root->reported_size >> 8 ) & 0xFF; + size[3] = (root->reported_size) & 0xFF; + + memcpy(where,&size,4); + where+=4; + + if(root->size) { + if(root->free) + memcpy(where,root->value,root->size); + else + memcpy(where,root->svalue,root->size); + + where+=root->size; + } + + if(daap_serialmem(root->children,where)) + return -1; + + if(daap_serialmem(root->next,where)) + return -1; + + return 0; +} + + +/* + * daap_serialize + * + * Throw the whole daap structure out a fd (depth first), + * gzipped + * + * FIXME: this is gross. clean this up + */ +int daap_serialize(DAAP_BLOCK *root, int fd, int gzip) { + char *uncompressed; + long uncompressed_len; + char *compressed; + long compressed_len; + int err; + + uncompressed_len = root->reported_size + 8; + uncompressed=(char*)malloc(uncompressed_len); + if(!uncompressed) { + DPRINTF(ERR_INFO,"Error allocating serialization block\n"); + return -1; + } + + daap_serialmem(root,uncompressed); + + if(gzip) { + /* guarantee enough buffer space */ + compressed_len = uncompressed_len * 101/100 + 12; + compressed=(char*)malloc(compressed_len); + if(!compressed) { + DPRINTF(ERR_INFO,"Error allocation compression block\n"); + free(uncompressed); + return -1; + } + + err=daap_compress(uncompressed,uncompressed_len, + compressed, &compressed_len); + + if(err) { + DPRINTF(ERR_INFO,"Error compressing: %s\n",strerror(errno)); + free(uncompressed); + free(compressed); + return -1; + } + + if(r_write(fd,compressed,compressed_len) != compressed_len) { + DPRINTF(ERR_INFO,"Error writing compressed daap stream\n"); + free(uncompressed); + free(compressed); + return -1; + } + free(compressed); + free(uncompressed); + } else { + if(r_write(fd,uncompressed,uncompressed_len) != uncompressed_len) { + DPRINTF(ERR_INFO,"Error writing uncompressed daap stream\n"); + free(uncompressed); + return -1; + } + free(uncompressed); + } + return 0; +} + +/* + * daap_compress + * + * The zlib library is documented as threadsafe so long as + * the zalloc and zfree routines are implemented reentrantly. + * + * I have no idea what platforms this will be ported to, + * and even though I do not believe the functions I am using + * will call zalloc or zfree, I am going to put this function + * in a critical section. Someone with more knowledge of zlib + * than I can determine if it is really necessary. + * + */ +int daap_compress(char *input, long in_len, char *output, long *out_len) { + int err; + + err=compress(output,out_len,input,in_len); + switch(err) { + case Z_OK: + break; + case Z_MEM_ERROR: + errno=ENOMEM; + break; + case Z_BUF_ERROR: + errno=EINVAL; + break; + } + + return (err == Z_OK ? 0 : -1); +} + + +/* + * daap_free + * + * Free an entire daap formatted block + */ +int daap_free(DAAP_BLOCK *root) { + if((root->size)&&(root->free)) + free(root->value); + free(root->children); + free(root->next); + free(root); + return 0; +} diff --git a/src/daap-proto.h b/src/daap-proto.h new file mode 100644 index 00000000..fe34b6ef --- /dev/null +++ b/src/daap-proto.h @@ -0,0 +1,49 @@ +/* + * $Id$ + * Helper functions for formatting a daap message + * + * Copyright (C) 2003 Ron Pedde (ron@corbey.com) + * + * 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 of the License, 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, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _DAAP_PROTO_H_ +#define _DAAP_PROTO_H_ + + +typedef struct daap_block_tag { + char tag[4]; + int reported_size; + int size; + int free; + char *value; + char svalue[4]; /* for statics up to 4 bytes */ + struct daap_block_tag *parent; + struct daap_block_tag *children; + struct daap_block_tag *next; +} DAAP_BLOCK; + +DAAP_BLOCK *daap_add_int(DAAP_BLOCK *parent, char *tag, int value); +DAAP_BLOCK *daap_add_data(DAAP_BLOCK *parent, char *tag, int len, void *value); +DAAP_BLOCK *daap_add_string(DAAP_BLOCK *parent, char *tag, char *value); +DAAP_BLOCK *daap_add_empty(DAAP_BLOCK *parent, char *tag); +DAAP_BLOCK *daap_add_char(DAAP_BLOCK *parent, char *tag, char value); +DAAP_BLOCK *daap_add_short(DAAP_BLOCK *parent, char *tag, short int value); +DAAP_BLOCK *daap_add_long(DAAP_BLOCK *parent, char *tag, int v1, int v2); +int daap_serialize(DAAP_BLOCK *root, int fd, int gzip); +int daap_free(DAAP_BLOCK *root); + +#endif + diff --git a/src/daap.c b/src/daap.c new file mode 100644 index 00000000..b1bd0145 --- /dev/null +++ b/src/daap.c @@ -0,0 +1,300 @@ +/* + * $Id$ + * Build daap structs for replies + * + * Copyright (C) 2003 Ron Pedde (ron@pedde.com) + * + * 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 of the License, 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, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include "daap-proto.h" +#include "daap.h" +#include "err.h" + +typedef struct tag_daap_items { + int type; + char *tag; + char *description; +} DAAP_ITEMS; + +DAAP_ITEMS taglist[] = { + { 0x05, "miid", "dmap.itemid" }, + { 0x09, "minm", "dmap.itemname" }, + { 0x01, "mikd", "dmap.itemkind" }, + { 0x07, "mper", "dmap.persistentid" }, + { 0x0C, "mcon", "dmap.container" }, + { 0x05, "mcti", "dmap.containeritemid" }, + { 0x05, "mpco", "dmap.parentcontainerid" }, + { 0x05, "mstt", "dmap.status" }, + { 0x09, "msts", "dmap.statusstring" }, + { 0x05, "mimc", "dmap.itemcount" }, + { 0x05, "mctc", "dmap.containercount" }, + { 0x05, "mrco", "dmap.returnedcount" }, + { 0x05, "mtco", "dmap.specifiedtotalcount" }, + { 0x0C, "mlcl", "dmap.listing" }, + { 0x0C, "mlit", "dmap.listingitem" }, + { 0x0C, "mbcl", "dmap.bag" }, + { 0x0C, "mdcl", "dmap.dictionary" }, + { 0x0C, "msrv", "dmap.serverinforesponse" }, + { 0x01, "msau", "dmap.authenticationmethod" }, + { 0x01, "mslr", "dmap.loginrequired" }, + { 0x0B, "mpro", "dmap.protocolversion" }, + { 0x01, "msal", "dmap.supportsautologout" }, + { 0x01, "msup", "dmap.supportsupdate" }, + { 0x01, "mspi", "dmap.supportspersistentids" }, + { 0x01, "msex", "dmap.supportsextensions" }, + { 0x01, "msbr", "dmap.supportsbrowse" }, + { 0x01, "msqy", "dmap.supportsquery" }, + { 0x01, "msix", "dmap.supportsindex" }, + { 0x01, "msrs", "dmap.supportsresolve" }, + { 0x05, "mstm", "dmap.timeoutinterval" }, + { 0x05, "msdc", "dmap.databasescount" }, + { 0x0C, "mlog", "dmap.loginresponse" }, + { 0x05, "mlid", "dmap.sessionid" }, + { 0x0C, "mupd", "dmap.updateresponse" }, + { 0x05, "musr", "dmap.serverrevision" }, + { 0x01, "muty", "dmap.updatetype" }, + { 0x0C, "mudl", "dmap.deletedidlisting" }, + { 0x0C, "mccr", "dmap.contentcodesresponse" }, + { 0x05, "mcnm", "dmap.contentcodesnumber" }, + { 0x09, "mcna", "dmap.contentcodesname" }, + { 0x03, "mcty", "dmap.contentcodestype" }, + { 0x0B, "apro", "daap.protocolversion" }, + { 0x0C, "avdb", "daap.serverdatabases" }, + { 0x0C, "abro", "daap.databasebrowse" }, + { 0x0C, "abal", "daap.browsealbumlisting" }, + { 0x0C, "abar", "daap.browseartistlisting" }, + { 0x0C, "abcp", "daap.browsecomposerlisting" }, + { 0x0C, "abgn", "daap.browsegenrelisting" }, + { 0x0C, "adbs", "daap.databasesongs" }, + { 0x09, "asal", "daap.songalbum" }, + { 0x09, "asar", "daap.songartist" }, + { 0x03, "asbt", "daap.songbeatsperminute" }, + { 0x03, "asbr", "daap.songbitrate" }, + { 0x09, "ascm", "daap.songcomment" }, + { 0x01, "asco", "daap.songcompilation" }, + { 0x09, "ascp", "daap.songcomposer" }, + { 0x0A, "asda", "daap.songdateadded" }, + { 0x0A, "asdm", "daap.songdatemodified" }, + { 0x03, "asdc", "daap.songdisccount" }, + { 0x03, "asdn", "daap.songdiscnumber" }, + { 0x01, "asdb", "daap.songdisabled" }, + { 0x09, "aseq", "daap.songeqpreset" }, + { 0x09, "asfm", "daap.songformat" }, + { 0x09, "asgn", "daap.songgenre" }, + { 0x09, "asdt", "daap.songdescription" }, + { 0x02, "asrv", "daap.songrelativevolume" }, + { 0x05, "assr", "daap.songsamplerate" }, + { 0x05, "assz", "daap.songsize" }, + { 0x05, "asst", "daap.songstarttime" }, + { 0x05, "assp", "daap.songstoptime" }, + { 0x05, "astm", "daap.songtime" }, + { 0x03, "astc", "daap.songtrackcount" }, + { 0x03, "astn", "daap.songtracknumber" }, + { 0x01, "asur", "daap.songuserrating" }, + { 0x03, "asyr", "daap.songyear" }, + { 0x01, "asdk", "daap.songdatakind" }, + { 0x09, "asul", "daap.songdataurl" }, + { 0x0C, "aply", "daap.databaseplaylists" }, + { 0x01, "abpl", "daap.baseplaylist" }, + { 0x0C, "apso", "daap.playlistsongs" }, + { 0x0C, "arsv", "daap.resolve" }, + { 0x0C, "arif", "daap.resolveinfo" }, + { 0x05, "aeNV", "com.apple.itunes.norm-volume" }, + { 0x01, "aeSP", "com.apple.itunes.smart-playlist" }, + { 0x00, NULL, NULL } +}; + + +int daap_add_mdcl(DAAP_BLOCK *root, char *tag, char *name, short int number) { + DAAP_BLOCK *mdcl; + int g=1; + + mdcl=daap_add_empty(root,"mdcl"); + if(mdcl) { + g=(int)daap_add_string(mdcl,"mcnm",tag); + g = g && daap_add_string(mdcl,"mcna",name); + g = g && daap_add_short(mdcl,"mcty",number); + } + + return (mdcl ? g : 0); +} + +/* + * daap_response_content_codes + * + * handle the daap block for the /content-codes URI + * + * This might more easily be done by just emitting a binary + * of the content-codes from iTunes, since this really + * isn't dynamic + */ + +DAAP_BLOCK *daap_response_content_codes(void) { + DAAP_BLOCK *root; + DAAP_ITEMS *current=taglist; + int g=1; + + + root=daap_add_empty(NULL,"mccr"); + if(root) { + g = (int)daap_add_int(root,"mstt",200); + + while(current->type) { + g = g && daap_add_mdcl(root,current->tag,current->description, + current->type); + current++; + } + } + + if(!g) { + daap_free(root); + return NULL; + } + + return root; +} + + +/* + * daap_response_login + * + * handle the daap block for the /login URI + */ + +DAAP_BLOCK *daap_response_login(void) { + DAAP_BLOCK *root; + int g=1; + + + root=daap_add_empty(NULL,"mlog"); + if(root) { + g = (int)daap_add_int(root,"mstt",200); + g = g && daap_add_int(root,"mlid",7); /* static id! */ + } + + if(!g) { + daap_free(root); + return NULL; + } + + return root; +} + +/* + * daap_response_update + * + * handle the daap block for the /update URI + */ + +DAAP_BLOCK *daap_response_update(void) { + DAAP_BLOCK *root; + int g=1; + + root=daap_add_empty(NULL,"mupd"); + if(root) { + g = (int)daap_add_int(root,"mstt",200); + /* theoretically, this would go up if the db changes? */ + g = g && daap_add_int(root,"musr",3); + } + + if(!g) { + daap_free(root); + return NULL; + } + + return root; +} + +/* + * daap_response_databases + * + * handle the daap block for the /databases URI + */ + +DAAP_BLOCK *daap_response_databases(void) { + DAAP_BLOCK *root; + DAAP_BLOCK *mlcl; + DAAP_BLOCK *mlit; + int g=1; + + root=daap_add_empty(NULL,"avdb"); + if(root) { + g = (int)daap_add_int(root,"mstt",200); + g = g && daap_add_char(root,"muty",0); + g = g && daap_add_int(root,"mtco",1); + g = g && daap_add_int(root,"mrco",1); + mlcl=daap_add_empty(root,"mlcl"); + if(mlcl) { + mlit=daap_add_empty(mlcl,"mlit"); + if(mlit) { + g = g && daap_add_int(mlit,"miid",0x20); + g = g && daap_add_long(mlit,"mper",0,1); + g = g && daap_add_string(mlit,"minm","daapd music"); + g = g && daap_add_int(mlit,"mimc",0x10); + g = g && daap_add_int(mlit,"mctc",0x1); + } + } + } + + g = g && mlcl && mlit; + + if(!g) { + DPRINTF(ERR_INFO,"Memory problem. Bailing\n"); + daap_free(root); + return NULL; + } + + return root; +} + +/* + * daap_response_server_info + * + * handle the daap block for the /server-info URI + */ +DAAP_BLOCK *daap_response_server_info(void) { + DAAP_BLOCK *root; + int g=1; + + root=daap_add_empty(NULL,"msrv"); + + if(root) { + g = (int)daap_add_int(root,"mstt",200); /* result */ + g = g && daap_add_int(root,"mpro",2 << 16); /* dmap proto ? */ + g = g && daap_add_int(root,"apro",2 << 16); /* daap protocol */ + g = g && daap_add_string(root,"minm","daapd music"); /* server name */ + g = g && daap_add_char(root,"mslr",0); /* logon required */ + g = g && daap_add_int(root,"mstm",1800); /* timeout - iTunes=1800 */ + g = g && daap_add_char(root,"msal",0); /* autologout */ + g = g && daap_add_char(root,"msup",0); /* update */ + g = g && daap_add_char(root,"mspi",0); /* persistant ids */ + g = g && daap_add_char(root,"msex",0); /* extensions */ + g = g && daap_add_char(root,"msbr",0); /* browsing */ + g = g && daap_add_char(root,"msqy",0); /* queries */ + g = g && daap_add_char(root,"msix",0); /* indexing? */ + g = g && daap_add_char(root,"msrs",0); /* resolve? req. persist id */ + g = g && daap_add_int(root,"msdc",1); /* database count */ + } + + if(!g) { + daap_free(root); + return NULL; + } + + return root; +} diff --git a/src/daap.h b/src/daap.h new file mode 100644 index 00000000..d2ca8d9e --- /dev/null +++ b/src/daap.h @@ -0,0 +1,33 @@ +/* + * $Id$ + * Build daap structs for replies + * + * Copyright (C) 2003 Ron Pedde (ron@pedde.com) + * + * 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 of the License, 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, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _DAAP_H_ +#define _DAAP_H_ + +#include "daap-proto.h" + +DAAP_BLOCK *daap_response_server_info(void); +DAAP_BLOCK *daap_response_content_codes(void); +DAAP_BLOCK *daap_response_login(void); +DAAP_BLOCK *daap_response_update(void); +DAAP_BLOCK *daap_response_databases(void); + +#endif /* _DAAP_H_ */ + diff --git a/src/rend-posix.c b/src/rend-posix.c new file mode 100644 index 00000000..2643ad3d --- /dev/null +++ b/src/rend-posix.c @@ -0,0 +1,591 @@ +/* + * $Id$ + * + * Do the zeroconf/mdns/rendezvous (tm) thing. This is a hacked version + * of Apple's Responder.c from the Rendezvous (tm) POSIX implementation + */ + +/* + * Copyright (c) 2002 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * The contents of this file constitute Original Code as defined in and + * are subject to the Apple Public Source License Version 1.2 (the + * "License"). You may not use this file except in compliance with the + * License. Please obtain a copy of the License at + * http://www.apple.com/publicsource and read it before using this file. + * + * This Original Code and all software distributed under the License are + * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the + * License for the specific language governing rights and limitations + * under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +/* + File: responder.c + + Contains: Code to implement an mDNS responder on the Posix platform. + + Written by: Quinn + + Copyright: Copyright (c) 2002 by Apple Computer, Inc., All Rights Reserved. + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under Apple's + copyrights in this original Apple software (the "Apple Software"), to use, + reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions of + the Apple Software. Neither the name, trademarks, service marks or logos of + Apple Computer, Inc. may be used to endorse or promote products derived from the + Apple Software without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or implied, + are granted by Apple herein, including but not limited to any patent rights that + may be infringed by your derivative works or by other works in which the Apple + Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION + OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT + (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Change History (most recent first): + + $Log$ + Revision 1.1 2003/10/30 22:41:56 ron + Initial checkin + + Revision 1.3 2002/09/21 20:44:53 zarzycki + Added APSL info + + Revision 1.2 2002/09/19 04:20:44 cheshire + Remove high-ascii characters that confuse some systems + + Revision 1.1 2002/09/17 06:24:35 cheshire + First checkin + +*/ + +#include "mdns/mDNSClientAPI.h"// Defines the interface to the client layer above +#include "mdns/mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform + +#include +#include // For printf() +#include // For exit() etc. +#include // For strlen() etc. +#include // For select() +#include // For errno, EINTR +#include +#include + +#include "err.h" + +#pragma mark ***** Globals + +static mDNS mDNSStorage; // mDNS core uses this to store its globals +static mDNS_PlatformSupport PlatformStorage; // Stores this platform's globals + +#pragma mark ***** Signals + +static volatile mDNSBool gStopNow; + +/* modified signal handling code - rep 21 Oct 2k3 */ + +// o SIGINT causes an orderly shutdown of the program. +// o SIGQUIT causes a somewhat orderly shutdown (direct but dangerous) +// +// There are fatal race conditions in our signal handling, but there's not much +// we can do about them while remaining within the Posix space. Specifically, +// if a signal arrives after we test the globals its sets but before we call +// select, the signal will be dropped. The user will have to send the signal +// again. Unfortunately, Posix does not have a "sigselect" to atomically +// modify the signal mask and start a select. + +static void HandleSigInt(int sigraised) +// A handler for SIGINT that causes us to break out of the +// main event loop when the user types ^C. This has the +// effect of quitting the program. +{ + assert(sigraised == SIGINT); + + DPRINTF(ERR_INFO,"SIGINT\n"); + gStopNow = mDNStrue; +} + +static void HandleSigQuit(int sigraised) +// If we get a SIGQUIT the user is desperate and we +// just call mDNS_Close directly. This is definitely +// not safe (because it could reenter mDNS), but +// we presume that the user has already tried the safe +// alternatives. +{ + assert(sigraised == SIGQUIT); + + DPRINTF(ERR_INFO,"SIGQUIT\n"); + + mDNS_Close(&mDNSStorage); + exit(0); +} + +#pragma mark ***** Parameter Checking + +static mDNSBool CheckThatRichTextHostNameIsUsable(const char *richTextHostName) +// Checks that richTextHostName is a reasonable host name +// label and, if it isn't and printExplanation is true, prints +// an explanation of why not. +{ + mDNSBool result; + domainlabel richLabel; + domainlabel poorLabel; + + result = mDNStrue; + if (result && strlen(richTextHostName) > 63) { + result = mDNSfalse; + } + if (result && richTextHostName[0] == 0) { + result = mDNSfalse; + } + if (result) { + ConvertCStringToDomainLabel(richTextHostName, &richLabel); + ConvertUTF8PstringToRFC1034HostLabel(richLabel.c, &poorLabel); + if (poorLabel.c[0] == 0) { + result = mDNSfalse; + } + } + return result; +} + +static mDNSBool CheckThatServiceTypeIsUsable(const char *serviceType) +// Checks that serviceType is a reasonable service type +// label and, if it isn't and printExplanation is true, prints +// an explanation of why not. +{ + mDNSBool result; + + result = mDNStrue; + if (result && strlen(serviceType) > 63) { + result = mDNSfalse; + } + if (result && serviceType[0] == 0) { + result = mDNSfalse; + } + return result; +} + +static mDNSBool CheckThatServiceTextIsUsable(const char *serviceText, + mDNSu8 *pStringList, mDNSu16 *pStringListLen) +// Checks that serviceText is a reasonable service text record +// and, if it isn't and printExplanation is true, prints +// an explanation of why not. Also parse the text into +// the packed PString buffer denoted by pStringList and +// return the length of that buffer in *pStringListLen. +// Note that this routine assumes that the buffer is +// sizeof(RDataBody) bytes long. +{ + mDNSBool result; + size_t serviceTextLen; + + // Note that parsing a C string into a PString list always + // expands the data by one character, so the following + // compare is ">=", not ">". Here's the logic: + // + // #1 For a string with not ^A's, the PString length is one + // greater than the C string length because we add a length + // byte. + // #2 For every regular (not ^A) character you add to the C + // string, you add a regular character to the PString list. + // This does not affect the equivalence stated in #1. + // #3 For every ^A you add to the C string, you add a length + // byte to the PString list but you also eliminate the ^A, + // which again does not affect the equivalence stated in #1. + + result = mDNStrue; + serviceTextLen = strlen(serviceText); + if (result && strlen(serviceText) >= sizeof(RDataBody)) { + result = mDNSfalse; + } + + // Now break the string up into PStrings delimited by ^A. + // We know the data will fit so we can ignore buffer overrun concerns. + // However, we still have to treat runs long than 255 characters as + // an error. + + if (result) { + int lastPStringOffset; + int i; + int thisPStringLen; + + // This algorithm is a little tricky. We start by copying + // the string directly into the output buffer, shifted up by + // one byte. We then fill in the first byte with a ^A. + // We then walk backwards through the buffer and, for each + // ^A that we find, we replace it with the difference between + // its offset and the offset of the last ^A that we found + // (ie lastPStringOffset). + + memcpy(&pStringList[1], serviceText, serviceTextLen); + pStringList[0] = 1; + lastPStringOffset = serviceTextLen + 1; + for (i = serviceTextLen; i >= 0; i--) { + if ( pStringList[i] == 1 ) { + thisPStringLen = (lastPStringOffset - i - 1); + assert(thisPStringLen >= 0); + if (thisPStringLen > 255) { + result = mDNSfalse; + break; + } else { + pStringList[i] = thisPStringLen; + lastPStringOffset = i; + } + } + } + + *pStringListLen = serviceTextLen + 1; + } + + return result; +} + +static mDNSBool CheckThatPortNumberIsUsable(long portNumber) +// Checks that portNumber is a reasonable port number +// and, if it isn't and printExplanation is true, prints +// an explanation of why not. +{ + mDNSBool result; + + result = mDNStrue; + if (result && (portNumber <= 0 || portNumber > 65535)) { + result = mDNSfalse; + } + return result; +} + +#pragma mark ***** Command Line Arguments + +/* get rid of pidfile handling - rep - 21 Oct 2k3 */ +static const char kDefaultServiceType[] = "_http._tcp."; +enum { + kDefaultPortNumber = 80 +}; + +static mDNSBool gAvoidPort53 = mDNStrue; +static const char *gRichTextHostName = ""; +static const char *gServiceType = kDefaultServiceType; +static mDNSu8 gServiceText[sizeof(RDataBody)]; +static mDNSu16 gServiceTextLen = 0; +static int gPortNumber = kDefaultPortNumber; + +/* +static void ParseArguments(int argc, char **argv) +// Parses our command line arguments into the global variables +// listed above. +{ + int ch; + + // Parse command line options using getopt. + + do { + ch = getopt(argc, argv, "v:rn:x:t:p:f:dP"); + if (ch != -1) { + switch (ch) { + break; + case 'r': + gAvoidPort53 = mDNSfalse; + break; + case 'n': + gRichTextHostName = optarg; + if ( ! CheckThatRichTextHostNameIsUsable(gRichTextHostName) ) { + exit(1); + } + break; + case 't': + gServiceType = optarg; + if ( ! CheckThatServiceTypeIsUsable(gServiceType) ) { + exit(1); + } + break; + case 'x': + if ( ! CheckThatServiceTextIsUsable(optarg, gServiceText, &gServiceTextLen) ) { + exit(1); + } + break; + case 'p': + gPortNumber = atol(optarg); + if ( ! CheckThatPortNumberIsUsable(gPortNumber) ) { + exit(1); + } + break; + case '?': + default: + PrintUsage(argv); + exit(1); + break; + } + } + } while (ch != -1); + + // Check for any left over command line arguments. + + if (optind != argc) { + fprintf(stderr, "%s: Unexpected argument '%s'\n", gProgramName, argv[optind]); + exit(1); + } + + // Check for inconsistency between the arguments. + + if ( (gRichTextHostName[0] == 0) && (gServiceFile[0] == 0) ) { + fprintf(stderr, "%s: You must specify a service to register (-n) or a service file (-f).\n", gProgramName); + exit(1); + } +} +*/ + +#pragma mark ***** Registration + +typedef struct PosixService PosixService; + +struct PosixService { + ServiceRecordSet coreServ; + PosixService *next; + int serviceID; +}; + +static PosixService *gServiceList = NULL; + +static void RegistrationCallback(mDNS *const m, ServiceRecordSet *const thisRegistration, mStatus status) +// mDNS core calls this routine to tell us about the status of +// our registration. The appropriate action to take depends +// entirely on the value of status. +{ + switch (status) { + + case mStatus_NoError: + DPRINTF(ERR_DEBUG,"Callback: %##s Name Registered", + thisRegistration->RR_SRV.name.c); + // Do nothing; our name was successfully registered. We may + // get more call backs in the future. + break; + + case mStatus_NameConflict: + DPRINTF(ERR_WARN,"Callback: %##s Name Conflict", + thisRegistration->RR_SRV.name.c); + + // In the event of a conflict, this sample RegistrationCallback + // just calls mDNS_RenameAndReregisterService to automatically + // pick a new unique name for the service. For a device such as a + // printer, this may be appropriate. For a device with a user + // interface, and a screen, and a keyboard, the appropriate response + // may be to prompt the user and ask them to choose a new name for + // the service. + // + // Also, what do we do if mDNS_RenameAndReregisterService returns an + // error. Right now I have no place to send that error to. + + status = mDNS_RenameAndReregisterService(m, thisRegistration); + assert(status == mStatus_NoError); + break; + + case mStatus_MemFree: + DPRINTF(ERR_WARN,"Callback: %##s Memory Free", + thisRegistration->RR_SRV.name.c); + + // When debugging is enabled, make sure that thisRegistration + // is not on our gServiceList. + +#if defined(DEBUG) + { + PosixService *cursor; + + cursor = gServiceList; + while (cursor != NULL) { + assert(&cursor->coreServ != thisRegistration); + cursor = cursor->next; + } + } +#endif + free(thisRegistration); + break; + + default: + DPRINTF(ERR_WARN,"Callback: %##s Unknown Status %d", + thisRegistration->RR_SRV.name.c, status); + break; + } +} + +static int gServiceID = 0; + +static mStatus RegisterOneService(const char * richTextHostName, + const char * serviceType, + const mDNSu8 text[], + mDNSu16 textLen, + long portNumber) +{ + mStatus status; + PosixService * thisServ; + mDNSOpaque16 port; + domainlabel name; + domainname type; + domainname domain; + + status = mStatus_NoError; + thisServ = (PosixService *) malloc(sizeof(*thisServ)); + if (thisServ == NULL) { + status = mStatus_NoMemoryErr; + } + if (status == mStatus_NoError) { + ConvertCStringToDomainLabel(richTextHostName, &name); + ConvertCStringToDomainName(serviceType, &type); + ConvertCStringToDomainName("local.", &domain); + port.b[0] = (portNumber >> 8) & 0x0FF; + port.b[1] = (portNumber >> 0) & 0x0FF;; + status = mDNS_RegisterService(&mDNSStorage, &thisServ->coreServ, + &name, &type, &domain, + NULL, + port, + text, textLen, + RegistrationCallback, thisServ); + } + if (status == mStatus_NoError) { + thisServ->serviceID = gServiceID; + gServiceID += 1; + + thisServ->next = gServiceList; + gServiceList = thisServ; + + DPRINTF(ERR_DEBUG, + "Registered service %d, name '%s', type '%s', port %ld\n", + thisServ->serviceID, + richTextHostName, + serviceType, + portNumber); + } else { + if (thisServ != NULL) { + free(thisServ); + } + } + return status; +} + +static void DeregisterOurServices(void) +{ + PosixService *thisServ; + int thisServID; + + while (gServiceList != NULL) { + thisServ = gServiceList; + gServiceList = thisServ->next; + + thisServID = thisServ->serviceID; + + mDNS_DeregisterService(&mDNSStorage, &thisServ->coreServ); + + DPRINTF(ERR_DEBUG,"Deregistered service %d\n", + thisServ->serviceID); + } +} + +#pragma mark **** Main + +int rend_init(pid_t *pid) { + mStatus status; + mDNSBool result; + + status = mDNS_Init(&mDNSStorage, &PlatformStorage, + mDNS_Init_NoCache, mDNS_Init_ZeroCacheSize, + mDNS_Init_AdvertiseLocalAddresses, + mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext); + + if (status != mStatus_NoError) { + DPRINTF(ERR_FATAL,"mDNS Error %d\n",status); + return(-1); + } + + *pid=fork(); + if(*pid) { + return 0; + } + + DPRINTF(ERR_DEBUG,"Registering tcp service\n"); + RegisterOneService("mt-daapd","_http._tcp",NULL,0,3689); + RegisterOneService("mt-daapd","_daap._tcp",NULL,0,3689); + + signal(SIGINT, HandleSigInt); // SIGINT is what you get for a Ctrl-C + signal(SIGQUIT, HandleSigQuit); // SIGQUIT is what you get for a Ctrl-\ (indeed) + + while (!gStopNow) { + int nfds = 0; + fd_set readfds; + struct timeval timeout; + int result; + + // 1. Set up the fd_set as usual here. + // This example client has no file descriptors of its own, + // but a real application would call FD_SET to add them to the set here + FD_ZERO(&readfds); + + // 2. Set up the timeout. + // This example client has no other work it needs to be doing, + // so we set an effectively infinite timeout + timeout.tv_sec = 0x3FFFFFFF; + timeout.tv_usec = 0; + + // 3. Give the mDNSPosix layer a chance to add its information to the fd_set and timeout + mDNSPosixGetFDSet(&mDNSStorage, &nfds, &readfds, &timeout); + + // 4. Call select as normal + DPRINTF(ERR_DEBUG,"select(%d, %d.%06d)\n", nfds, + timeout.tv_sec, timeout.tv_usec); + + result = select(nfds, &readfds, NULL, NULL, &timeout); + + if (result < 0) { + DPRINTF(ERR_WARN,"select() returned %d errno %d\n", result, errno); + if (errno != EINTR) gStopNow = mDNStrue; + } else { + // 5. Call mDNSPosixProcessFDSet to let the mDNSPosix layer do its work + mDNSPosixProcessFDSet(&mDNSStorage, result, &readfds); + + // 6. This example client has no other work it needs to be doing, + // but a real client would do its work here + // ... (do work) ... + } + } + + DPRINTF(ERR_DEBUG,"Exiting"); + + DeregisterOurServices(); + mDNS_Close(&mDNSStorage); + + if (status == mStatus_NoError) { + result = 0; + } else { + result = 2; + } + DPRINTF(ERR_DEBUG, "Finished with status %ld, result %d\n", + status, result); + + return result; +} + diff --git a/src/rend.h b/src/rend.h new file mode 100644 index 00000000..1d7e173c --- /dev/null +++ b/src/rend.h @@ -0,0 +1,11 @@ +/* + * $Id$ + * + */ + +#ifndef _REND_H_ +#define _REND_H_ + +int rend_init(pid_t *pid); + +#endif /* _REND_H_ */