From 298bb25ae81383ea20e22de5dd053f2a29197923 Mon Sep 17 00:00:00 2001
From: Urval <kheniurval777@gmail.com>
Date: Mon, 13 Apr 2026 04:19:44 +0530
Subject: [PATCH 2/5] service_scan: Add ALPN annotation and HTTP/2 hinting

---
 nse_nmaplib.cc  |   3 +-
 portlist.cc     |   5 +-
 portlist.h      |   5 +-
 service_scan.cc | 137 +++++++++++++++++++++++++++++++++++++++++++++---
 service_scan.h  |   1 +
 5 files changed, 141 insertions(+), 10 deletions(-)

diff --git a/nse_nmaplib.cc b/nse_nmaplib.cc
index 1a6b30ffa..1796b8bf9 100644
--- a/nse_nmaplib.cc
+++ b/nse_nmaplib.cc
@@ -581,6 +581,7 @@ static int l_set_port_version (lua_State *L)
     *product        = (lua_getfield(L, 4, "product"),    lua_tostring(L, -1)),
     *version        = (lua_getfield(L, 4, "version"),    lua_tostring(L, -1)),
     *extrainfo      = (lua_getfield(L, 4, "extrainfo"),  lua_tostring(L, -1)),
+    *alpn           = (lua_getfield(L, 4, "alpn"),       lua_tostring(L, -1)),
     *hostname       = (lua_getfield(L, 4, "hostname"),   lua_tostring(L, -1)),
     *ostype         = (lua_getfield(L, 4, "ostype"),     lua_tostring(L, -1)),
     *devicetype     = (lua_getfield(L, 4, "devicetype"), lua_tostring(L, -1)),
@@ -606,7 +607,7 @@ static int l_set_port_version (lua_State *L)
 
   target->ports.setServiceProbeResults(p->portno, p->proto,
       probestate, name, tunnel, product,
-      version, extrainfo, hostname, ostype, devicetype,
+      version, extrainfo, alpn, hostname, ostype, devicetype,
       (cpe.size() > 0) ? &cpe : NULL,
       probestate==PROBESTATE_FINISHED_HARDMATCHED ? NULL : service_fp);
   return 0;
diff --git a/portlist.cc b/portlist.cc
index 1d958d334..f15608f43 100644
--- a/portlist.cc
+++ b/portlist.cc
@@ -114,6 +114,7 @@ void serviceDeductions::erase() {
   this->hostname = NULL;
   this->ostype = NULL;
   this->devicetype = NULL;
+  this->alpn.clear();
   this->service_tunnel = SERVICE_TUNNEL_NONE;
   this->service_fp = NULL;
   this->dtype = SERVICE_DETECTION_TABLE;
@@ -181,6 +182,7 @@ serviceDeductions::serviceDeductions() {
   hostname = NULL;
   ostype = NULL;
   devicetype = NULL;
+  alpn.clear();
   service_tunnel = SERVICE_TUNNEL_NONE;
   service_fp = NULL;
   dtype = SERVICE_DETECTION_TABLE;
@@ -310,7 +312,7 @@ static char *cstringSanityCheck(const char* string, int len) {
 void PortList::setServiceProbeResults(u16 portno, int protocol,
   enum serviceprobestate sres, const char *sname,
   enum service_tunnel_type tunnel, const char *product, const char *version,
-  const char *extrainfo, const char *hostname, const char *ostype,
+  const char *extrainfo, const char *alpn, const char *hostname, const char *ostype,
   const char *devicetype, const std::vector<const char *> *cpe,
   const char *fingerprint) {
   std::vector<char *>::iterator it;
@@ -361,6 +363,7 @@ void PortList::setServiceProbeResults(u16 portno, int protocol,
   port->service->product = cstringSanityCheck(product, 80);
   port->service->version = cstringSanityCheck(version, 80);
   port->service->extrainfo = cstringSanityCheck(extrainfo, 256);
+  port->service->alpn = alpn ? alpn : "";
   port->service->hostname = cstringSanityCheck(hostname, 80);
   port->service->ostype = cstringSanityCheck(ostype, 32);
   port->service->devicetype = cstringSanityCheck(devicetype, 32);
diff --git a/portlist.h b/portlist.h
index 5c4eb56dd..9f1b9de96 100644
--- a/portlist.h
+++ b/portlist.h
@@ -130,6 +130,7 @@ struct serviceDeductions {
   char *hostname;
   char *ostype;
   char *devicetype;
+  std::string alpn;
   std::vector<char *> cpe;
   // SERVICE_TUNNEL_NONE or SERVICE_TUNNEL_SSL
   enum service_tunnel_type service_tunnel;
@@ -230,9 +231,9 @@ class PortList {
   void setServiceProbeResults(u16 portno, int protocol,
                               enum serviceprobestate sres, const char *sname,
                               enum service_tunnel_type tunnel, const char *product,
-                              const char *version, const char *hostname,
+                              const char *version, const char *extrainfo,
+                              const char *alpn, const char *hostname,
                               const char *ostype, const char *devicetype,
-                              const char *extrainfo,
                               const std::vector<const char *> *cpe,
                               const char *fingerprint);
 
diff --git a/service_scan.cc b/service_scan.cc
index a758962d7..ec0700f1e 100644
--- a/service_scan.cc
+++ b/service_scan.cc
@@ -90,6 +90,10 @@
 #include <openssl/ssl.h>
 #endif
 
+#ifndef HAVE_SSL_GET0_ALPN_SELECTED
+#define HAVE_SSL_GET0_ALPN_SELECTED 0
+#endif
+
 #if TIME_WITH_SYS_TIME
 # include <sys/time.h>
 # include <time.h>
@@ -113,6 +117,36 @@ extern NmapOps o;
 #define SERVICE_FIELD_LEN 80
 #define SERVICE_EXTRA_LEN 256
 #define SERVICE_TYPE_LEN 32
+
+#if HAVE_OPENSSL
+static int getSelectedAlpn(SSL *ssl, char *buf, size_t buflen) {
+#if HAVE_SSL_GET0_ALPN_SELECTED
+  const unsigned char *proto = NULL;
+  unsigned int proto_len = 0;
+  size_t n;
+
+  if (ssl == NULL || buf == NULL || buflen < 2) {
+    return 0;
+  }
+
+  SSL_get0_alpn_selected(ssl, &proto, &proto_len);
+  if (proto == NULL || proto_len == 0) {
+    return 0;
+  }
+
+  n = proto_len < buflen - 1 ? proto_len : buflen - 1;
+  memcpy(buf, proto, n);
+  buf[n] = '\0';
+  return (int) n;
+#else
+  (void) ssl;
+  if (buf != NULL && buflen > 0)
+    buf[0] = '\0';
+  return 0;
+#endif
+}
+#endif
+
 // Details on a particular service (open port) we are trying to match
 class ServiceNFO {
 public:
@@ -150,6 +184,7 @@ public:
   char cpe_h_matched[SERVICE_FIELD_LEN];
   char cpe_o_matched[SERVICE_FIELD_LEN];
   enum service_tunnel_type tunnel; /* SERVICE_TUNNEL_NONE, SERVICE_TUNNEL_SSL */
+  char alpn_selected[SERVICE_FIELD_LEN];
   // This stores our SSL session id, which will help speed up subsequent
   // SSL connections.  It's overwritten each time.  void* is used so we don't
   // need to #ifdef HAVE_OPENSSL all over.  We'll cast later as needed.
@@ -1553,6 +1588,34 @@ int AllProbes::isExcluded(unsigned short port, int proto) const {
   return 0;
 }
 
+bool AllProbes::isProbableSSLPort(int proto, u16 portno) const {
+  /* Common SSL/TLS ports that should be retried through SSL tunnel if cleartext fails */
+  static const u16 common_ssl_ports[] = {
+    443, 465, 587, 636, 989, 990, 992, 993, 995,
+    8443, 9443, 9999, 3269, 3306, 5432, 5985, 5986, 6697, 8883, 8984, 9001, 0
+  };
+  
+  if (proto != IPPROTO_TCP)
+    return false;
+
+  /* First check hardcoded common SSL ports for this TCP service */
+  for (int i = 0; common_ssl_ports[i] != 0; i++) {
+    if (portno == common_ssl_ports[i])
+      return true;
+  }
+
+  /* Also check if port is explicitly in nmap-service-probes sslports directives */
+  std::vector<ServiceProbe *>::const_iterator vi;
+  for (vi = probes.begin(); vi != probes.end(); vi++) {
+    if ((*vi)->getProbeProtocol() != proto)
+      continue;
+    if ((*vi)->portIsProbable(SERVICE_TUNNEL_SSL, portno))
+      return true;
+  }
+
+  return false;
+}
+
 
 // Before this function is called, the fallbacks exist as unparsed
 // comma-separated strings in the fallbackStr field of each probe.
@@ -1628,6 +1691,7 @@ ServiceNFO::ServiceNFO(AllProbes *newAP) {
   hostname_matched[0] = ostype_matched[0] = devicetype_matched[0] = '\0';
   cpe_a_matched[0] = cpe_h_matched[0] = cpe_o_matched[0] = '\0';
   tunnel = SERVICE_TUNNEL_NONE;
+  alpn_selected[0] = '\0';
   ssl_session = NULL;
   softMatchFound = false;
   servicefplen = servicefpalloc = 0;
@@ -1870,6 +1934,24 @@ bool dropdown = false;
      current_probe++;
    }
 
+#ifdef HAVE_OPENSSL
+   if (tunnel == SERVICE_TUNNEL_NONE && proto == IPPROTO_TCP &&
+       AP->isProbableSSLPort(proto, portno)) {
+     /* If cleartext probing produced no match on an SSL-likely port,
+      * retry all probes through SSL to collect tunneled service metadata.
+      */
+     tunnel = SERVICE_TUNNEL_SSL;
+     softMatchFound = false;
+     probe_matched = NULL;
+     product_matched[0] = version_matched[0] = extrainfo_matched[0] = '\0';
+     hostname_matched[0] = ostype_matched[0] = devicetype_matched[0] = '\0';
+     cpe_a_matched[0] = cpe_h_matched[0] = cpe_o_matched[0] = '\0';
+     alpn_selected[0] = '\0';
+     resetProbes(true);
+     return nextProbe(true);
+   }
+#endif
+
    // Tried all NONMATCHINGPROBES -- we're finished
    probe_state = (softMatchFound)? PROBESTATE_FINISHED_SOFTMATCHED : PROBESTATE_FINISHED_NOMATCH;
    return NULL;
@@ -2202,6 +2284,7 @@ static int scanThroughTunnel(ServiceNFO *svc) {
   svc->product_matched[0] = svc->version_matched[0] = svc->extrainfo_matched[0] = '\0';
   svc->hostname_matched[0] = svc->ostype_matched[0] = svc->devicetype_matched[0] = '\0';
   svc->cpe_a_matched[0] = svc->cpe_h_matched[0] = svc->cpe_o_matched[0] = '\0';
+  svc->alpn_selected[0] = '\0';
   svc->softMatchFound = false;
    svc->resetProbes(true);
   return 1;
@@ -2347,6 +2430,9 @@ static void servicescan_connect_handler(nsock_pool nsp, nsock_event nse, void *m
   enum nse_status status = nse_status(nse);
   enum nse_type type = nse_type(nse);
   ServiceNFO *svc = (ServiceNFO *) mydata;
+  if (svc == NULL)
+    return;
+
   ServiceProbe *probe = svc->currentProbe();
   ServiceGroup *SG = (ServiceGroup *) nsock_pool_get_udata(nsp);
 
@@ -2369,6 +2455,12 @@ static void servicescan_connect_handler(nsock_pool nsp, nsock_event nse, void *m
       } else {
         svc->ssl_session = (SSL_SESSION *)(nsock_iod_get_ssl_session(nsi, 1));
       }
+
+      {
+        char alpn_buf[64];
+        if (getSelectedAlpn((SSL *) nsock_iod_get_ssl(nsi), alpn_buf, sizeof(alpn_buf)) > 0)
+          Strncpy(svc->alpn_selected, alpn_buf, sizeof(svc->alpn_selected));
+      }
     }
 #endif
 
@@ -2419,6 +2511,9 @@ static void servicescan_write_handler(nsock_pool nsp, nsock_event nse, void *myd
   ServiceGroup *SG;
   int err;
 
+  if (svc == NULL)
+    return;
+
   SG = (ServiceGroup *) nsock_pool_get_udata(nsp);
   nsi = nse_iod(nse);
 
@@ -2515,6 +2610,10 @@ static void servicescan_read_handler(nsock_pool nsp, nsock_event nse, void *myda
   enum nse_status status = nse_status(nse);
   enum nse_type type = nse_type(nse);
   ServiceNFO *svc = (ServiceNFO *) mydata;
+
+  if (svc == NULL)
+    return;
+
   ServiceProbe *probe = svc->currentProbe();
   ServiceGroup *SG = (ServiceGroup *) nsock_pool_get_udata(nsp);
   const u8 *readstr;
@@ -2712,6 +2811,28 @@ static void processResults(ServiceGroup *SG) {
 std::list<ServiceNFO *>::iterator svc;
 
  for(svc = SG->services_finished.begin(); svc != SG->services_finished.end(); svc++) {
+   if (svc == SG->services_finished.end() || *svc == NULL) {
+     continue;
+   }
+
+   const char *service_name = (*svc)->probe_matched;
+   const char *version = *(*svc)->version_matched ? (*svc)->version_matched : NULL;
+   const char *output_extrainfo = *(*svc)->extrainfo_matched ? (*svc)->extrainfo_matched : NULL;
+   const char *alpn = *(*svc)->alpn_selected ? (*svc)->alpn_selected : NULL;
+   char annotated_extrainfo[SERVICE_EXTRA_LEN + SERVICE_FIELD_LEN + 8];
+
+   if (alpn != NULL) {
+     if (output_extrainfo != NULL && *output_extrainfo != '\0')
+       Snprintf(annotated_extrainfo, sizeof(annotated_extrainfo), "%s ALPN:%s", output_extrainfo, alpn);
+     else
+       Snprintf(annotated_extrainfo, sizeof(annotated_extrainfo), "ALPN:%s", alpn);
+     output_extrainfo = annotated_extrainfo;
+
+     if (strcmp(alpn, "h2") == 0 && (service_name == NULL || *service_name == '\0') && version == NULL) {
+       service_name = "http";
+     }
+   }
+
    if ((*svc)->probe_state != PROBESTATE_FINISHED_NOMATCH) {
      std::vector<const char *> cpe;
 
@@ -2724,20 +2845,24 @@ std::list<ServiceNFO *>::iterator svc;
 
      (*svc)->target->ports.setServiceProbeResults((*svc)->portno, (*svc)->proto,
                                           (*svc)->probe_state,
-                                          (*svc)->probe_matched,
+                                         service_name,
                                           (*svc)->tunnel,
                                           *(*svc)->product_matched? (*svc)->product_matched : NULL,
-                                          *(*svc)->version_matched? (*svc)->version_matched : NULL,
-                                          *(*svc)->extrainfo_matched? (*svc)->extrainfo_matched : NULL,
+                                         version,
+                                         output_extrainfo,
+                                         alpn,
                                           *(*svc)->hostname_matched? (*svc)->hostname_matched : NULL,
                                           *(*svc)->ostype_matched? (*svc)->ostype_matched : NULL,
                                           *(*svc)->devicetype_matched? (*svc)->devicetype_matched : NULL,
                                           (cpe.size() > 0) ? &cpe : NULL,
                                           shouldWePrintFingerprint(*svc) ? (*svc)->getServiceFingerprint(NULL) : NULL);
    }  else {
+       const char *nomatch_name = service_name;
+       const char *nomatch_extrainfo = output_extrainfo;
+
        (*svc)->target->ports.setServiceProbeResults((*svc)->portno, (*svc)->proto,
-                                            (*svc)->probe_state, NULL,
-                                            (*svc)->tunnel, NULL, NULL, NULL, NULL, NULL, NULL,
+                                            (*svc)->probe_state, nomatch_name,
+                                            (*svc)->tunnel, NULL, NULL, nomatch_extrainfo, alpn, NULL, NULL, NULL,
                                             NULL,
                                             (*svc)->getServiceFingerprint(NULL));
    }
@@ -2766,7 +2891,7 @@ static void remove_excluded_ports(AllProbes *AP, ServiceGroup *SG) {
                                         PROBESTATE_EXCLUDED, NULL,
                                         SERVICE_TUNNEL_NONE,
                                         "Excluded from version scan", NULL,
-                                        NULL, NULL, NULL, NULL, NULL, NULL);
+                                        NULL, NULL, NULL, NULL, NULL, NULL, NULL);
 
       SG->services_remaining.erase(i);
       SG->services_finished.push_back(svc);
diff --git a/service_scan.h b/service_scan.h
index c91f5f7fa..17912a1a2 100644
--- a/service_scan.h
+++ b/service_scan.h
@@ -310,6 +310,7 @@ public:
   void compileFallbacks();
 
   int isExcluded(unsigned short port, int proto) const;
+  bool isProbableSSLPort(int proto, u16 portno) const;
   bool excluded_seen;
   struct scan_lists excludedports;
 
-- 
2.50.1.windows.1

