source: titan/minidlna-1.0.22/upnpevents.c @ 13567

Last change on this file since 13567 was 13567, checked in by obi, 12 years ago

[titan] add minidlna-1.0.22 first step

File size: 14.2 KB
Line 
1/* MiniDLNA project
2 * http://minidlna.sourceforge.net/
3 *
4 * MiniDLNA media server
5 * Copyright (C) 2008-2009  Justin Maggard
6 *
7 * This file is part of MiniDLNA.
8 *
9 * MiniDLNA is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
12 *
13 * MiniDLNA is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
20 *
21 * Portions of the code from the MiniUPnP project:
22 *
23 * Copyright (c) 2006-2007, Thomas Bernard
24 * All rights reserved.
25 *
26 * Redistribution and use in source and binary forms, with or without
27 * modification, are permitted provided that the following conditions are met:
28 *     * Redistributions of source code must retain the above copyright
29 *       notice, this list of conditions and the following disclaimer.
30 *     * Redistributions in binary form must reproduce the above copyright
31 *       notice, this list of conditions and the following disclaimer in the
32 *       documentation and/or other materials provided with the distribution.
33 *     * The name of the author may not be used to endorse or promote products
34 *       derived from this software without specific prior written permission.
35 *
36 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
37 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
38 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
39 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
40 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
41 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
42 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
43 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
44 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 * POSSIBILITY OF SUCH DAMAGE.
47 */
48#include <stdio.h>
49#include <string.h>
50#include <errno.h>
51#include <sys/queue.h>
52#include <stdlib.h>
53#include <unistd.h>
54#include <time.h>
55#include <sys/types.h>
56#include <sys/socket.h>
57#include <netinet/in.h>
58#include <arpa/inet.h>
59#include <fcntl.h>
60#include <errno.h>
61
62#include "config.h"
63#include "upnpevents.h"
64#include "minidlnapath.h"
65#include "upnpglobalvars.h"
66#include "upnpdescgen.h"
67#include "uuid.h"
68#include "log.h"
69
70/* stuctures definitions */
71struct subscriber {
72        LIST_ENTRY(subscriber) entries;
73        struct upnp_event_notify * notify;
74        time_t timeout;
75        uint32_t seq;
76        /*enum { EWanCFG = 1, EWanIPC, EL3F } service;*/
77        enum subscriber_service_enum service;
78        char uuid[42];
79        char callback[];
80};
81
82struct upnp_event_notify {
83        LIST_ENTRY(upnp_event_notify) entries;
84    int s;  /* socket */
85    enum { ECreated=1,
86               EConnecting,
87               ESending,
88               EWaitingForResponse,
89               EFinished,
90               EError } state;
91    struct subscriber * sub;
92    char * buffer;
93    int buffersize;
94        int tosend;
95    int sent;
96        const char * path;
97        char addrstr[16];
98        char portstr[8];
99};
100
101/* prototypes */
102static void
103upnp_event_create_notify(struct subscriber * sub);
104
105/* Subscriber list */
106LIST_HEAD(listhead, subscriber) subscriberlist = { NULL };
107
108/* notify list */
109LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL };
110
111/* create a new subscriber */
112static struct subscriber *
113newSubscriber(const char * eventurl, const char * callback, int callbacklen)
114{
115        struct subscriber * tmp;
116        if(!eventurl || !callback || !callbacklen)
117                return NULL;
118        tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1);
119        if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0)
120                tmp->service = EContentDirectory;
121        else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0)
122                tmp->service = EConnectionManager;
123        else if(strcmp(eventurl, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL)==0)
124                tmp->service = EMSMediaReceiverRegistrar;
125        else {
126                free(tmp);
127                return NULL;
128        }
129        memcpy(tmp->callback, callback, callbacklen);
130        tmp->callback[callbacklen] = '\0';
131        /* make a dummy uuid */
132        strncpy(tmp->uuid, uuidvalue, sizeof(tmp->uuid));
133        if( get_uuid_string(tmp->uuid+5) != 0 )
134        {
135                tmp->uuid[sizeof(tmp->uuid)-1] = '\0';
136                snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff);
137        }
138
139        return tmp;
140}
141
142/* creates a new subscriber and adds it to the subscriber list
143 * also initiate 1st notify */
144const char *
145upnpevents_addSubscriber(const char * eventurl,
146                         const char * callback, int callbacklen,
147                         int timeout)
148{
149        struct subscriber * tmp;
150        /*static char uuid[42];*/
151        /* "uuid:00000000-0000-0000-0000-000000000000"; 5+36+1=42bytes */
152        DPRINTF(E_DEBUG, L_HTTP, "addSubscriber(%s, %.*s, %d)\n",
153               eventurl, callbacklen, callback, timeout);
154        /*strncpy(uuid, uuidvalue, sizeof(uuid));
155        uuid[sizeof(uuid)-1] = '\0';*/
156        tmp = newSubscriber(eventurl, callback, callbacklen);
157        if(!tmp)
158                return NULL;
159        if(timeout)
160                tmp->timeout = time(NULL) + timeout;
161        LIST_INSERT_HEAD(&subscriberlist, tmp, entries);
162        upnp_event_create_notify(tmp);
163        return tmp->uuid;
164}
165
166/* renew a subscription (update the timeout) */
167int
168renewSubscription(const char * sid, int sidlen, int timeout)
169{
170        struct subscriber * sub;
171        for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
172                if(memcmp(sid, sub->uuid, 41) == 0) {
173                        sub->timeout = (timeout ? time(NULL) + timeout : 0);
174                        return 0;
175                }
176        }
177        return -1;
178}
179
180int
181upnpevents_removeSubscriber(const char * sid, int sidlen)
182{
183        struct subscriber * sub;
184        if(!sid)
185                return -1;
186        DPRINTF(E_DEBUG, L_HTTP, "removeSubscriber(%.*s)\n",
187               sidlen, sid);
188        for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
189                if(memcmp(sid, sub->uuid, 41) == 0) {
190                        if(sub->notify) {
191                                sub->notify->sub = NULL;
192                        }
193                        LIST_REMOVE(sub, entries);
194                        free(sub);
195                        return 0;
196                }
197        }
198        return -1;
199}
200
201/* notifies all subscriber of a number of port mapping change
202 * or external ip address change */
203void
204upnp_event_var_change_notify(enum subscriber_service_enum service)
205{
206        struct subscriber * sub;
207        for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
208                if(sub->service == service && sub->notify == NULL)
209                        upnp_event_create_notify(sub);
210        }
211}
212
213/* create and add the notify object to the list */
214static void
215upnp_event_create_notify(struct subscriber * sub)
216{
217        struct upnp_event_notify * obj;
218        int flags;
219        obj = calloc(1, sizeof(struct upnp_event_notify));
220        if(!obj) {
221                DPRINTF(E_ERROR, L_HTTP, "%s: calloc(): %s\n", "upnp_event_create_notify", strerror(errno));
222                return;
223        }
224        obj->sub = sub;
225        obj->state = ECreated;
226        obj->s = socket(PF_INET, SOCK_STREAM, 0);
227        if(obj->s<0) {
228                DPRINTF(E_ERROR, L_HTTP, "%s: socket(): %s\n", "upnp_event_create_notify", strerror(errno));
229                goto error;
230        }
231        if((flags = fcntl(obj->s, F_GETFL, 0)) < 0) {
232                DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_GETFL..): %s\n",
233                       "upnp_event_create_notify", strerror(errno));
234                goto error;
235        }
236        if(fcntl(obj->s, F_SETFL, flags | O_NONBLOCK) < 0) {
237                DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_SETFL..): %s\n",
238                       "upnp_event_create_notify", strerror(errno));
239                goto error;
240        }
241        if(sub)
242                sub->notify = obj;
243        LIST_INSERT_HEAD(&notifylist, obj, entries);
244        return;
245error:
246        if(obj->s >= 0)
247                close(obj->s);
248        free(obj);
249}
250
251static void
252upnp_event_notify_connect(struct upnp_event_notify * obj)
253{
254        int i;
255        const char * p;
256        unsigned short port;
257        struct sockaddr_in addr;
258        if(!obj)
259                return;
260        memset(&addr, 0, sizeof(addr));
261        i = 0;
262        if(obj->sub == NULL) {
263                obj->state = EError;
264                return;
265        }
266        p = obj->sub->callback;
267        p += 7; /* http:// */
268        while(*p != '/' && *p != ':')
269                obj->addrstr[i++] = *(p++);
270        obj->addrstr[i] = '\0';
271        if(*p == ':') {
272                obj->portstr[0] = *p;
273                i = 1;
274                p++;
275                port = (unsigned short)atoi(p);
276                while(*p != '/' && *p != '\0') {
277                        if(i<7) obj->portstr[i++] = *p;
278                        p++;
279                }
280                obj->portstr[i] = 0;
281        } else {
282                port = 80;
283                obj->portstr[0] = '\0';
284        }
285        if( *p )
286                obj->path = p;
287        else
288                obj->path = "/";
289        addr.sin_family = AF_INET;
290        inet_aton(obj->addrstr, &addr.sin_addr);
291        addr.sin_port = htons(port);
292        DPRINTF(E_DEBUG, L_HTTP, "%s: '%s' %hu '%s'\n", "upnp_event_notify_connect",
293               obj->addrstr, port, obj->path);
294        obj->state = EConnecting;
295        if(connect(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
296                if(errno != EINPROGRESS && errno != EWOULDBLOCK) {
297                        DPRINTF(E_ERROR, L_HTTP, "%s: connect(): %s\n", "upnp_event_notify_connect", strerror(errno));
298                        obj->state = EError;
299                }
300        }
301}
302
303static void upnp_event_prepare(struct upnp_event_notify * obj)
304{
305        static const char notifymsg[] =
306                "NOTIFY %s HTTP/1.1\r\n"
307                "Host: %s%s\r\n"
308                "Content-Type: text/xml; charset=\"utf-8\"\r\n"
309                "Content-Length: %d\r\n"
310                "NT: upnp:event\r\n"
311                "NTS: upnp:propchange\r\n"
312                "SID: %s\r\n"
313                "SEQ: %u\r\n"
314                "Connection: close\r\n"
315                "Cache-Control: no-cache\r\n"
316                "\r\n"
317                "%.*s\r\n";
318        char * xml;
319        int l;
320        if(obj->sub == NULL) {
321                obj->state = EError;
322                return;
323        }
324        switch(obj->sub->service) {
325        case EContentDirectory:
326                xml = getVarsContentDirectory(&l);
327                break;
328        case EConnectionManager:
329                xml = getVarsConnectionManager(&l);
330                break;
331        case EMSMediaReceiverRegistrar:
332                xml = getVarsX_MS_MediaReceiverRegistrar(&l);
333                break;
334        default:
335                xml = NULL;
336                l = 0;
337        }
338        obj->tosend = asprintf(&(obj->buffer), notifymsg,
339                               obj->path, obj->addrstr, obj->portstr, l+2,
340                               obj->sub->uuid, obj->sub->seq,
341                               l, xml);
342        obj->buffersize = obj->tosend;
343        if(xml) {
344                free(xml);
345                xml = NULL;
346        }
347        DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event response:\n%s\n", obj->buffer);
348        obj->state = ESending;
349}
350
351static void upnp_event_send(struct upnp_event_notify * obj)
352{
353        int i;
354        //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event:\n%s", obj->buffer+obj->sent);
355        i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0);
356        if(i<0) {
357                DPRINTF(E_WARN, L_HTTP, "%s: send(): %s\n", "upnp_event_send", strerror(errno));
358                obj->state = EError;
359                return;
360        }
361        else if(i != (obj->tosend - obj->sent))
362                DPRINTF(E_WARN, L_HTTP, "%s: %d bytes send out of %d\n",
363                       "upnp_event_send", i, obj->tosend - obj->sent);
364        obj->sent += i;
365        if(obj->sent == obj->tosend)
366                obj->state = EWaitingForResponse;
367}
368
369static void upnp_event_recv(struct upnp_event_notify * obj)
370{
371        int n;
372        n = recv(obj->s, obj->buffer, obj->buffersize, 0);
373        if(n<0) {
374                DPRINTF(E_ERROR, L_HTTP, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno));
375                obj->state = EError;
376                return;
377        }
378        DPRINTF(E_DEBUG, L_HTTP, "%s: (%dbytes) %.*s\n", "upnp_event_recv",
379               n, n, obj->buffer);
380        obj->state = EFinished;
381        if(obj->sub)
382                obj->sub->seq++;
383}
384
385static void
386upnp_event_process_notify(struct upnp_event_notify * obj)
387{
388        switch(obj->state) {
389        case EConnecting:
390                /* now connected or failed to connect */
391                upnp_event_prepare(obj);
392                upnp_event_send(obj);
393                break;
394        case ESending:
395                upnp_event_send(obj);
396                break;
397        case EWaitingForResponse:
398                upnp_event_recv(obj);
399                break;
400        case EFinished:
401                close(obj->s);
402                obj->s = -1;
403                break;
404        default:
405                DPRINTF(E_ERROR, L_HTTP, "upnp_event_process_notify: unknown state\n");
406        }
407}
408
409void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd)
410{
411        struct upnp_event_notify * obj;
412        for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
413                DPRINTF(E_DEBUG, L_HTTP, "upnpevents_selectfds: %p %d %d\n",
414                       obj, obj->state, obj->s);
415                if(obj->s >= 0) {
416                        switch(obj->state) {
417                        case ECreated:
418                                upnp_event_notify_connect(obj);
419                                if(obj->state != EConnecting)
420                                        break;
421                        case EConnecting:
422                        case ESending:
423                                FD_SET(obj->s, writeset);
424                                if(obj->s > *max_fd)
425                                        *max_fd = obj->s;
426                                break;
427                        case EFinished:
428                        case EError:
429                        case EWaitingForResponse:
430                                FD_SET(obj->s, readset);
431                                if(obj->s > *max_fd)
432                                        *max_fd = obj->s;
433                                break;
434                        }
435                }
436        }
437}
438
439void upnpevents_processfds(fd_set *readset, fd_set *writeset)
440{
441        struct upnp_event_notify * obj;
442        struct upnp_event_notify * next;
443        struct subscriber * sub;
444        struct subscriber * subnext;
445        time_t curtime;
446        for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
447                DPRINTF(E_DEBUG, L_HTTP, "%s: %p %d %d %d %d\n",
448                       "upnpevents_processfds", obj, obj->state, obj->s,
449                       FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset));
450                if(obj->s >= 0) {
451                        if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset))
452                                upnp_event_process_notify(obj);
453                }
454        }
455        obj = notifylist.lh_first;
456        while(obj != NULL) {
457                next = obj->entries.le_next;
458                if(obj->state == EError || obj->state == EFinished) {
459                        if(obj->s >= 0) {
460                                close(obj->s);
461                        }
462                        if(obj->sub)
463                                obj->sub->notify = NULL;
464#if 0 /* Just let it time out instead of explicitly removing the subscriber */
465                        /* remove also the subscriber from the list if there was an error */
466                        if(obj->state == EError && obj->sub) {
467                                LIST_REMOVE(obj->sub, entries);
468                                free(obj->sub);
469                        }
470#endif
471                        if(obj->buffer) {
472                                free(obj->buffer);
473                        }
474                        LIST_REMOVE(obj, entries);
475                        free(obj);
476                }
477                obj = next;
478        }
479        /* remove timeouted subscribers */
480        curtime = time(NULL);
481        for(sub = subscriberlist.lh_first; sub != NULL; ) {
482                subnext = sub->entries.le_next;
483                if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) {
484                        LIST_REMOVE(sub, entries);
485                        free(sub);
486                }
487                sub = subnext;
488        }
489}
490
491#ifdef USE_MINIDLNACTL
492void write_events_details(int s) {
493        int n;
494        char buff[80];
495        struct upnp_event_notify * obj;
496        struct subscriber * sub;
497        write(s, "Events details\n", 15);
498        for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
499                n = snprintf(buff, sizeof(buff), " %p sub=%p state=%d s=%d\n",
500                             obj, obj->sub, obj->state, obj->s);
501                write(s, buff, n);
502        }
503        write(s, "Subscribers :\n", 14);
504        for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
505                n = snprintf(buff, sizeof(buff), " %p timeout=%d seq=%u service=%d\n",
506                             sub, sub->timeout, sub->seq, sub->service);
507                write(s, buff, n);
508                n = snprintf(buff, sizeof(buff), "   notify=%p %s\n",
509                             sub->notify, sub->uuid);
510                write(s, buff, n);
511                n = snprintf(buff, sizeof(buff), "   %s\n",
512                             sub->callback);
513                write(s, buff, n);
514        }
515}
516#endif
Note: See TracBrowser for help on using the repository browser.