packagedatabaseimport("encoding/binary""fmt""net""slices""strconv""strings""time")funcHasGeoFields(queryEventQuery)bool{returnlen(query.ASNs)>0||len(query.Countries)>0||len(query.Cities)>0||len(query.Domains)>0||len(query.FQDNs)>0||slices.Contains(query.Columns,"country")||slices.Contains(query.Columns,"city")||slices.Contains(query.Columns,"latitude")||slices.Contains(query.Columns,"longitude")}funcbuildQueryString(queryEventQuery,totalbool)(string,[]any){hasGeo:=HasGeoFields(query)queryString:=buildSelectClause(query,total,hasGeo)whereClauses,whereArgs:=buildWhereClauses(query,hasGeo)iflen(whereClauses)>0{queryString+=" WHERE "+strings.Join(whereClauses," AND ")}if!total{queryString+=buildPaginationClause(query)}returnqueryString,whereArgs}funcbuildSelectClause(queryEventQuery,totalbool,hasGeobool)string{iftotal{ifhasGeo{return"SELECT COUNT(*) FROM honeypot_events JOIN ips ON honeypot_events.remote_addr = ips.ip"}return"SELECT COUNT(*) FROM honeypot_events"}allowedColumns:=[]string{"id","time","type","event","remote_addr","remote_port","dst_port","fields","country","city","latitude","longitude"}validColumns:=make([]string,0,len(query.Columns))for_,col:=rangequery.Columns{ifslices.Contains(allowedColumns,col){validColumns=append(validColumns,col)}}columns:=strings.Join(validColumns,", ")ifcolumns==""{ifhasGeo{columns="honeypot_events.id, honeypot_events.time, honeypot_events.type, honeypot_events.event, honeypot_events.remote_addr, honeypot_events.remote_port, honeypot_events.dst_port, honeypot_events.fields, ips.country, ips.city, ips.latitude, ips.longitude"}else{columns="id, time, type, event, remote_addr, remote_port, dst_port, fields"}}ifhasGeo{return"SELECT "+columns+" FROM honeypot_events JOIN ips ON honeypot_events.remote_addr = ips.ip"}return"SELECT "+columns+" FROM honeypot_events"}funcbuildWhereClauses(queryEventQuery,hasGeobool)([]string,[]any){clauses:=make([]string,0)args:=make([]any,0)idField:="id"typeField:="type"eventField:="event"remoteAddrField:="remote_addr"remotePortField:="remote_port"dstPortField:="dst_port"ifhasGeo{idField="honeypot_events.id"typeField="honeypot_events.type"eventField="honeypot_events.event"remoteAddrField="honeypot_events.remote_addr"remotePortField="honeypot_events.remote_port"dstPortField="honeypot_events.dst_port"}// Time range filteringaddTimeRangeClauses(&clauses,&args,query.TimeStart,query.TimeEnd)// IN/NOT IN filteringaddInNotInClauses(&clauses,&args,idField,query.IDs)addAddrClauses(&clauses,&args,query.RemoteAddrs,remoteAddrField)addPortClauses(&clauses,&args,query.RemotePorts,remotePortField)addPortClauses(&clauses,&args,query.DstPorts,dstPortField)addInNotInClauses(&clauses,&args,typeField,query.Type)addInNotInClauses(&clauses,&args,eventField,query.Event)// Geo filteringaddInNotInClauses(&clauses,&args,"ips.asn",query.ASNs)addInNotInClauses(&clauses,&args,"ips.country",query.Countries)addInNotInClauses(&clauses,&args,"ips.city",query.Cities)addInNotInClauses(&clauses,&args,"ips.domain",query.Domains)addInNotInClauses(&clauses,&args,"ips.fqdn",query.FQDNs)// JSON field filteringaddJSONFieldClauses(&clauses,&args,query.FieldFilters)addJSONFieldExistsClauses(&clauses,&args,query.FieldExists)returnclauses,args}funcaddInNotInClauses(clauses*[]string,args*[]any,fieldstring,values[]string){iflen(values)==0{return}varinVals,notInVals[]stringvarinGlobVals,notInGlobVals[]stringfor_,v:=rangevalues{exclude:=falseifstrings.HasPrefix(v,"!")&&len(v)>1{exclude=truev=v[1:]}hasWildcard:=strings.ContainsAny(v,"*?")ifexclude{ifhasWildcard{notInGlobVals=append(notInGlobVals,v)}else{notInVals=append(notInVals,v)}}else{ifhasWildcard{inGlobVals=append(inGlobVals,v)}else{inVals=append(inVals,v)}}}iflen(inVals)>0||len(inGlobVals)>0{varparts[]stringiflen(inVals)>0{placeholders:=strings.TrimRight(strings.Repeat("?, ",len(inVals)),", ")parts=append(parts,fmt.Sprintf("%s IN (%s)",field,placeholders))*args=append(*args,sliceToAny(inVals)...)}for_,g:=rangeinGlobVals{parts=append(parts,fmt.Sprintf("%s GLOB ?",field))*args=append(*args,g)}iflen(parts)>1{*clauses=append(*clauses,"("+strings.Join(parts," OR ")+")")}else{*clauses=append(*clauses,parts[0])}}iflen(notInVals)>0||len(notInGlobVals)>0{iflen(notInVals)>0{placeholders:=strings.TrimRight(strings.Repeat("?, ",len(notInVals)),", ")*clauses=append(*clauses,fmt.Sprintf("%s NOT IN (%s)",field,placeholders))*args=append(*args,sliceToAny(notInVals)...)}for_,g:=rangenotInGlobVals{*clauses=append(*clauses,fmt.Sprintf("%s NOT GLOB ?",field))*args=append(*args,g)}}}funcaddAddrClauses(clauses*[]string,args*[]any,values[]string,fieldstring){iflen(values)==0{return}varinParts[]stringvarnotInParts[]stringvarinArgs[]anyvarnotInArgs[]anyfor_,v:=rangevalues{exclude:=falseifstrings.HasPrefix(v,"!")&&len(v)>1{exclude=truev=v[1:]}ifstrings.Contains(v,"/"){// Subnet matching_,ipNet,err:=net.ParseCIDR(v)iferr==nil&&ipNet.IP.To4()!=nil{// IPv4 Subnetmask:=binary.BigEndian.Uint32(ipNet.Mask)start:=binary.BigEndian.Uint32(ipNet.IP.To4())end:=start|^maskintField:=field+"_int"iffield=="remote_addr"||field=="honeypot_events.remote_addr"{intField="remote_ip_int"iffield=="honeypot_events.remote_addr"{intField="honeypot_events.remote_ip_int"}}ifexclude{notInParts=append(notInParts,fmt.Sprintf("NOT (%s >= ? AND %s <= ?)",intField,intField))notInArgs=append(notInArgs,start,end)}else{inParts=append(inParts,fmt.Sprintf("(%s >= ? AND %s <= ?)",intField,intField))inArgs=append(inArgs,start,end)}}else{// Fallback to string matching for IPv6 or if parsing failedifexclude{notInParts=append(notInParts,fmt.Sprintf("NOT %s::INET <<= ?::INET",field))notInArgs=append(notInArgs,v)}else{inParts=append(inParts,fmt.Sprintf("%s::INET <<= ?::INET",field))inArgs=append(inArgs,v)}}}else{// Single addressip:=net.ParseIP(v)ifip!=nil&&ip.To4()!=nil{// IPv4 addressipInt:=binary.BigEndian.Uint32(ip.To4())intField:=field+"_int"iffield=="remote_addr"||field=="honeypot_events.remote_addr"{intField="remote_ip_int"iffield=="honeypot_events.remote_addr"{intField="honeypot_events.remote_ip_int"}}ifexclude{notInParts=append(notInParts,fmt.Sprintf("%s != ?",intField))notInArgs=append(notInArgs,ipInt)}else{inParts=append(inParts,fmt.Sprintf("%s = ?",intField))inArgs=append(inArgs,ipInt)}}else{// Fallback to string matching for IPv6 or if parsing failedifexclude{notInParts=append(notInParts,fmt.Sprintf("%s != ?",field))notInArgs=append(notInArgs,v)}else{inParts=append(inParts,fmt.Sprintf("%s = ?",field))inArgs=append(inArgs,v)}}}}iflen(inParts)>0{*clauses=append(*clauses,"("+strings.Join(inParts," OR ")+")")*args=append(*args,inArgs...)}iflen(notInParts)>0{*clauses=append(*clauses,"("+strings.Join(notInParts," AND ")+")")*args=append(*args,notInArgs...)}}funcaddPortClauses(clauses*[]string,args*[]any,values[]string,fieldstring){iflen(values)==0{return}varinParts[]stringvarnotInParts[]stringvarinArgs[]anyvarnotInArgs[]anyfor_,v:=rangevalues{exclude:=falseifstrings.HasPrefix(v,"!")&&len(v)>1{exclude=truev=v[1:]}ifstrings.Contains(v,"-"){parts:=strings.Split(v,"-")iflen(parts)==2{start,err1:=strconv.Atoi(strings.TrimSpace(parts[0]))end,err2:=strconv.Atoi(strings.TrimSpace(parts[1]))iferr1==nil&&err2==nil{ifexclude{notInParts=append(notInParts,fmt.Sprintf("%s NOT BETWEEN ? AND ?",field))notInArgs=append(notInArgs,start,end)}else{inParts=append(inParts,fmt.Sprintf("%s BETWEEN ? AND ?",field))inArgs=append(inArgs,start,end)}continue}}}// Single portport,err:=strconv.Atoi(strings.TrimSpace(v))iferr==nil{ifexclude{notInParts=append(notInParts,fmt.Sprintf("%s != ?",field))notInArgs=append(notInArgs,port)}else{inParts=append(inParts,fmt.Sprintf("%s = ?",field))inArgs=append(inArgs,port)}}}iflen(inParts)>0{*clauses=append(*clauses,"("+strings.Join(inParts," OR ")+")")*args=append(*args,inArgs...)}iflen(notInParts)>0{*clauses=append(*clauses,"("+strings.Join(notInParts," AND ")+")")*args=append(*args,notInArgs...)}}funcaddTimeRangeClauses(clauses*[]string,args*[]any,timeStart,timeEndtime.Time){if!timeStart.IsZero(){*clauses=append(*clauses,"time >= ?")*args=append(*args,timeStart.Format(time.RFC3339))}if!timeEnd.IsZero(){*clauses=append(*clauses,"time <= ?")*args=append(*args,timeEnd.Format(time.RFC3339))}}funcaddJSONFieldClauses(clauses*[]string,args*[]any,fieldFiltersmap[string][]string){forkey,values:=rangefieldFilters{varinVals,notInVals[]stringvarinGlobVals,notInGlobVals[]stringfor_,v:=rangevalues{exclude:=falseifstrings.HasPrefix(v,"!")&&len(v)>1{exclude=truev=v[1:]}hasWildcard:=strings.ContainsAny(v,"*?")ifexclude{ifhasWildcard{notInGlobVals=append(notInGlobVals,v)}else{notInVals=append(notInVals,v)}}else{ifhasWildcard{inGlobVals=append(inGlobVals,v)}else{inVals=append(inVals,v)}}}jsonPath:=fmt.Sprintf("$.%s",key)iflen(inVals)>0||len(inGlobVals)>0{varparts[]stringiflen(inVals)>0{placeholders:=strings.TrimRight(strings.Repeat("?, ",len(inVals)),", ")parts=append(parts,fmt.Sprintf("json_extract_string(fields, ?) IN (%s)",placeholders))*args=append(*args,jsonPath)*args=append(*args,sliceToAny(inVals)...)}for_,g:=rangeinGlobVals{parts=append(parts,"json_extract_string(fields, ?) GLOB ?")*args=append(*args,jsonPath,g)}iflen(parts)>1{*clauses=append(*clauses,"("+strings.Join(parts," OR ")+")")}else{*clauses=append(*clauses,parts[0])}}iflen(notInVals)>0||len(notInGlobVals)>0{iflen(notInVals)>0{placeholders:=strings.TrimRight(strings.Repeat("?, ",len(notInVals)),", ")*clauses=append(*clauses,fmt.Sprintf("json_extract_string(fields, ?) NOT IN (%s)",placeholders))*args=append(*args,jsonPath)*args=append(*args,sliceToAny(notInVals)...)}for_,g:=rangenotInGlobVals{*clauses=append(*clauses,"json_extract_string(fields, ?) NOT GLOB ?")*args=append(*args,jsonPath,g)}}}}funcaddJSONFieldExistsClauses(clauses*[]string,args*[]any,fieldExists[]string){for_,key:=rangefieldExists{*clauses=append(*clauses,"json_extract(fields, ?) IS NOT NULL")*args=append(*args,fmt.Sprintf("$.%s",key))}}funcbuildPaginationClause(queryEventQuery)string{varparts[]stringifquery.OrderDirection!=""{parts=append(parts,"ORDER BY time "+query.OrderDirection)}ifquery.Limit>0{parts=append(parts,"LIMIT "+strconv.Itoa(query.Limit))}ifquery.Offset>0{parts=append(parts,"OFFSET "+strconv.Itoa(query.Offset))}iflen(parts)==0{return""}return" "+strings.Join(parts," ")}funcsliceToAny(s[]string)[]any{result:=make([]any,len(s))fori,v:=ranges{result[i]=v}returnresult}