diff --git a/src/main.c b/src/main.c index 95fe4f92..2d37a4d3 100644 --- a/src/main.c +++ b/src/main.c @@ -423,10 +423,6 @@ int main(int argc, char *argv[]) { VERSION,err_getlevel()); -#ifdef UPNP - upnp_init(); -#endif - /* load plugins before we drop privs? Maybe... let the * plugins do stuff they might need to */ plugin_init(); @@ -476,6 +472,10 @@ int main(int argc, char *argv[]) { free(runas); +#ifdef UPNP + upnp_init(); +#endif + /* this will require that the db be readable by the runas user */ db_type = conf_alloc_string("general","db_type","sqlite"); db_parms = conf_alloc_string("general","db_parms","/var/cache/mt-daapd"); @@ -497,7 +497,7 @@ int main(int argc, char *argv[]) { /* Initialize the database before starting */ DPRINTF(E_LOG,L_MAIN|L_DB,"Initializing database\n"); - if(db_init(&reload)) { + if(db_init(reload)) { DPRINTF(E_FATAL,L_MAIN|L_DB,"Error in db_init: %s\n",strerror(errno)); } @@ -641,6 +641,10 @@ int main(int argc, char *argv[]) { } #endif +#ifdef UPNP + upnp_deinit(); +#endif + /* Got to find a cleaner way to stop the web server. * Closing the fd of the socking accepting doesn't necessarily diff --git a/src/upnp.c b/src/upnp.c index c9271acd..de0b890f 100755 --- a/src/upnp.c +++ b/src/upnp.c @@ -1,4 +1,4 @@ -/* +/** * $Id: $ * * Implementation of functions for UPnP discovery @@ -20,10 +20,12 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include #include #include #include #include +#include #include #include #include @@ -41,47 +43,80 @@ #define UPNP_ADDR "239.255.255.250" #define UPNP_PORT 1900 +#define UPNP_TYPE_ALIVE 1 +#define UPNP_TYPE_BYEBYE 2 +#define UPNP_TYPE_RESPONSE 3 + +#define UPNP_SELECT_TIMEOUT 1 /* update loop for discover replies */ + +typedef struct upnp_adinfo_t { + int type; /** one of the UPNP_AD_ values */ + int version; /** numeric version. 0 if unversioned */ + char *location; /** url path to descriptor. e.g. /upnp-device.xml */ + char *namespace; /** usually "upnp" or "urn:schemas-upnp-org" */ + char *name; /** MediaServer, ConnectionManager, rootdevice, etc */ + char *body; /** body of a alive packet (postinfo) */ +} UPNP_ADINFO; + typedef struct upnp_packetinfo_t { char *group_id; - char *base_packet; - char *location; - char *usn; - char *nt; - char *body; + UPNP_ADINFO *padinfo; struct upnp_packetinfo_t *next; } UPNP_PACKETINFO; +typedef struct upnp_disco_t { + int seconds_remaining; + char *query; + struct sockaddr to; + struct upnp_disco_t *next; +} UPNP_DISCO; + /* Globals */ UPNP_PACKETINFO upnp_packetlist; OS_SOCKETTYPE upnp_socket; +pthread_t upnp_listener_tid; +int upnp_quitflag = 0; +int upnp_thread_started = 0; /* Forwards */ int upnp_strcat(char *what, char *where, int bytes_left); -void upnp_build_packet(char *packet, int len, UPNP_PACKETINFO *pi); -void upnp_broadcast(void); +void upnp_build_packet(char *packet, int len, int type, UPNP_PACKETINFO *pi, + UPNP_DISCO *pdisco); +void upnp_broadcast(int type, UPNP_DISCO *pdisco); +void *upnp_listener(void *arg); +void upnp_process_packet(void); /** * add a upnp packet too the root list. */ -void upnp_add_packet(char *group_id, char *location, - char *usn, char *nt, char *body) { +void upnp_add_packet(char *group_id, int type, char *location, + char *namespace, char *name, int version, + char *body) { UPNP_PACKETINFO *pnew; + UPNP_ADINFO *pnewinfo; pnew = (UPNP_PACKETINFO *)malloc(sizeof(UPNP_PACKETINFO)); if(!pnew) DPRINTF(E_FATAL,L_MISC,"Malloc error\n"); + pnewinfo = (UPNP_ADINFO *)malloc(sizeof(UPNP_ADINFO)); + if(!pnewinfo) + DPRINTF(E_FATAL,L_MISC,"Malloc error\n"); + memset(pnew,0,sizeof(UPNP_PACKETINFO)); + memset(pnewinfo,0,sizeof(UPNP_ADINFO)); + if(group_id) pnew->group_id = strdup(group_id); - if(location) - pnew->location = strdup(location); - if(usn) - pnew->usn = strdup(usn); - if(nt) - pnew->nt = strdup(nt); - if(body) - pnew->body = strdup(body); + + pnewinfo->type = type; + pnewinfo->version = version; + if(location) pnewinfo->location = strdup(location); + if(namespace) pnewinfo->namespace = strdup(namespace); + if(name) pnewinfo->name = strdup(name); + if(body) pnewinfo->body = strdup(body); + + pnew->padinfo = pnewinfo; util_mutex_lock(l_upnp); pnew->next = upnp_packetlist.next; @@ -102,7 +137,8 @@ int upnp_strcat(char *what, char *where, int bytes_left) { } -void upnp_build_packet(char *packet, int len, UPNP_PACKETINFO *pi) { +void upnp_build_packet(char *packet, int len, int type, + UPNP_PACKETINFO *pi, UPNP_DISCO *pdisco) { char buffer[256]; char hostname[256]; *packet = '\0'; @@ -110,41 +146,81 @@ void upnp_build_packet(char *packet, int len, UPNP_PACKETINFO *pi) { port = conf_get_int("general","port",0); - len = upnp_strcat("NOTIFY * HTTP/1.1\r\n",packet,len); - if(pi->location) { + if(type == UPNP_TYPE_RESPONSE) { + len = upnp_strcat("HTTP/1.1 200 OK\r\n",packet,len); + } else { + len = upnp_strcat("NOTIFY * HTTP/1.1\r\n",packet,len); + } + + if(pi->padinfo->location) { gethostname(hostname,sizeof(hostname)); snprintf(buffer,sizeof(buffer),"LOCATION: http://%s:%d%s\r\n", - hostname,port,pi->location); + hostname,port,pi->padinfo->location); len=upnp_strcat(buffer,packet,len); } - len=upnp_strcat("HOST: 239.255.255.250:1900\r\n",packet,len); + + if(type != UPNP_TYPE_RESPONSE) + len=upnp_strcat("HOST: 239.255.255.250:1900\r\n",packet,len); + len=upnp_strcat("SERVER: POSIX, UPnP/1.0, Firefly/" VERSION "\r\n", packet,len); - len=upnp_strcat("NTS: ssdp:alive\r\n",packet,len); - /* USN */ - len=upnp_strcat("USN:uuid:",packet,len); - len=upnp_strcat(UPNP_UUID,packet,len); - if(pi->usn) { - snprintf(buffer,sizeof(buffer),"::%s",pi->usn); - len=upnp_strcat(buffer,packet,len); + if(type == UPNP_TYPE_ALIVE) { + len=upnp_strcat("NTS: ssdp:alive\r\n",packet,len); + } else if(type == UPNP_TYPE_BYEBYE) { + len=upnp_strcat("NTS: ssdp:byebye\r\n",packet,len); + } else if ((type == UPNP_TYPE_RESPONSE) && (pdisco)) { + /* we need the original ST */ + snprintf(buffer,sizeof(buffer),"ST: %s\r\n",pdisco->query); + len = upnp_strcat(buffer,packet,len); + } + + /* USN & NT */ + switch(pi->padinfo->type) { + case UPNP_AD_BARE: + snprintf(buffer,sizeof(buffer),"USN: uuid:%s\r\n",UPNP_UUID); + len=upnp_strcat(buffer,packet,len); + if(type != UPNP_TYPE_RESPONSE) { /* no NT for responses */ + snprintf(buffer,sizeof(buffer),"NT: uuid:%s\r\n",UPNP_UUID); + len=upnp_strcat(buffer,packet,len); + } + break; + + case UPNP_AD_DEVICE: + case UPNP_AD_SERVICE: + case UPNP_AD_ROOT: + snprintf(buffer,sizeof(buffer),"USN: uuid:%s::%s:%s",UPNP_UUID, + pi->padinfo->namespace, pi->padinfo->name); + len=upnp_strcat(buffer,packet,len); + if(pi->padinfo->version) { + snprintf(buffer,sizeof(buffer),"%d",pi->padinfo->version); + len=upnp_strcat(buffer,packet,len); + } + len=upnp_strcat("\r\n",packet,len); + + if(type != UPNP_TYPE_RESPONSE) { /* no NT for responses */ + snprintf(buffer,sizeof(buffer),"NT: %s:%s", + pi->padinfo->namespace, pi->padinfo->name); + len=upnp_strcat(buffer,packet,len); + if(pi->padinfo->version) { + snprintf(buffer,sizeof(buffer),"%d",pi->padinfo->version); + len=upnp_strcat(buffer,packet,len); + } + len=upnp_strcat("\r\n",packet,len); + } + break; + default: + break; } - len=upnp_strcat("\r\n",packet,len); len=upnp_strcat("CACHE-CONTROL: max-age=1800\r\n",packet,len); - if(pi->nt) { - snprintf(buffer,sizeof(buffer),"NT:%s\r\n",pi->nt); - } else { - snprintf(buffer,sizeof(buffer),"NT:uuid:%s\r\n", UPNP_UUID); - } - len=upnp_strcat(buffer,packet,len); - if(pi->body) { + if((pi->padinfo->body) && (type != UPNP_TYPE_RESPONSE)) { snprintf(buffer,sizeof(buffer),"Content-Length: %d\r\n\r\n", - (int)strlen(pi->body)); + (int)strlen(pi->padinfo->body)); len=upnp_strcat(buffer,packet,len); - len=upnp_strcat(pi->body,packet,len); + len=upnp_strcat(pi->padinfo->body,packet,len); } else { len=upnp_strcat("Content-Length: 0\r\n\r\n",packet,len); } @@ -153,7 +229,7 @@ void upnp_build_packet(char *packet, int len, UPNP_PACKETINFO *pi) { /** * broadcast out the upnp packets */ -void upnp_broadcast(void) { +void upnp_broadcast(int type, UPNP_DISCO *pdisco) { UPNP_PACKETINFO *pi; struct sockaddr_in sin; char packet[UPNP_MAX_PACKET]; @@ -166,10 +242,12 @@ void upnp_broadcast(void) { util_mutex_lock(l_upnp); + DPRINTF(E_DBG,L_MISC,"Sending upnp broadcast of type: %d\n",type); + for(pass=0; pass < 2; pass++) { pi=upnp_packetlist.next; while(pi) { - upnp_build_packet(packet, UPNP_MAX_PACKET, pi); + upnp_build_packet(packet, UPNP_MAX_PACKET, type, pi, pdisco); sendto(upnp_socket,packet,strlen(packet),0, (struct sockaddr *)&sin, sizeof(sin)); pi = pi->next; @@ -184,7 +262,7 @@ int upnp_tick(void) { static time_t last_broadcast = 0; if((time(NULL) - last_broadcast) > 60) { - upnp_broadcast(); + upnp_broadcast(UPNP_TYPE_ALIVE,NULL); last_broadcast = time(NULL); } @@ -200,24 +278,23 @@ int upnp_init(void) { int ttl = 3; int reuse=1; int result; + int err; struct sockaddr_in addr; struct ip_mreq mreq; + srand((unsigned)time(NULL)); + memset(&upnp_packetlist,0,sizeof(upnp_packetlist)); - upnp_add_packet("base","/upnp-basic.xml",NULL,NULL,NULL); - upnp_add_packet("base","/upnp-basic.xml", - "urn:schemas-upnp-org:device:MediaServer:1", - "urn:schemas-upnp-org:device:MediaServer:1",NULL); - upnp_add_packet("base","/upnp-basic.xml", - "urn:schemas-upnp-org:service:AVTransport:1", - "urn:schemas-upnp-org:service:AVTransport:1",NULL); - upnp_add_packet("base","/upnp-basic.xml", - "urn:schemas-upnp-org:service:ContentDirectory:1", - "urn:schemas-upnp-org:service:ContentDirectory:1",NULL); - upnp_add_packet("basic","/upnp-basic.xml", - "urn:schemas-upnp-org:service:ConnectionManager:1", - "urn:schemas-upnp-org:service:ConnectionManager:1",NULL); - upnp_add_packet("base","/upnp-basic.xml","upnp:rootdevice","upnp:rootdevice",NULL); + upnp_add_packet("base,", UPNP_AD_BARE, "/upnp-basic.xml", + NULL, NULL, 0, NULL); + upnp_add_packet("base,", UPNP_AD_DEVICE, "/upnp-basic.xml", + "urn:schemas-upnp-org","MediaServer", 1, NULL); + upnp_add_packet("base,", UPNP_AD_SERVICE, "/upnp-basic.xml", + "urn:schemas-upnp-org","ContentDirectory", 1, NULL); + upnp_add_packet("base,", UPNP_AD_SERVICE, "/upnp-basic.xml", + "urn:schemas-upnp-org","ConnectionManager", 1, NULL); + upnp_add_packet("base,", UPNP_AD_ROOT, "/upnp-basic.xml", + "upnp","rootdevice",0,NULL); upnp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); result = setsockopt(upnp_socket, IPPROTO_IP, IP_MULTICAST_TTL, @@ -256,15 +333,181 @@ int upnp_init(void) { return FALSE; } + /* got the socket, let's start up the listener thread */ + DPRINTF(E_DBG,L_MISC,"Starting UPnP listener thread\n"); + if((err=pthread_create(&upnp_listener_tid,NULL,upnp_listener,NULL))) { + DPRINTF(E_LOG,L_MISC,"Warning: could not start upnp listener: %s\n", + strerror(err)); + upnp_thread_started=0; + return TRUE; + } + return TRUE; } +/** + * listener thread for upnp query requests + */ +void *upnp_listener(void *arg) { + int result; + fd_set readset; + struct timeval timeout; + + upnp_thread_started = 1; + DPRINTF(E_DBG,L_MISC,"upnp listener thread started\n"); + + /* sit in a tight select loop until upnp_deinit sets the + * quitflag */ + while(!upnp_quitflag) { + FD_ZERO(&readset); + FD_SET(upnp_socket,&readset); /* win32? */ + + /* this is kind of lame, but will defend from an OS + * that doesn't return a select error when an underlying + * file handle is closed */ + timeout.tv_sec=UPNP_SELECT_TIMEOUT; + timeout.tv_usec=0; + + while((result=select(upnp_socket+1,&readset,NULL,NULL,&timeout)==-1) && + (errno = EINTR)) { + } + + if(result == -1) { + /* this is a real error */ + DPRINTF(E_LOG,L_MISC,"Error in select in upnp listener: %s\n", + strerror(errno)); + upnp_thread_started=0; + pthread_exit(NULL); + } + upnp_process_packet(); + } + + upnp_thread_started=0; + DPRINTF(E_DBG,L_MISC,"upnp listener exiting\n"); + pthread_exit(NULL); +} + +/** + * process a upnp packet. This is either a discovery or an advertisement + * from another device on the local netowrk. + */ +void upnp_process_packet(void) { + char upnp_packet[UPNP_MAX_PACKET]; + ssize_t bytes_read; + char *line_start, *line_end; + int discovery = 0; + int mx = 0; + char *query=NULL; + struct sockaddr_in from; + socklen_t from_len; + UPNP_DISCO *pdisco; + + from_len = sizeof(from); + bytes_read = recvfrom(upnp_socket,&upnp_packet,sizeof(upnp_packet),0, + (struct sockaddr *)&from, &from_len); + + /* now, parse the packet. If it's a upnp query, it's a + * packet with a ssdp of "discover". + */ + + line_start = line_end = upnp_packet; + while((line_end - upnp_packet) < bytes_read) { + while(((*line_start=='\n')||(*line_start=='\r')) && + (line_end - upnp_packet < bytes_read)) { + /* absorb empty cr/lf pairs */ + line_start++; + line_end++; + } + + while((*line_end != '\n') && (*line_end != '\r') && + (line_end - upnp_packet < bytes_read)) + line_end++; + + if((*line_end == '\n') || (*line_end == '\r')) { + /* we have a full line */ + *line_end = '\0'; + line_end++; + + /* okay... see what it is */ + if(strncasecmp(line_start,"M-SEARCH",8) == 0) { + DPRINTF(E_DBG,L_MISC,"upnp_packet: M-SEARCH from %s\n", + inet_ntoa(from.sin_addr)); + + discovery=1; + } + + if(strncasecmp(line_start,"ST:",3)==0) { + /* There is a search term specified */ + if(query) + free(query); + + line_start += 3; + while(*line_start && *line_start == ' ') + line_start++; + + query = strdup(line_start); + DPRINTF(E_DBG,L_MISC,"upnp_packet: Query: %s\n",query); + } + if(strncasecmp(line_start,"MX:",3)==0) { + mx = atoi(line_start + 3); + } + } + + line_start=line_end; + /* we could process the packet, and advertise, if necessary */ + + } + + if(discovery) { + DPRINTF(E_DBG,L_MISC,"Responding to query request...\n"); + + /* do we care? Let's check the query and see if it is interesting */ + discovery = 0; + + + pdisco = (UPNP_DISCO *)malloc(sizeof(UPNP_DISCO)); + if(!pdisco) + DPRINTF(E_FATAL,L_MISC,"malloc error"); + + memset(pdisco,0,sizeof(UPNP_DISCO)); + if(mx) { + pdisco->seconds_remaining = 2; + } + + pdisco->query = query; + query = NULL; + memcpy((void*)&pdisco->to, (void*)&from, sizeof(struct sockaddr)); + } + + if(query) { + free(query); + query = NULL; + } +} + + + /** * turn off any upnp services. Should really de-register * all registered devices, etc. */ int upnp_deinit(void) { - close(upnp_socket); + void *result_ptr; + + upnp_quitflag=1; + upnp_broadcast(UPNP_TYPE_BYEBYE,NULL); + + if(upnp_socket) + close(upnp_socket); + + /* socket is dead, quitflag is set, listener should terminate + * reasonably quickly */ + + if(upnp_thread_started) { + upnp_thread_started=0; + pthread_join(upnp_listener_tid, &result_ptr); + } + return TRUE; } diff --git a/src/upnp.h b/src/upnp.h index f6f8f6a5..0bcb4fce 100755 --- a/src/upnp.h +++ b/src/upnp.h @@ -33,10 +33,16 @@ #define UPNP_UUID "12345678-1234-1234-1234-123456789013" +#define UPNP_AD_DEVICE 1 +#define UPNP_AD_SERVICE 2 +#define UPNP_AD_ROOT 3 +#define UPNP_AD_BARE 4 + extern int upnp_init(void); extern int upnp_tick(void); extern int upnp_deinit(void); -extern void upnp_add_packet(char *group_id, char *location, - char *usn, char *nt, char *body); +extern void upnp_add_packet(char *group_id, int type, char *location, + char *namespace, char *name, int version, + char *body); #endif /* _UPNP_H_ */