/*
 * soulseek.c
 *
 * Copyright (C) 2016-18 - ntop.org
 *
 * This file is part of nDPI, an open source deep packet inspection
 * library based on the OpenDPI and PACE technology by ipoque GmbH
 *
 * nDPI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * nDPI 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with nDPI.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "ndpi_protocol_ids.h"

#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_SOULSEEK

#include "ndpi_api.h"

#define SOULSEEK_DETECT \
    if(src != NULL) \
	      src->soulseek_last_safe_access_time = packet->tick_timestamp; \
    if(dst != NULL) \
	      dst->soulseek_last_safe_access_time = packet->tick_timestamp; \
    ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SOULSEEK, NDPI_PROTOCOL_UNKNOWN)

void ndpi_search_soulseek_tcp(struct ndpi_detection_module_struct *ndpi_struct,
			      struct ndpi_flow_struct *flow)
{
  struct ndpi_packet_struct *packet = &flow->packet;

  struct ndpi_id_struct *src = flow->src;
  struct ndpi_id_struct *dst = flow->dst;

  if(packet->tcp) {

    if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_SOULSEEK) {
      NDPI_LOG_DBG2(ndpi_struct, "packet marked as Soulseek\n");
      if(src != NULL)
	NDPI_LOG_DBG2(ndpi_struct,
		 "  SRC bitmask: %u, packet tick %llu , last safe access timestamp: %llu\n",
		      NDPI_COMPARE_PROTOCOL_TO_BITMASK(src->detected_protocol_bitmask, NDPI_PROTOCOL_SOULSEEK)
		 != 0 ? 1 : 0, (long long unsigned int) packet->tick_timestamp,
		      (long long unsigned int) src->soulseek_last_safe_access_time);
      if(dst != NULL)
	NDPI_LOG_DBG2(ndpi_struct,
		 "  DST bitmask: %u, packet tick %llu , last safe ts: %llu\n",
		 NDPI_COMPARE_PROTOCOL_TO_BITMASK(dst->detected_protocol_bitmask, NDPI_PROTOCOL_SOULSEEK)
		 != 0 ? 1 : 0, (long long unsigned int) packet->tick_timestamp,
		      (long long unsigned int) dst->soulseek_last_safe_access_time);

      if(packet->payload_packet_len == 431) {
	if(dst != NULL) {
	  dst->soulseek_last_safe_access_time = packet->tick_timestamp;
	}
	return;
      }
      if(packet->payload_packet_len == 12 && get_l32(packet->payload, 4) == 0x02) {
	if(src != NULL) {
	  src->soulseek_last_safe_access_time = packet->tick_timestamp;
	  if(packet->tcp != NULL && src->soulseek_listen_port == 0) {
	    src->soulseek_listen_port = get_l32(packet->payload, 8);
	    return;
	  }
	}
      }

      if(src != NULL && ((u_int32_t)(packet->tick_timestamp - src->soulseek_last_safe_access_time) < ndpi_struct->soulseek_connection_ip_tick_timeout)) {
	NDPI_LOG_DBG2(ndpi_struct,
		 "Soulseek: SRC update last safe access time and SKIP_FOR_TIME \n");
	src->soulseek_last_safe_access_time = packet->tick_timestamp;
      }

      if(dst != NULL && ((u_int32_t)(packet->tick_timestamp - dst->soulseek_last_safe_access_time) < ndpi_struct->soulseek_connection_ip_tick_timeout)) {
	NDPI_LOG_DBG2(ndpi_struct,
		 "Soulseek: DST update last safe access time and SKIP_FOR_TIME \n");
	dst->soulseek_last_safe_access_time = packet->tick_timestamp;
      }
    }


    if(dst != NULL && dst->soulseek_listen_port != 0 && dst->soulseek_listen_port == ntohs(packet->tcp->dest)
       && ((u_int32_t)(packet->tick_timestamp - dst->soulseek_last_safe_access_time) < ndpi_struct->soulseek_connection_ip_tick_timeout)) {
      
      NDPI_LOG_DBG2(ndpi_struct,
	       "Soulseek: Plain detection on Port : %u packet_tick_timestamp: %u soulseek_last_safe_access_time: %u soulseek_connection_ip_ticktimeout: %u\n",
	       dst->soulseek_listen_port, packet->tick_timestamp, dst->soulseek_last_safe_access_time, ndpi_struct->soulseek_connection_ip_tick_timeout);
      
      dst->soulseek_last_safe_access_time = packet->tick_timestamp;
      if(src != NULL)
	src->soulseek_last_safe_access_time = packet->tick_timestamp;

      NDPI_LOG_INFO(ndpi_struct, "found Soulseek\n");
      ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SOULSEEK, NDPI_PROTOCOL_UNKNOWN);
      return;
    }
    
    if(flow->l4.tcp.soulseek_stage == 0) {

      u_int32_t index = 0;

      if(packet->payload_packet_len >= 12 && packet->payload_packet_len < 300 && get_l32(packet->payload, 4) == 1) {
	while (!get_u_int16_t(packet->payload, index + 2)
	       && (index + get_l32(packet->payload, index)) < packet->payload_packet_len - 4) {
	  if(get_l32(packet->payload, index) < 8)	/*Minimum soulseek login msg is 8B */
	    break;

	  if(index + get_l32(packet->payload, index) + 4 <= index) {
	    /* avoid overflow */
	    break;
	  }

	  index += get_l32(packet->payload, index) + 4;
	}
	if(index + get_l32(packet->payload, index) == packet->payload_packet_len - 4 && !get_u_int16_t(packet->payload, 10)) {
	  /* This structure seems to be soulseek proto */
	  index = get_l32(packet->payload, 8) + 12;	// end of "user name"
	  if((index + 4) <= packet->payload_packet_len && !get_u_int16_t(packet->payload, index + 2))	// for passwd len
	    {
	      index += get_l32(packet->payload, index) + 4;	//end of  "Passwd"
	      if((index + 4 + 4) <= packet->payload_packet_len && !get_u_int16_t(packet->payload, index + 6))	// to read version,hashlen
		{
		  index += get_l32(packet->payload, index + 4) + 8;	// enf of "hash value"
		  if(index == get_l32(packet->payload, 0)) {
		    
		    NDPI_LOG_INFO(ndpi_struct, "found soulseek Login Detected\n");
		    SOULSEEK_DETECT;		    
		    return;
		  }
		}
	    }
	}
      }
      if (packet->payload_packet_len > 8
	  && packet->payload_packet_len < 200 && get_l32(packet->payload, 0) == packet->payload_packet_len - 4) {
	//Server Messages:
	const u_int32_t msgcode = get_l32(packet->payload, 4);

	if(msgcode == 0x7d) {
	  flow->l4.tcp.soulseek_stage = 1 + packet->packet_direction;
	  NDPI_LOG_DBG2(ndpi_struct, "Soulseek Messages Search\n");
	  return;
	} else if(msgcode == 0x02 && packet->payload_packet_len == 12) {
	  const u_int32_t soulseek_listen_port = get_l32(packet->payload, 8);

	  if(src != NULL) {
	    src->soulseek_last_safe_access_time = packet->tick_timestamp;

	    if(packet->tcp != NULL && src->soulseek_listen_port == 0) {
	      src->soulseek_listen_port = soulseek_listen_port;
	      NDPI_LOG_DBG2(ndpi_struct, "\n Listen Port Saved : %u", src->soulseek_listen_port);

	      if(dst != NULL)
		dst->soulseek_last_safe_access_time = packet->tick_timestamp;
	      
	      ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SOULSEEK, NDPI_PROTOCOL_UNKNOWN);
	      return;
	    }
	  }
	}
	//Peer Messages  : Peer Init Message Detection
	if(get_l32(packet->payload, 0) == packet->payload_packet_len - 4) {
	  const u_int32_t typelen = get_l32(packet->payload, packet->payload_packet_len - 9);
	  const u_int8_t type = packet->payload[packet->payload_packet_len - 5];
	  const u_int32_t namelen = get_l32(packet->payload, 5);
	  if(packet->payload[4] == 0x01 && typelen == 1
	      && namelen <= packet->payload_packet_len
	      && (4 + 1 + 4 + namelen + 4 + 1 + 4) ==
	      packet->payload_packet_len && (type == 'F' || type == 'P' || type == 'D')) {
	    NDPI_LOG_INFO(ndpi_struct, "found soulseek\n");
	    SOULSEEK_DETECT;
	    return;
	  }
	}
	NDPI_LOG_DBG2(ndpi_struct, "3\n");
	//Peer Message : Pierce Firewall
	if(packet->payload_packet_len == 9 && get_l32(packet->payload, 0) == 5
	    && packet->payload[4] <= 0x10 && get_u_int32_t(packet->payload, 5) != 0x00000000) {
	  flow->l4.tcp.soulseek_stage = 1 + packet->packet_direction;
	  NDPI_LOG_DBG2(ndpi_struct, "Soulseek Size 9 Pierce Firewall\n");
	  return;
	}
      }

      if(packet->payload_packet_len > 25 && packet->payload[4] == 0x01 && !get_u_int16_t(packet->payload, 7)
	  && !get_u_int16_t(packet->payload, 2)) {
	const u_int32_t usrlen = get_l32(packet->payload, 5);

	if(usrlen <= packet->payload_packet_len - 4 + 1 + 4 + 4 + 1 + 4) {
	  const u_int32_t typelen = get_l32(packet->payload, 4 + 1 + 4 + usrlen);
	  const u_int8_t type = packet->payload[4 + 1 + 4 + usrlen + 4];
	  if(typelen == 1 && (type == 'F' || type == 'P' || type == 'D')) {
	    NDPI_LOG_INFO(ndpi_struct, "found soulseek Pattern command(D|P|F)\n");
	    SOULSEEK_DETECT;	    
	    return;
	  }
	}
      }

    } else if(flow->l4.tcp.soulseek_stage == 2 - packet->packet_direction) {
      if(packet->payload_packet_len > 8) {
	if((packet->payload[0] || packet->payload[1]) && get_l32(packet->payload, 4) == 9) {
	  /* 9 is search result */
	  NDPI_LOG_INFO(ndpi_struct, "found soulseek Second Pkt\n");
	  SOULSEEK_DETECT;
	  return;
	}
	if(get_l32(packet->payload, 0) == packet->payload_packet_len - 4) {
	  const u_int32_t msgcode = get_l32(packet->payload, 4);
	  if(msgcode == 0x03 && packet->payload_packet_len >= 12)	//Server Message : Get Peer Address
	    {
	      const u_int32_t usrlen = get_l32(packet->payload, 8);
	      if(usrlen <= packet->payload_packet_len && 4 + 4 + 4 + usrlen == packet->payload_packet_len) {
		NDPI_LOG_INFO(ndpi_struct, "found soulseek Request Get Peer Address Detected\n");
		SOULSEEK_DETECT;		
		return;
	      }
	    }
	}
      }

      if(packet->payload_packet_len == 8 && get_l32(packet->payload, 4) == 0x00000004) {
	NDPI_LOG_INFO(ndpi_struct, "found soulseek\n");
	SOULSEEK_DETECT;
	return;
      }

      if(packet->payload_packet_len == 4
	 && get_u_int16_t(packet->payload, 2) == 0x00 && get_u_int16_t(packet->payload, 0) != 0x00) {
	NDPI_LOG_INFO(ndpi_struct, "found soulseek\n");
	SOULSEEK_DETECT;	
	return;
      } else if(packet->payload_packet_len == 4) {
	flow->l4.tcp.soulseek_stage = 3;
	return;
      }
    } else if(flow->l4.tcp.soulseek_stage == 1 + packet->packet_direction) {
      if(packet->payload_packet_len > 8) {
	if(packet->payload[4] == 0x03 && get_l32(packet->payload, 5) == 0x00000031) {
	  NDPI_LOG_INFO(ndpi_struct, "found soulseek Second Pkt with SIGNATURE :: 0x0331000000 \n");
	  SOULSEEK_DETECT;	  
	  return;
	}
      }
    }
    if(flow->l4.tcp.soulseek_stage == 3 && packet->payload_packet_len == 8 && !get_u_int32_t(packet->payload, 4)) {

      NDPI_LOG_INFO(ndpi_struct, "found soulseek bcz of 8B  pkt\n");
      SOULSEEK_DETECT;
      return;
    }
    if(flow->l4.tcp.soulseek_stage && flow->packet_counter < 11) {
    } else {
      NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
    }
  }
}


void init_soulseek_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask)
{
  ndpi_set_bitmask_protocol_detection("Soulseek", ndpi_struct, detection_bitmask, *id,
				      NDPI_PROTOCOL_SOULSEEK,
				      ndpi_search_soulseek_tcp,
				      NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD,
				      SAVE_DETECTION_BITMASK_AS_UNKNOWN,
				      ADD_TO_DETECTION_BITMASK);

  *id += 1;
}
