2021-09-22 22:41:07 +05:30
package ssl
import (
2022-04-01 14:29:02 -05:00
"fmt"
2021-09-22 22:41:07 +05:30
"net"
"time"
2022-09-01 23:56:55 +05:30
"github.com/fatih/structs"
2021-11-01 18:02:45 +05:30
jsoniter "github.com/json-iterator/go"
2021-09-22 22:41:07 +05:30
"github.com/pkg/errors"
2021-11-25 17:09:20 +02:00
2021-09-22 22:41:07 +05:30
"github.com/projectdiscovery/fastdialer/fastdialer"
2021-09-24 19:35:00 +05:30
"github.com/projectdiscovery/gologger"
2023-10-17 17:44:13 +05:30
"github.com/projectdiscovery/nuclei/v3/pkg/model"
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool"
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
2022-09-01 23:56:55 +05:30
"github.com/projectdiscovery/tlsx/pkg/tlsx"
"github.com/projectdiscovery/tlsx/pkg/tlsx/clients"
2023-03-04 04:47:41 +05:30
"github.com/projectdiscovery/tlsx/pkg/tlsx/openssl"
errorutil "github.com/projectdiscovery/utils/errors"
stringsutil "github.com/projectdiscovery/utils/strings"
urlutil "github.com/projectdiscovery/utils/url"
2021-09-22 22:41:07 +05:30
)
// Request is a request for the SSL protocol
type Request struct {
// Operators for the current request go here.
2023-02-07 16:10:40 +08:00
operators . Operators ` yaml:",inline,omitempty" json:",inline,omitempty" `
CompiledOperators * operators . Operators ` yaml:"-" json:"-" `
2022-09-01 23:56:55 +05:30
2023-08-31 18:03:01 +05:30
// ID is the optional id of the request
2023-09-16 16:02:17 +05:30
ID string ` yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id of the request,description=ID of the request" `
2023-08-31 18:03:01 +05:30
2021-11-05 03:01:41 +05:30
// description: |
// Address contains address for the request
2023-02-07 16:10:40 +08:00
Address string ` yaml:"address,omitempty" json:"address,omitempty" jsonschema:"title=address for the ssl request,description=Address contains address for the request" `
2022-01-25 13:26:22 +01:00
// description: |
// Minimum tls version - auto if not specified.
// values:
// - "sslv3"
// - "tls10"
// - "tls11"
// - "tls12"
// - "tls13"
2023-02-07 16:10:40 +08:00
MinVersion string ` yaml:"min_version,omitempty" json:"min_version,omitempty" jsonschema:"title=Min. TLS version,description=Minimum tls version - automatic if not specified.,enum=sslv3,enum=tls10,enum=tls11,enum=tls12,enum=tls13" `
2022-01-25 13:26:22 +01:00
// description: |
// Max tls version - auto if not specified.
// values:
// - "sslv3"
// - "tls10"
// - "tls11"
// - "tls12"
// - "tls13"
2023-02-07 16:10:40 +08:00
MaxVersion string ` yaml:"max_version,omitempty" json:"max_version,omitempty" jsonschema:"title=Max. TLS version,description=Max tls version - automatic if not specified.,enum=sslv3,enum=tls10,enum=tls11,enum=tls12,enum=tls13" `
2022-01-25 13:26:22 +01:00
// description: |
2022-01-25 20:48:21 +01:00
// Client Cipher Suites - auto if not specified.
2023-03-27 09:18:27 -04:00
CipherSuites [ ] string ` yaml:"cipher_suites,omitempty" json:"cipher_suites,omitempty" `
2022-12-04 17:34:18 +01:00
// description: |
// Tls Scan Mode - auto if not specified
// values:
// - "ctls"
// - "ztls"
// - "auto"
2023-03-04 04:47:41 +05:30
// - "openssl" # reverts to "auto" is openssl is not installed
2023-02-07 16:10:40 +08:00
ScanMode string ` yaml:"scan_mode,omitempty" json:"scan_mode,omitempty" jsonschema:"title=Scan Mode,description=Scan Mode - auto if not specified.,enum=ctls,enum=ztls,enum=auto" `
2021-09-22 22:41:07 +05:30
// cache any variables that may be needed for operation.
dialer * fastdialer . Dialer
2022-09-01 23:56:55 +05:30
tlsx * tlsx . Service
2023-05-31 16:58:10 -04:00
options * protocols . ExecutorOptions
2021-09-22 22:41:07 +05:30
}
2023-01-17 18:20:05 +05:30
// CanCluster returns true if the request can be clustered.
func ( request * Request ) CanCluster ( other * Request ) bool {
2023-03-27 09:18:27 -04:00
if len ( request . CipherSuites ) > 0 || request . MinVersion != "" || request . MaxVersion != "" {
2023-01-17 18:20:05 +05:30
return false
}
if request . Address != other . Address || request . ScanMode != other . ScanMode {
return false
}
return true
}
2021-09-22 22:41:07 +05:30
// Compile compiles the request generators preparing any requests possible.
2023-05-31 16:58:10 -04:00
func ( request * Request ) Compile ( options * protocols . ExecutorOptions ) error {
2021-11-03 02:34:48 +05:30
request . options = options
2021-09-22 22:41:07 +05:30
client , err := networkclientpool . Get ( options . Options , & networkclientpool . Configuration { } )
if err != nil {
2023-03-04 04:47:41 +05:30
return errorutil . NewWithTag ( "ssl" , "could not get network client" ) . Wrap ( err )
2021-09-22 22:41:07 +05:30
}
2021-11-03 02:34:48 +05:30
request . dialer = client
2023-03-04 04:47:41 +05:30
switch {
//validate scanmode
case request . ScanMode == "" :
request . ScanMode = "auto"
case ! stringsutil . EqualFoldAny ( request . ScanMode , "auto" , "openssl" , "ztls" , "ctls" ) :
return errorutil . NewWithTag ( request . TemplateID , "template %v does not contain valid scan-mode" , request . TemplateID )
case request . ScanMode == "openssl" && ! openssl . IsAvailable ( ) :
// if openssl is not installed instead of failing "auto" scanmode is used
request . ScanMode = "auto"
}
2021-09-22 22:41:07 +05:30
2022-09-01 23:56:55 +05:30
tlsxOptions := & clients . Options {
AllCiphers : true ,
2023-03-04 04:47:41 +05:30
ScanMode : request . ScanMode ,
2022-09-01 23:56:55 +05:30
Expired : true ,
SelfSigned : true ,
2023-03-04 04:47:41 +05:30
Revoked : true ,
2022-09-01 23:56:55 +05:30
MisMatched : true ,
MinVersion : request . MinVersion ,
MaxVersion : request . MaxVersion ,
2023-03-27 09:18:27 -04:00
Ciphers : request . CipherSuites ,
2022-09-01 23:56:55 +05:30
WildcardCertCheck : true ,
Retries : request . options . Options . Retries ,
Timeout : request . options . Options . Timeout ,
Fastdialer : client ,
2022-12-04 17:34:18 +01:00
ClientHello : true ,
ServerHello : true ,
2023-10-16 14:34:52 +05:30
DisplayDns : true ,
2022-09-01 23:56:55 +05:30
}
2023-03-04 04:47:41 +05:30
2022-09-01 23:56:55 +05:30
tlsxService , err := tlsx . New ( tlsxOptions )
2022-03-07 14:07:30 +05:30
if err != nil {
2023-03-04 04:47:41 +05:30
return errorutil . NewWithTag ( request . TemplateID , "could not create tlsx service" )
2022-03-07 14:07:30 +05:30
}
2022-09-01 23:56:55 +05:30
request . tlsx = tlsxService
2022-03-07 14:07:30 +05:30
2021-11-03 02:34:48 +05:30
if len ( request . Matchers ) > 0 || len ( request . Extractors ) > 0 {
compiled := & request . Operators
2022-06-24 23:09:27 +05:30
compiled . ExcludeMatchers = options . ExcludeMatchers
compiled . TemplateID = options . TemplateID
2021-09-22 22:41:07 +05:30
if err := compiled . Compile ( ) ; err != nil {
2023-03-04 04:47:41 +05:30
return errorutil . NewWithTag ( request . TemplateID , "could not compile operators got %v" , err )
2021-09-22 22:41:07 +05:30
}
2021-11-03 02:34:48 +05:30
request . CompiledOperators = compiled
2021-09-22 22:41:07 +05:30
}
return nil
}
2023-01-17 18:20:05 +05:30
// Options returns executer options for http request
2023-05-31 16:58:10 -04:00
func ( r * Request ) Options ( ) * protocols . ExecutorOptions {
2023-01-17 18:20:05 +05:30
return r . options
}
2021-09-22 22:41:07 +05:30
// Requests returns the total number of requests the rule will perform
2021-11-03 02:34:48 +05:30
func ( request * Request ) Requests ( ) int {
2021-09-22 22:41:07 +05:30
return 1
}
// GetID returns the ID for the request if any.
2021-11-03 02:34:48 +05:30
func ( request * Request ) GetID ( ) string {
2021-09-22 22:41:07 +05:30
return ""
}
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
2022-10-03 12:12:20 +02:00
func ( request * Request ) ExecuteWithResults ( input * contextargs . Context , dynamicValues , previous output . InternalEvent , callback protocols . OutputEventCallback ) error {
2022-12-03 01:59:22 +01:00
hostPort , err := getAddress ( input . MetaInput . Input )
2021-09-22 22:41:07 +05:30
if err != nil {
2023-03-04 04:47:41 +05:30
return err
2021-09-22 22:41:07 +05:30
}
2022-12-03 01:59:22 +01:00
hostname , port , _ := net . SplitHostPort ( hostPort )
2021-09-22 22:41:07 +05:30
2021-11-05 03:01:41 +05:30
requestOptions := request . options
2022-12-20 21:32:18 +01:00
payloadValues := generators . BuildPayloadFromOptions ( request . options . Options )
2021-11-05 03:01:41 +05:30
for k , v := range dynamicValues {
payloadValues [ k ] = v
}
2022-12-20 21:32:18 +01:00
2022-12-03 01:59:22 +01:00
payloadValues [ "Hostname" ] = hostPort
2021-11-05 03:01:41 +05:30
payloadValues [ "Host" ] = hostname
payloadValues [ "Port" ] = port
2023-05-02 23:49:56 +05:30
hostnameVariables := protocolutils . GenerateDNSVariables ( hostname )
2023-06-09 19:52:56 +05:30
// add template context variables to varMap
2023-08-31 18:03:01 +05:30
values := generators . MergeMaps ( payloadValues , hostnameVariables , request . options . GetTemplateCtx ( input . MetaInput ) . GetAll ( ) )
2022-07-22 01:35:21 +05:30
variablesMap := request . options . Variables . Evaluate ( values )
2023-05-25 18:32:35 +02:00
payloadValues = generators . MergeMaps ( variablesMap , payloadValues , request . options . Constants )
2022-07-22 01:35:21 +05:30
2022-11-01 20:28:50 +05:30
if vardump . EnableVarDump {
2023-10-13 13:17:27 +05:30
gologger . Debug ( ) . Msgf ( "SSL Protocol request variables: \n%s\n" , vardump . DumpVariables ( payloadValues ) )
2022-08-25 15:37:03 +05:30
}
2021-11-05 03:01:41 +05:30
finalAddress , dataErr := expressions . EvaluateByte ( [ ] byte ( request . Address ) , payloadValues )
if dataErr != nil {
2022-11-09 14:18:56 +01:00
requestOptions . Output . Request ( requestOptions . TemplateID , input . MetaInput . Input , request . Type ( ) . String ( ) , dataErr )
2021-11-05 03:01:41 +05:30
requestOptions . Progress . IncrementFailedRequestsBy ( 1 )
return errors . Wrap ( dataErr , "could not evaluate template expressions" )
}
addressToDial := string ( finalAddress )
2022-09-01 23:56:55 +05:30
host , port , err := net . SplitHostPort ( addressToDial )
if err != nil {
2023-03-04 04:47:41 +05:30
return errorutil . NewWithErr ( err ) . Msgf ( "could not split input host port" )
2022-01-24 18:20:29 +01:00
}
2022-12-03 01:59:22 +01:00
var hostIp string
if input . MetaInput . CustomIP != "" {
hostIp = input . MetaInput . CustomIP
} else {
hostIp = host
}
response , err := request . tlsx . Connect ( host , hostIp , port )
2021-09-22 22:41:07 +05:30
if err != nil {
2022-11-09 14:18:56 +01:00
requestOptions . Output . Request ( requestOptions . TemplateID , input . MetaInput . Input , request . Type ( ) . String ( ) , err )
2021-11-05 03:01:41 +05:30
requestOptions . Progress . IncrementFailedRequestsBy ( 1 )
2023-03-04 04:47:41 +05:30
return errorutil . NewWithTag ( request . TemplateID , "could not connect to server" ) . Wrap ( err )
2021-09-22 22:41:07 +05:30
}
2022-12-03 01:59:22 +01:00
requestOptions . Output . Request ( requestOptions . TemplateID , hostPort , request . Type ( ) . String ( ) , err )
2023-08-31 18:03:01 +05:30
gologger . Verbose ( ) . Msgf ( "[%s] Sent SSL request to %s" , request . options . TemplateID , hostPort )
2021-09-24 19:35:00 +05:30
2022-04-01 14:29:02 -05:00
if requestOptions . Options . Debug || requestOptions . Options . DebugRequests || requestOptions . Options . StoreResponse {
2022-11-09 14:18:56 +01:00
msg := fmt . Sprintf ( "[%s] Dumped SSL request for %s" , requestOptions . TemplateID , input . MetaInput . Input )
2022-04-01 14:29:02 -05:00
if requestOptions . Options . Debug || requestOptions . Options . DebugRequests {
2022-11-09 14:18:56 +01:00
gologger . Debug ( ) . Str ( "address" , input . MetaInput . Input ) . Msg ( msg )
2022-04-01 14:29:02 -05:00
}
if requestOptions . Options . StoreResponse {
2022-11-09 14:18:56 +01:00
request . options . Output . WriteStoreDebugData ( input . MetaInput . Input , request . options . TemplateID , request . Type ( ) . String ( ) , msg )
2022-04-01 14:29:02 -05:00
}
2021-11-01 18:02:45 +05:30
}
2022-09-01 23:56:55 +05:30
jsonData , _ := jsoniter . Marshal ( response )
2021-11-01 18:02:45 +05:30
jsonDataString := string ( jsonData )
2021-09-22 22:41:07 +05:30
data := make ( map [ string ] interface { } )
2023-03-04 04:47:41 +05:30
for k , v := range payloadValues {
data [ k ] = v
}
2021-11-22 17:53:25 +05:30
data [ "type" ] = request . Type ( ) . String ( )
2021-11-01 18:02:45 +05:30
data [ "response" ] = jsonDataString
2023-03-04 04:47:41 +05:30
data [ "host" ] = input . MetaInput . Input
2021-11-05 03:01:41 +05:30
data [ "matched" ] = addressToDial
2022-11-09 14:18:56 +01:00
if input . MetaInput . CustomIP != "" {
2022-12-03 01:59:22 +01:00
data [ "ip" ] = hostIp
2022-11-09 14:18:56 +01:00
} else {
data [ "ip" ] = request . dialer . GetDialedIP ( hostname )
}
2022-06-03 03:02:45 -05:00
data [ "template-path" ] = requestOptions . TemplatePath
data [ "template-id" ] = requestOptions . TemplateID
data [ "template-info" ] = requestOptions . TemplateInfo
2022-07-22 01:35:21 +05:30
2023-05-03 20:47:08 +05:30
// if response is not struct compatible, error out
if ! structs . IsStruct ( response ) {
return errorutil . NewWithTag ( "ssl" , "response cannot be parsed into a struct: %v" , response )
}
2022-09-01 23:56:55 +05:30
// Convert response to key value pairs and first cert chain item as well
responseParsed := structs . New ( response )
for _ , f := range responseParsed . Fields ( ) {
2023-05-03 20:47:08 +05:30
if ! f . IsExported ( ) {
// if field is not exported f.IsZero() , f.Value() will panic
continue
}
2023-06-09 19:52:56 +05:30
tag := protocolutils . CleanStructFieldJSONTag ( f . Tag ( "json" ) )
2022-09-01 23:56:55 +05:30
if tag == "" || f . IsZero ( ) {
continue
}
2023-08-31 18:03:01 +05:30
request . options . AddTemplateVar ( input . MetaInput , request . Type ( ) , request . ID , tag , f . Value ( ) )
2022-09-01 23:56:55 +05:30
data [ tag ] = f . Value ( )
}
2023-05-03 20:47:08 +05:30
// if certificate response is not struct compatible, error out
if ! structs . IsStruct ( response . CertificateResponse ) {
return errorutil . NewWithTag ( "ssl" , "certificate response cannot be parsed into a struct: %v" , response . CertificateResponse )
}
2022-09-01 23:56:55 +05:30
responseParsed = structs . New ( response . CertificateResponse )
for _ , f := range responseParsed . Fields ( ) {
2023-05-03 20:47:08 +05:30
if ! f . IsExported ( ) {
// if field is not exported f.IsZero() , f.Value() will panic
continue
}
2023-06-09 19:52:56 +05:30
tag := protocolutils . CleanStructFieldJSONTag ( f . Tag ( "json" ) )
2022-09-01 23:56:55 +05:30
if tag == "" || f . IsZero ( ) {
continue
}
2023-08-31 18:03:01 +05:30
request . options . AddTemplateVar ( input . MetaInput , request . Type ( ) , request . ID , tag , f . Value ( ) )
2022-09-01 23:56:55 +05:30
data [ tag ] = f . Value ( )
}
2023-06-09 19:52:56 +05:30
// add response fields ^ to template context and merge templatectx variables to output event
2023-08-31 18:03:01 +05:30
data = generators . MergeMaps ( data , request . options . GetTemplateCtx ( input . MetaInput ) . GetAll ( ) )
2021-11-05 03:01:41 +05:30
event := eventcreator . CreateEvent ( request , data , requestOptions . Options . Debug || requestOptions . Options . DebugResponse )
2022-04-01 14:29:02 -05:00
if requestOptions . Options . Debug || requestOptions . Options . DebugResponse || requestOptions . Options . StoreResponse {
2022-11-09 14:18:56 +01:00
msg := fmt . Sprintf ( "[%s] Dumped SSL response for %s" , requestOptions . TemplateID , input . MetaInput . Input )
2022-04-01 14:29:02 -05:00
if requestOptions . Options . Debug || requestOptions . Options . DebugResponse {
2022-06-03 03:02:45 -05:00
gologger . Debug ( ) . Msg ( msg )
gologger . Print ( ) . Msgf ( "%s" , responsehighlighter . Highlight ( event . OperatorsResult , jsonDataString , requestOptions . Options . NoColor , false ) )
2022-04-01 14:29:02 -05:00
}
if requestOptions . Options . StoreResponse {
2022-11-09 14:18:56 +01:00
request . options . Output . WriteStoreDebugData ( input . MetaInput . Input , request . options . TemplateID , request . Type ( ) . String ( ) , fmt . Sprintf ( "%s\n%s" , msg , jsonDataString ) )
2022-04-01 14:29:02 -05:00
}
2021-11-01 18:02:45 +05:30
}
2021-10-29 18:26:06 +05:30
callback ( event )
2021-09-22 22:41:07 +05:30
return nil
}
2021-11-26 16:23:54 +05:30
// RequestPartDefinitions contains a mapping of request part definitions and their
// description. Multiple definitions are separated by commas.
// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.
var RequestPartDefinitions = map [ string ] string {
"type" : "Type is the type of request made" ,
"response" : "JSON SSL protocol handshake details" ,
"not_after" : "Timestamp after which the remote cert expires" ,
"host" : "Host is the input to the template" ,
"matched" : "Matched is the input which was matched upon" ,
}
2021-09-22 22:41:07 +05:30
// getAddress returns the address of the host to make request to
func getAddress ( toTest string ) ( string , error ) {
2023-03-04 04:47:41 +05:30
urlx , err := urlutil . Parse ( toTest )
if err != nil {
// use given input instead of url parsing failure
2021-09-24 19:35:00 +05:30
return toTest , nil
2021-09-22 22:41:07 +05:30
}
2023-03-04 04:47:41 +05:30
if urlx . Port ( ) == "" {
urlx . UpdatePort ( "443" )
}
return urlx . Host , nil
2021-09-22 22:41:07 +05:30
}
2021-10-29 18:26:06 +05:30
// Match performs matching operation for a matcher on model and returns:
// true and a list of matched snippets if the matcher type is supports it
// otherwise false and an empty string slice
2021-11-03 02:34:48 +05:30
func ( request * Request ) Match ( data map [ string ] interface { } , matcher * matchers . Matcher ) ( bool , [ ] string ) {
2021-10-29 18:26:06 +05:30
return protocols . MakeDefaultMatchFunc ( data , matcher )
}
// Extract performs extracting operation for an extractor on model and returns true or false.
2021-11-03 02:34:48 +05:30
func ( request * Request ) Extract ( data map [ string ] interface { } , matcher * extractors . Extractor ) map [ string ] struct { } {
2021-10-29 18:26:06 +05:30
return protocols . MakeDefaultExtractFunc ( data , matcher )
}
// MakeResultEvent creates a result event from internal wrapped event
2021-11-03 02:34:48 +05:30
func ( request * Request ) MakeResultEvent ( wrapped * output . InternalWrappedEvent ) [ ] * output . ResultEvent {
return protocols . MakeDefaultResultEvent ( request , wrapped )
2021-10-29 18:26:06 +05:30
}
// GetCompiledOperators returns a list of the compiled operators
2021-11-03 02:34:48 +05:30
func ( request * Request ) GetCompiledOperators ( ) [ ] * operators . Operators {
return [ ] * operators . Operators { request . CompiledOperators }
2021-10-29 18:26:06 +05:30
}
2021-11-03 19:53:45 +05:30
// Type returns the type of the protocol request
func ( request * Request ) Type ( ) templateTypes . ProtocolType {
return templateTypes . SSLProtocol
}
2021-11-03 02:34:48 +05:30
func ( request * Request ) MakeResultEventItem ( wrapped * output . InternalWrappedEvent ) * output . ResultEvent {
2021-09-22 22:41:07 +05:30
data := & output . ResultEvent {
2023-01-17 18:20:05 +05:30
TemplateID : types . ToString ( wrapped . InternalEvent [ "template-id" ] ) ,
TemplatePath : types . ToString ( wrapped . InternalEvent [ "template-path" ] ) ,
Info : wrapped . InternalEvent [ "template-info" ] . ( model . Info ) ,
2021-11-22 17:53:25 +05:30
Type : types . ToString ( wrapped . InternalEvent [ "type" ] ) ,
2021-09-22 22:41:07 +05:30
Host : types . ToString ( wrapped . InternalEvent [ "host" ] ) ,
2023-03-04 04:47:41 +05:30
Matched : types . ToString ( wrapped . InternalEvent [ "matched" ] ) ,
2021-09-22 22:41:07 +05:30
Metadata : wrapped . OperatorsResult . PayloadValues ,
ExtractedResults : wrapped . OperatorsResult . OutputExtracts ,
Timestamp : time . Now ( ) ,
2021-11-22 17:53:25 +05:30
MatcherStatus : true ,
2021-09-22 22:41:07 +05:30
IP : types . ToString ( wrapped . InternalEvent [ "ip" ] ) ,
2023-11-11 02:12:27 +03:00
TemplateEncoded : request . options . EncodeTemplate ( ) ,
2021-09-22 22:41:07 +05:30
}
return data
}