diff --git a/src/ssdp.h b/src/ssdp.h index a53c8aa..9dff17a 100644 --- a/src/ssdp.h +++ b/src/ssdp.h @@ -63,6 +63,10 @@ # define LOCATION_PORT 8080 #endif #define LOCATION_DESC "/description.xml" +#define LOCATION_ICON "/icon.png" + +#define MIME_XML "text/xml" +#define MIME_PNG "image/png" #define SSDP_ST_ALL "ssdp:all" @@ -108,10 +112,12 @@ extern int log_level; extern int log_opts; extern char uuid[42]; extern char url[128]; +extern char deviceType[64]; extern char fname[128]; extern char model[128]; extern char modelNumber[128]; extern char serialNumber[128]; +extern char iconFile[256]; extern char mfrurl[128]; extern char mfrnm[128]; extern int ttl; diff --git a/src/ssdpd.c b/src/ssdpd.c index bd33034..44001af 100644 --- a/src/ssdpd.c +++ b/src/ssdpd.c @@ -29,10 +29,12 @@ int ttl = MC_TTL_DEFAULT; char *cachefn = NULL; char *ver = NULL; char *os = NULL; +char deviceType[64] = "Basic"; char fname[128]; char model[128]; char modelNumber[128]; char serialNumber[128]; +char iconFile[256]; #ifdef MANUFACTURER_URL char mfrurl[128] = MANUFACTURER_URL; #else @@ -561,28 +563,31 @@ static void signal_init(void) static int usage(int code) { - printf("Usage: %s [-hnsvw] [-c FILE] [-D MODEL] [-N MODELN] [-S SERIAL] [-d URL] [-i SEC] [-l LEVEL] [-m NAME] [-M URL]\n" + printf("Usage: %s [-hnsvw] [-c FILE] [-D MODEL] [-N MODELN] [-S SERIAL] [-d URL] [-i SEC] [-I ICON] [-T DTYPE] [-l LEVEL] [-m NAME] [-M URL]\n" " [-p URL] [-P FILE] [-r SEC] [-R NUM] [-t TTL] [-u UUID]\n" " [IFACE [IFACE ...]]\n" "\n" - " -c FILE Path to alternate ssdpd.cache to store and/or read the UUID\n" - " -d URL Override UPnP description.xml URL in announcements. The '%%s' in\n" - " the URL is replaced with the IP, e.g. https://%%s:1901/main.xml\n" + " SSDP Params:\n" + " -T DTYPE Override deviceType in the default description.xml\n" " -D MODEL Override modelName in the default description.xml\n" " -N MODELN Override modelNumber in the default description.xml\n" " -S SERIAL Override serialNumber in the default description.xml\n" " -f FNAME Override friendlyName in the default description.xml\n" - " -h This help text\n" - " -i SEC SSDP notify interval (30-900), default %d sec\n" - " -l LVL Set log level: none, err, notice (default), info, debug\n" - " -m NAME Override manufacturer in the default description.xml\n" + " -m NAME Override manufacturer in the default description.xml\n" " -M URL Override manufacturerURL in the default description.xml\n" + " -I ICON Icon file (only png file, required 128x128px)\n" + " -p URL Override presentationURL (WebUI) in the default description.xml\n" + " The '%%s' is replaced with the IP address. Default: http://%%s/\n" + " -c FILE Path to alternate ssdpd.cache to store and/or read the UUID\n" + " -d URL Override UPnP description.xml URL in announcements. The '%%s' in\n" + " the URL is replaced with the IP, e.g. https://%%s:1901/main.xml\n" + " -h This help text\n" + " -i SEC SSDP notify interval (30-900), default %d sec\n" + " -l LVL Set log level: none, err, notice (default), info, debug\n" " -n Run in foreground, do not daemonize by default\n" " -r SEC Interface refresh interval (5-1800), default %d sec\n" " -R NUM Initial retries, using 10 sec refresh interval, default 3 times\n" " Note: unused on systems with netlink interface monitoring.\n" - " -p URL Override presentationURL (WebUI) in the default description.xml\n" - " The '%%s' is replaced with the IP address. Default: http://%%s/\n" " -P FILE Override PID file location, absolute path required\n" " -s Use syslog, default unless running in foreground, -n\n" " -t TTL TTL for multicast frames, default 2, according to the UDA\n" @@ -614,7 +619,7 @@ int main(int argc, char *argv[]) int nlmon = 0; int c; - while ((c = getopt(argc, argv, "c:d:D:f:hi:l:m:M:np:P:r:R:st:u:vwN:S:")) != EOF) { + while ((c = getopt(argc, argv, "c:d:D:f:hi:l:m:M:np:P:r:R:st:u:vwN:S:I:T:")) != EOF) { switch (c) { case 'c': cachefn = strdup(optarg); @@ -627,13 +632,20 @@ int main(int argc, char *argv[]) case 'D': strlcpy(model, optarg, sizeof(model)); break; - + + case 'T': + strlcpy(deviceType, optarg, sizeof(deviceType)); + break; case 'N': - strlcpy(modelNumber, optarg, sizeof(model)); + strlcpy(modelNumber, optarg, sizeof(modelNumber)); break; case 'S': - strlcpy(serialNumber, optarg, sizeof(model)); + strlcpy(serialNumber, optarg, sizeof(serialNumber)); + break; + + case 'I': + strlcpy(iconFile, optarg, sizeof(iconFile)); break; case 'f': diff --git a/src/web.c b/src/web.c index c11781b..cf8922b 100644 --- a/src/web.c +++ b/src/web.c @@ -46,7 +46,7 @@ const char *xml = " 0\r\n" " \r\n" " \r\n" - " urn:schemas-upnp-org:device:Basic:1\r\n" + " urn:schemas-upnp-org:device:%s:1\r\n" " %s\r\n" " %s\r\n%s" " %s\r\n" @@ -54,23 +54,16 @@ const char *xml = " %s" " %s\r\n" " %s\r\n" + " " + " " + " image/png" + " 120" + " 120" + " 24" + " icon.png" + " " + " " " \r\n" - //"" - //"" - //"image/png" - //"48" - //"48" - //"24" - //"icon48.png" - //"" - //"" - //"image/png" - //"120" - //"120" - //"24" - //"icon120.png" - //"" - //"" "\r\n" "\r\n"; @@ -142,6 +135,12 @@ static int validate_http(int sd, char *http) return 0; } +static int errorNotFound(int sd){ + if (write(sd, "HTTP/1.1 404 Not Found\r\n", 24) < 0) + logit(LOG_WARNING, "Failed returning status 404 to client: %s", strerror(errno)); + return -1; +} + static int respond(int sd, struct sockaddr_in *sin) { struct pollfd pfd = { @@ -151,7 +150,7 @@ static int respond(int sd, struct sockaddr_in *sin) const char *head = "HTTP/1.1 200 OK\r\n" "Date: %s\r\n" "Server: ssdp-responder/%s\r\n" - "Content-Type: text/xml\r\n" + "Content-Type: %s\r\n" "Connection: close\r\n" "\r\n"; char manufacturer_url[192] = ""; @@ -187,7 +186,7 @@ static int respond(int sd, struct sockaddr_in *sin) if (validate_http(sd, reqline[2])) return -1; - snprintf(mesg, sizeof(mesg), head, compose_time(), VERSION); + snprintf(mesg, sizeof(mesg), head, compose_time(), VERSION, MIME_XML); if (send(sd, mesg, strlen(mesg), 0) < 0) return -1; } else if (strncmp(reqline[0], "GET", 4) == 0) { @@ -195,11 +194,57 @@ static int respond(int sd, struct sockaddr_in *sin) return -1; /* XXX: Add support for icon as well */ - if (!reqline[1] || !strstr(reqline[1], LOCATION_DESC)) { - if (write(sd, "HTTP/1.1 404 Not Found\r\n", 24) < 0) - logit(LOG_WARNING, "Failed returning status 404 to client: %s", strerror(errno)); - return -1; + if (!reqline[1] || (!strstr(reqline[1], LOCATION_DESC) && !strstr(reqline[1], LOCATION_ICON))) { + return errorNotFound(sd); + } + + if(strstr(reqline[1], LOCATION_ICON)){ + if(!strlen(iconFile)){ + return errorNotFound(sd); + } + + FILE *fp = fopen(iconFile, "r"); //open file + if(fp) + { + logit(LOG_DEBUG, "Sending Icon reply ..."); + //Calculate file size + int64_t _file_size = 0; + fseek(fp, 0, SEEK_END); + _file_size = ftello(fp); + + //malloc buffer for image + uint8_t *buffer = (uint8_t*)malloc(_file_size + 1); + if(buffer == NULL){ + logit(LOG_WARNING, "Failed malloc()"); + return errorNotFound(sd); + } + fseek(fp, 0, SEEK_SET); //move to start file + + //Read image file in buffer + if(!fread(buffer, _file_size, 1, fp)){ + logit(LOG_DEBUG, "Error reading image file"); + return errorNotFound(sd); + } + + snprintf(mesg, sizeof(mesg), head, compose_time(), VERSION, MIME_PNG); //Generate head string + //Send HTTP Headers + if (send(sd, mesg, strlen(mesg), 0) < 0) { + logit(LOG_WARNING, "Failed sending file to client: %s", strerror(errno)); + } + //Send Image buffer + if (send(sd, buffer, _file_size, 0) < 0) { + logit(LOG_WARNING, "Failed sending file to client: %s", strerror(errno)); + } + fclose(fp); //Close file + free(buffer); //Free buffer + } + else{ + logit(LOG_DEBUG, "Icon file not found ..."); + return errorNotFound(sd); + } + return 0; } + if(!strlen(fname)) gethostname(fname, sizeof(fname)); if (mfrurl[0]) @@ -207,8 +252,9 @@ static int respond(int sd, struct sockaddr_in *sin) " %s\r\n", mfrurl); logit(LOG_DEBUG, "Sending XML reply ..."); - rc = snprintf(mesg, sizeof(mesg), head, compose_time(), VERSION); + rc = snprintf(mesg, sizeof(mesg), head, compose_time(), VERSION, MIME_XML); snprintf(&mesg[rc], sizeof(mesg) - rc, xml, + deviceType, fname, mfrnm, manufacturer_url,