479 lines
16 KiB
Plaintext
479 lines
16 KiB
Plaintext
OPTIONS SHORT CIRCUIT
|
|
|
|
IMPORT util
|
|
IMPORT com
|
|
|
|
|
|
FUNCTION apiclient(pemail)
|
|
DEFINE data_apilayer RECORD
|
|
email,did_you_mean,USER,domain,syntax_valid STRING,
|
|
is_disposable,is_role_account,is_catch_all,is_deliverable,can_connect_smtp,
|
|
is_inbox_full,is_disabled,mx_records,FREE,score BOOLEAN
|
|
END RECORD
|
|
DEFINE data RECORD
|
|
data RECORD
|
|
status ,
|
|
result,
|
|
deprecation_notice string,
|
|
score smallint,
|
|
email string,
|
|
regexp,
|
|
gibberish,
|
|
disposable,
|
|
webmail,
|
|
mx_records,
|
|
smtp_server,
|
|
smtp_check,
|
|
accept_all,
|
|
block BOOLEAN
|
|
|
|
END RECORD,
|
|
source DYNAMIC ARRAY OF STRING,
|
|
meta RECORD
|
|
param STRING
|
|
END RECORD
|
|
END RECORD
|
|
|
|
DEFINE data_ipq RECORD
|
|
MESSAGE STRING,
|
|
success,
|
|
valid,
|
|
disposable BOOLEAN,
|
|
smtp_score ,
|
|
overall_score DEC(10,2),
|
|
first_name string,
|
|
generic,
|
|
common,
|
|
dns_valid,
|
|
honeypot BOOLEAN,
|
|
deliverability STRING,
|
|
frequent_complainer BOOLEAN,
|
|
spam_trap_score STRING,
|
|
catch_all,
|
|
timed_out,
|
|
suspect,
|
|
recent_abuse BOOLEAN,
|
|
fraud_score DEC(10,2),
|
|
suggested_domain string,
|
|
leaked BOOLEAN
|
|
END RECORD
|
|
|
|
DEFINE estado,purl,cmd,res, msg,s,pemail,tiposervice STRING,
|
|
ret INT
|
|
DEFINE dominio, detalle, api_url, api_key STRING,
|
|
dns_estado SMALLINT,
|
|
hunter_ok BOOLEAN
|
|
|
|
# LA API PARA VERIFICAR ESTA OFFLINE 29/05/2025
|
|
|
|
{ LET estado = 'VALIDO'
|
|
LET ret = 200
|
|
RETURN estado,ret
|
|
}
|
|
# SE DEVUELVE SI EL EMAIL ESTA EN BLANCO
|
|
LET pemail = pemail.trim()
|
|
IF pemail IS NULL OR pemail.getLength() = 0 THEN
|
|
|
|
LET ret = 400
|
|
LET estado = 'DEBES DIGITAR EL EMAIL POR FAVOR'
|
|
RETURN estado,ret
|
|
END IF
|
|
DISPLAY "llego: ",pemail
|
|
|
|
# VALIDACION PROPIA 14/10/2026: FORMATO + DOMINIO EN DNS (NO DEPENDE DE LA API)
|
|
IF NOT email_formato_valido(pemail) THEN
|
|
INSERT INTO envia_email_log (email,estado,fecha,programa_envio,us_crea,score)
|
|
VALUES (pemail,'formato_invalido',getdate(),'mt101_local',suser_Sname(),0)
|
|
LET ret = 400
|
|
LET estado = 'EL FORMATO DEL EMAIL NO ES VALIDO'
|
|
RETURN estado,ret
|
|
END IF
|
|
|
|
LET dominio = pemail.subString(pemail.getIndexOf("@",1) + 1, pemail.getLength())
|
|
LET dns_estado = email_dominio_dns(dominio)
|
|
DISPLAY "dominio: ",dominio," dns: ",dns_estado
|
|
IF dns_estado = 0 THEN
|
|
INSERT INTO envia_email_log (email,estado,fecha,programa_envio,us_crea,score)
|
|
VALUES (pemail,'dominio_inexistente',getdate(),'mt101_dns',suser_Sname(),0)
|
|
LET ret = 400
|
|
LET estado = 'EL DOMINIO DEL EMAIL NO EXISTE O NO RECIBE CORREO'
|
|
RETURN estado,ret
|
|
END IF
|
|
# ELECCION DE SERVICIOS, POR EL MOMENTO ES IPQUALITY
|
|
# URL Y API KEY SE LEEN DE LA TABLA APIS (YA NO VAN EN EL FUENTE)
|
|
SELECT a.IDENTIFICACION, a.url, a.apikey INTO tiposervice, api_url, api_key FROM APIS a
|
|
WHERE a.estado = 'ONLINE'
|
|
IF STATUS = NOTFOUND THEN
|
|
RETURN estado,ret
|
|
END IF
|
|
LET tiposervice = tiposervice.trim()
|
|
LET api_url = api_url.trim()
|
|
LET api_key = api_key.trim()
|
|
# LET tiposervice = 'ipq1'
|
|
|
|
IF tiposervice = 'HUNTER' THEN
|
|
|
|
LET purl = api_url,"?email=",pemail CLIPPED,"&api_key=",api_key
|
|
CALL ipquality_send_command(purl, NULL) RETURNING ret,res
|
|
DISPLAY "response api: ",res," longitud: ",res.getLength()
|
|
|
|
# HUNTER PUEDE NO RESPONDER (CAIDO, 202 EN PROCESO, 222/451 SMTP):
|
|
# EN ESE CASO DECIDE LA VALIDACION DNS PROPIA
|
|
LET hunter_ok = FALSE
|
|
IF ret == 0 AND res.getLength() > 0 THEN
|
|
TRY
|
|
CALL util.JSON.parse(res,data)
|
|
LET hunter_ok = TRUE
|
|
CATCH
|
|
LET hunter_ok = FALSE
|
|
END TRY
|
|
END IF
|
|
|
|
IF NOT hunter_ok THEN
|
|
INSERT INTO envia_email_log (email,display_name,estado,fecha,programa_envio,us_crea,score)
|
|
VALUES (pemail,SFMT('hunter http=%1',ret),'hunter_sin_respuesta',getdate(),'mt101_dns',suser_Sname(),0)
|
|
CALL email_resultado_dns(dns_estado) RETURNING estado,ret
|
|
RETURN estado,ret
|
|
END IF
|
|
|
|
DISPLAY "DATA EMAIL: ",pemail CLIPPED, " score: ",data.data.score
|
|
LET detalle = SFMT('mx=%1 smtp=%2 check=%3 regexp=%4 disp=%5 dns=%6',
|
|
data.data.mx_records, data.data.smtp_server, data.data.smtp_check,
|
|
data.data.regexp, data.data.disposable, dns_estado)
|
|
INSERT INTO envia_email_log (email,display_name,estado,fecha,programa_envio,us_crea,score)
|
|
VALUES (pemail,detalle,data.data.status,getdate(),'mt101_hunter',suser_Sname(),data.data.score)
|
|
|
|
# DESECHABLES (MAILINATOR, ETC.) SE RECHAZAN SIEMPRE
|
|
IF data.data.disposable THEN
|
|
LET estado = 'NO SE PUEDE ENTREGAR MENSAJE A ESTA DIRECCION EMAIL'
|
|
LET ret = 400
|
|
RETURN estado,ret
|
|
END IF
|
|
|
|
IF (data.data.status = 'valid' OR data.data.status = 'accept_all' OR
|
|
data.data.status = 'unknown') AND data.data.score > 29 THEN
|
|
LET estado = 'VALIDO'
|
|
LET ret = 200
|
|
RETURN estado,ret
|
|
END IF
|
|
|
|
# HUNTER LO RECHAZA: SOLO SE RESPETA SI LLEGO AL SERVIDOR DE CORREO
|
|
# Y ESTE RECHAZO EL BUZON (EL BUZON NO EXISTE)
|
|
IF data.data.mx_records AND data.data.smtp_server AND NOT data.data.smtp_check THEN
|
|
LET estado = 'NO SE PUEDE ENTREGAR MENSAJE A ESTA DIRECCION EMAIL'
|
|
LET ret = 400
|
|
RETURN estado,ret
|
|
END IF
|
|
|
|
# HUNTER NO VE EL DOMINIO O NO PUDO CONECTAR (DOMINIOS .DO PEQUENOS):
|
|
# SI EL DNS CONFIRMA QUE EL DOMINIO RECIBE CORREO, SE ACEPTA
|
|
CALL email_resultado_dns(dns_estado) RETURNING estado,ret
|
|
IF ret = 200 THEN
|
|
INSERT INTO envia_email_log (email,display_name,estado,fecha,programa_envio,us_crea,score)
|
|
VALUES (pemail,detalle,'valid_dns',getdate(),'mt101_dns',suser_Sname(),0)
|
|
END IF
|
|
RETURN estado,ret
|
|
|
|
END IF
|
|
|
|
IF tiposervice = 'IPQUALITY' THEN
|
|
# EN APIS LA URL DE IPQUALITY YA TRAE LA KEY: .../email/<key>/
|
|
LET purl = api_url,pemail CLIPPED
|
|
CALL ipquality_send_command(purl, api_key) RETURNING ret,res
|
|
|
|
IF ret ==0 THEN
|
|
LET s = util.JSON.stringify(res)
|
|
CALL util.JSON.parse(res,data_ipq)
|
|
|
|
IF data_ipq.valid THEN
|
|
LET estado = 'VALIDO'
|
|
LET ret = 200
|
|
|
|
ELSE
|
|
LET ret = 400
|
|
LET estado = 'NO SE PUEDE ENTREGAR MENSAJE A ESTA DIRECCION EMAIL'
|
|
|
|
END IF
|
|
END IF
|
|
END IF
|
|
IF tiposervice = 'APILAYER' THEN
|
|
# DISPLAY " ESTOY EN APILAYER"
|
|
LET purl= api_url,pemail CLIPPED
|
|
CALL apilayer_send_command(purl, api_key) RETURNING ret, res
|
|
# DISPLAY "res ",res," ",ret
|
|
IF ret ==0 THEN
|
|
LET s = util.JSON.stringify(res)
|
|
CALL util.JSON.parse(res,data_apilayer)
|
|
|
|
IF data_apilayer.can_connect_smtp THEN
|
|
LET estado = 'VALIDO'
|
|
LET ret = 200
|
|
|
|
ELSE
|
|
LET ret = 400
|
|
LET estado = 'NO SE PUEDE ENTREGAR MENSAJE A ESTA DIRECCION EMAIL'
|
|
|
|
END IF
|
|
|
|
|
|
# DISPLAY "sintax ",data.syntax_valid," deliv ",data.is_deliverable
|
|
ELSE
|
|
IF ret = 429 THEN
|
|
LET ret = 200
|
|
LET estado ='VALIDO POR EXESO DE REQUEST'
|
|
ELSE
|
|
LET ret = 400
|
|
LET estado = 'NO FUE POSIBLE LA CONEXION, '
|
|
END IF
|
|
END IF
|
|
END IF
|
|
# LET ret = 200
|
|
DISPLAY "en return ",estado," ",ret
|
|
RETURN estado,ret
|
|
END FUNCTION
|
|
|
|
#
|
|
## Communications library using com (http or https)
|
|
#
|
|
|
|
FUNCTION apilayer_send_command(url, papikey)
|
|
|
|
DEFINE url, papikey STRING
|
|
DEFINE result, tmp STRING
|
|
DEFINE http_req com.HTTPRequest
|
|
DEFINE http_resp com.HTTPResponse
|
|
|
|
|
|
|
|
TRY
|
|
UPDATE apis SET cuenta = cuenta + 1 WHERE estado = 'ONLINE'
|
|
|
|
|
|
LET http_req = com.HTTPRequest.Create(url)
|
|
CALL http_req.setHeader("apikey",papikey)
|
|
|
|
CALL http_req.doRequest()
|
|
|
|
LET http_resp=http_req.getResponse()
|
|
|
|
IF http_resp.getStatusCode() != 200 THEN
|
|
RETURN http_resp.getStatusCode(), http_resp.getStatusDescription()
|
|
END IF
|
|
LET tmp = http_resp.getStatusDescription()
|
|
IF (tmp == "OK" || tmp == "no error" ) THEN
|
|
RETURN -1, SFMT("HTTP request status description: %1 ",tmp)
|
|
END IF
|
|
LET result = http_resp.getTextResponse()
|
|
CATCH
|
|
RETURN -2, SFMT("HTTP request error: STATUS=%1 (%2)",STATUS,SQLCA.sqlerrd[2])
|
|
END TRY
|
|
|
|
|
|
RETURN 0, result
|
|
|
|
END FUNCTION
|
|
|
|
FUNCTION ipquality_send_command(url, papikey)
|
|
|
|
DEFINE url, papikey STRING
|
|
DEFINE result, tmp STRING
|
|
DEFINE http_req com.HTTPRequest
|
|
DEFINE http_resp com.HTTPResponse
|
|
|
|
# NO SE MUESTRA LA URL: LLEVA LA API KEY
|
|
TRY
|
|
LET http_req = com.HTTPRequest.Create(url)
|
|
IF papikey IS NOT NULL THEN
|
|
CALL http_req.setHeader("apikey",papikey)
|
|
END IF
|
|
|
|
CALL http_req.doRequest()
|
|
|
|
LET http_resp=http_req.getResponse()
|
|
|
|
IF http_resp.getStatusCode() != 200 THEN
|
|
RETURN http_resp.getStatusCode(), http_resp.getStatusDescription()
|
|
END IF
|
|
LET tmp = http_resp.getStatusDescription()
|
|
IF (tmp == "OK" || tmp == "no error" ) THEN
|
|
RETURN -1, SFMT("HTTP request status description: %1 ",tmp)
|
|
END IF
|
|
LET result = http_resp.getTextResponse()
|
|
CATCH
|
|
RETURN -2, SFMT("HTTP request error: STATUS=%1 (%2)",STATUS,SQLCA.sqlerrd[2])
|
|
END TRY
|
|
|
|
|
|
RETURN 0, result
|
|
|
|
END FUNCTION
|
|
|
|
#
|
|
## VALIDACION PROPIA DEL EMAIL (14/10/2026)
|
|
## Formato basico + el dominio debe existir y recibir correo (registro MX, o A
|
|
## como MX implicito segun RFC 5321). Se consulta el DNS por HTTPS (dns.google)
|
|
## porque BDL no tiene resolvedor DNS.
|
|
#
|
|
|
|
FUNCTION email_formato_valido(pemail)
|
|
DEFINE pemail, usuario, dominio, etiqueta, c, permitidos STRING
|
|
DEFINE i, arroba, inicio INT
|
|
|
|
LET permitidos = "._%+-!#$&'*/=?^`{|}~"
|
|
|
|
IF pemail IS NULL OR pemail.getLength() > 254 THEN
|
|
RETURN FALSE
|
|
END IF
|
|
|
|
# UNA SOLA ARROBA, CON USUARIO Y DOMINIO
|
|
LET arroba = pemail.getIndexOf("@",1)
|
|
IF arroba <= 1 OR arroba = pemail.getLength() THEN
|
|
RETURN FALSE
|
|
END IF
|
|
IF pemail.getIndexOf("@",arroba + 1) > 0 THEN
|
|
RETURN FALSE
|
|
END IF
|
|
|
|
LET usuario = pemail.subString(1, arroba - 1)
|
|
LET dominio = pemail.subString(arroba + 1, pemail.getLength())
|
|
|
|
# USUARIO: MAX 64, SIN PUNTO AL INICIO/FINAL NI DOBLE PUNTO
|
|
IF usuario.getLength() > 64 THEN
|
|
RETURN FALSE
|
|
END IF
|
|
IF usuario.getCharAt(1) = "." OR usuario.getCharAt(usuario.getLength()) = "."
|
|
OR usuario.getIndexOf("..",1) > 0 THEN
|
|
RETURN FALSE
|
|
END IF
|
|
FOR i = 1 TO usuario.getLength()
|
|
LET c = usuario.getCharAt(i)
|
|
IF NOT (c MATCHES "[a-zA-Z0-9]" OR permitidos.getIndexOf(c,1) > 0) THEN
|
|
RETURN FALSE
|
|
END IF
|
|
END FOR
|
|
|
|
# DOMINIO: AL MENOS UN PUNTO, ETIQUETAS DE LETRAS/NUMEROS/GUION,
|
|
# SIN GUION AL INICIO/FINAL, Y EXTENSION (TLD) DE 2+ LETRAS
|
|
IF dominio.getLength() > 253 OR dominio.getIndexOf(".",1) = 0 THEN
|
|
RETURN FALSE
|
|
END IF
|
|
LET inicio = 1
|
|
FOR i = 1 TO dominio.getLength() + 1
|
|
IF i > dominio.getLength() OR dominio.getCharAt(i) = "." THEN
|
|
LET etiqueta = dominio.subString(inicio, i - 1)
|
|
IF etiqueta.getLength() = 0 OR etiqueta.getLength() > 63 THEN
|
|
RETURN FALSE
|
|
END IF
|
|
IF etiqueta.getCharAt(1) = "-" OR etiqueta.getCharAt(etiqueta.getLength()) = "-" THEN
|
|
RETURN FALSE
|
|
END IF
|
|
LET inicio = i + 1
|
|
ELSE
|
|
LET c = dominio.getCharAt(i)
|
|
IF NOT (c MATCHES "[a-zA-Z0-9]" OR c = "-") THEN
|
|
RETURN FALSE
|
|
END IF
|
|
END IF
|
|
END FOR
|
|
# ETIQUETA FINAL = TLD
|
|
IF etiqueta.getLength() < 2 THEN
|
|
RETURN FALSE
|
|
END IF
|
|
FOR i = 1 TO etiqueta.getLength()
|
|
IF NOT (etiqueta.getCharAt(i) MATCHES "[a-zA-Z]") THEN
|
|
RETURN FALSE
|
|
END IF
|
|
END FOR
|
|
|
|
RETURN TRUE
|
|
END FUNCTION
|
|
|
|
# 1 = EL DOMINIO RECIBE CORREO, 0 = NO EXISTE / SIN MX NI A, -1 = NO SE PUDO CONSULTAR
|
|
FUNCTION email_dominio_dns(pdominio)
|
|
DEFINE pdominio STRING
|
|
DEFINE r SMALLINT
|
|
|
|
LET r = email_dns_consulta(pdominio, "MX")
|
|
IF r <> 0 THEN
|
|
RETURN r
|
|
END IF
|
|
# SIN MX: EL REGISTRO A FUNCIONA COMO MX IMPLICITO
|
|
RETURN email_dns_consulta(pdominio, "A")
|
|
END FUNCTION
|
|
|
|
FUNCTION email_dns_consulta(pdominio, ptipo)
|
|
DEFINE pdominio, ptipo, res, dato STRING
|
|
DEFINE http_req com.HTTPRequest
|
|
DEFINE http_resp com.HTTPResponse
|
|
DEFINE obj, rr util.JSONObject
|
|
DEFINE answers util.JSONArray
|
|
DEFINE i, dns_status, tipo_rr, tipo_buscado INT
|
|
|
|
IF ptipo = "MX" THEN
|
|
LET tipo_buscado = 15
|
|
ELSE
|
|
LET tipo_buscado = 1
|
|
END IF
|
|
|
|
TRY
|
|
LET http_req = com.HTTPRequest.Create(
|
|
SFMT("https://dns.google/resolve?name=%1&type=%2", pdominio.toLowerCase(), ptipo))
|
|
CALL http_req.setTimeOut(10)
|
|
CALL http_req.setHeader("Accept","application/dns-json")
|
|
CALL http_req.doRequest()
|
|
LET http_resp = http_req.getResponse()
|
|
IF http_resp.getStatusCode() != 200 THEN
|
|
RETURN -1
|
|
END IF
|
|
LET res = http_resp.getTextResponse()
|
|
LET obj = util.JSONObject.parse(res)
|
|
LET dns_status = obj.get("Status")
|
|
CATCH
|
|
DISPLAY "email_dns_consulta error: ",STATUS
|
|
RETURN -1
|
|
END TRY
|
|
|
|
# 3 = NXDOMAIN (EL DOMINIO NO EXISTE); OTRO <> 0 = FALLA DEL DNS
|
|
IF dns_status = 3 THEN
|
|
RETURN 0
|
|
END IF
|
|
IF dns_status <> 0 THEN
|
|
RETURN -1
|
|
END IF
|
|
IF NOT obj.has("Answer") THEN
|
|
RETURN 0
|
|
END IF
|
|
IF obj.getType("Answer") <> "ARRAY" THEN
|
|
RETURN 0
|
|
END IF
|
|
|
|
LET answers = obj.get("Answer")
|
|
FOR i = 1 TO answers.getLength()
|
|
LET rr = answers.get(i)
|
|
LET tipo_rr = rr.get("type")
|
|
LET dato = rr.get("data")
|
|
IF tipo_rr = tipo_buscado THEN
|
|
# "0 ." ES UN NULL MX (RFC 7505): EL DOMINIO DECLARA QUE NO RECIBE CORREO
|
|
IF tipo_buscado = 15 AND dato.trim() MATCHES "* ." THEN
|
|
CONTINUE FOR
|
|
END IF
|
|
RETURN 1
|
|
END IF
|
|
END FOR
|
|
|
|
RETURN 0
|
|
END FUNCTION
|
|
|
|
FUNCTION email_resultado_dns(dns_estado)
|
|
DEFINE dns_estado SMALLINT
|
|
|
|
CASE dns_estado
|
|
WHEN 1
|
|
RETURN 'VALIDO', 200
|
|
WHEN 0
|
|
RETURN 'EL DOMINIO DEL EMAIL NO EXISTE O NO RECIBE CORREO', 400
|
|
OTHERWISE
|
|
RETURN 'NO FUE POSIBLE VERIFICAR EL EMAIL, INTENTE DE NUEVO', 400
|
|
END CASE
|
|
END FUNCTION
|