]>
jfr.im git - irc/evilnet/x3.git/blob - src/mod-blacklist.c
1 /* Blacklist module for srvx 1.x
2 * Copyright 2007 Michael Poole <mdpoole@troilus.org>
4 * This file is part of srvx.
6 * srvx is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
27 const char * blacklist_module_deps
[] = { NULL
};
30 struct string_list reasons
;
31 const char * description
;
33 unsigned int duration
;
35 unsigned int debug
: 1 ;
40 char client_ip
[ IRC_NTOP_MAX_SIZE
];
44 static struct log_type
* bl_log
;
45 static dict_t blacklist_zones
; /* contains struct dnsbl_zone */
46 static dict_t blacklist_hosts
; /* maps IPs or hostnames to reasons from blacklist_reasons */
47 static dict_t blacklist_reasons
; /* maps strings to themselves (poor man's data sharing) */
50 struct userNode
* debug_bot
;
51 struct chanNode
* debug_channel
;
52 unsigned long gline_duration
;
55 #if defined(GCC_VARMACROS)
56 # define blacklist_debug(ARGS...) do { if (conf.debug_bot && conf.debug_channel) send_channel_notice(conf.debug_channel, conf.debug_bot, ARGS); } while (0)
57 #elif defined(C99_VARMACROS)
58 # define blacklist_debug(...) do { if (conf.debug_bot && conf.debug_channel) send_channel_notice(conf.debug_channel, conf.debug_bot, __VA_ARGS__); } while (0)
62 do_expandos ( char * output
, unsigned int out_len
, const char * input
, ...)
72 safestrncpy ( output
, input
, out_len
);
73 va_start ( args
, input
);
74 while (( key
= va_arg ( args
, const char *)) != NULL
) {
75 datum
= va_arg ( args
, const char *);
78 for ( found
= output
; ( found
= strstr ( output
, key
)) != NULL
; found
+= dlen
) {
79 rlen
= strlen ( found
+ klen
);
80 if (( dlen
> klen
) && (( unsigned )( found
+ dlen
+ rlen
- output
) > out_len
))
81 rlen
= output
+ out_len
- found
- dlen
;
82 memmove ( found
+ dlen
, found
+ klen
, rlen
);
83 memcpy ( found
, datum
, dlen
+ 1 );
90 dnsbl_hit ( struct sar_request
* req
, struct dns_header
* hdr
, struct dns_rr
* rr
, unsigned char * raw
, unsigned int raw_size
)
92 struct dnsbl_data
* data
;
93 struct dnsbl_zone
* zone
;
101 char target
[ IRC_NTOP_MAX_SIZE
+ 2 ];
103 /* Get the DNSBL zone (to make sure it has not disappeared in a rehash). */
104 data
= ( struct dnsbl_data
*)( req
+ 1 );
105 zone
= dict_find ( blacklist_zones
, data
-> zone_name
, NULL
);
109 /* Scan the results. */
110 for ( mask
= 0 , ii
= 0 , txt
= NULL
; ii
< hdr
-> ancount
; ++ ii
) {
111 pos
= rr
[ ii
]. rd_start
;
112 switch ( rr
[ ii
]. type
) {
114 if ( rr
[ ii
]. rdlength
!= 4 )
116 if ( pos
+ 3 < raw_size
)
117 mask
|= ( 1 << raw
[ pos
+ 3 ]);
121 txt
= malloc ( len
+ 1 );
122 memcpy ( txt
, raw
+ pos
+ 1 , len
);
128 /* Do we care about one of the masks we found? */
129 if ( mask
& zone
-> mask
) {
130 /* See if a per-result message was provided. */
131 for ( ii
= 0 , message
= NULL
; mask
&& ( ii
< zone
-> reasons
. used
); ++ ii
, mask
>>= 1 ) {
134 if ( NULL
!= ( message
= zone
-> reasons
. list
[ ii
]))
138 /* If not, use a standard fallback. */
139 if ( message
== NULL
) {
140 message
= zone
-> reason
;
142 message
= "client is blacklisted" ;
145 /* Expand elements of the message as necessary. */
146 do_expandos ( reason
, sizeof ( reason
), message
, " %t xt%" , ( txt
? txt
: "(no-txt)" ), " %i p%" , data
-> client_ip
, NULL
);
149 blacklist_debug ( "DNSBL match: [ %s ] %s ( %s )" , zone
-> zone
, data
-> client_ip
, reason
);
151 /* Now generate the G-line. */
154 strcpy ( target
+ 2 , data
-> client_ip
);
155 gline_add ( self
-> name
, target
, zone
-> duration
, reason
, now
, 1 , 0 );
162 blacklist_check_user ( struct userNode
* user
)
164 static const char * hexdigits
= "0123456789abcdef" ;
168 unsigned int dnsbl_len
;
170 char ip
[ IRC_NTOP_MAX_SIZE
];
171 char dnsbl_target
[ 128 ];
173 /* Users added during burst should not be checked. */
174 if ( user
-> uplink
-> burst
)
177 /* Users with bogus IPs are probably service bots. */
178 if (! irc_in_addr_is_valid ( user
-> ip
))
181 /* Check local file-based blacklist. */
182 irc_ntop ( ip
, sizeof ( ip
), & user
-> ip
);
183 reason
= dict_find ( blacklist_hosts
, host
= ip
, NULL
);
184 if ( reason
== NULL
) {
185 reason
= dict_find ( blacklist_hosts
, host
= user
-> hostname
, NULL
);
187 if ( reason
!= NULL
) {
189 target
= alloca ( strlen ( host
) + 3 );
192 strcpy ( target
+ 2 , host
);
193 gline_add ( self
-> name
, target
, conf
. gline_duration
, reason
, now
, 1 , 0 );
196 /* Figure out the base part of a DNS blacklist hostname. */
197 if ( irc_in_addr_is_ipv4 ( user
-> ip
)) {
198 dnsbl_len
= snprintf ( dnsbl_target
, sizeof ( dnsbl_target
), " %d . %d . %d . %d ." , user
-> ip
. in6_8
[ 15 ], user
-> ip
. in6_8
[ 14 ], user
-> ip
. in6_8
[ 13 ], user
-> ip
. in6_8
[ 12 ]);
199 } else if ( irc_in_addr_is_ipv6 ( user
-> ip
)) {
200 for ( ii
= 0 ; ii
< 16 ; ++ ii
) {
201 dnsbl_target
[ ii
* 4 + 0 ] = hexdigits
[ user
-> ip
. in6_8
[ 15 - ii
] & 15 ];
202 dnsbl_target
[ ii
* 4 + 1 ] = '.' ;
203 dnsbl_target
[ ii
* 4 + 2 ] = hexdigits
[ user
-> ip
. in6_8
[ 15 - ii
] >> 4 ];
204 dnsbl_target
[ ii
* 4 + 3 ] = '.' ;
211 /* Start a lookup for the appropriate hostname in each DNSBL. */
212 for ( it
= dict_first ( blacklist_zones
); it
; it
= iter_next ( it
)) {
213 struct dnsbl_data
* data
;
214 struct sar_request
* req
;
218 safestrncpy ( dnsbl_target
+ dnsbl_len
, zone
, sizeof ( dnsbl_target
) - dnsbl_len
);
219 req
= sar_request_simple ( sizeof (* data
) + strlen ( zone
), dnsbl_hit
, NULL
, dnsbl_target
, REQ_QTYPE_ALL
, NULL
);
221 data
= ( struct dnsbl_data
*)( req
+ 1 );
222 strcpy ( data
-> client_ip
, ip
);
223 strcpy ( data
-> zone_name
, zone
);
231 blacklist_load_file ( const char * filename
, const char * default_reason
)
238 char linebuf
[ MAXLEN
];
243 default_reason
= "client is blacklisted" ;
244 file
= fopen ( filename
, "r" );
246 log_module ( bl_log
, LOG_ERROR
, "Unable to open %s for reading: %s " , filename
, strerror ( errno
));
249 log_module ( bl_log
, LOG_DEBUG
, "Loading blacklist from %s ." , filename
);
250 while ( fgets ( linebuf
, sizeof ( linebuf
), file
)) {
251 /* Trim whitespace from end of line. */
252 len
= strlen ( linebuf
);
253 while ( isspace ( linebuf
[ len
- 1 ]))
254 linebuf
[-- len
] = '\0' ;
256 /* Figure out which reason string we should use. */
257 reason
= default_reason
;
258 sep
= strchr ( linebuf
, ' ' );
261 while ( isspace (* sep
))
267 /* See if the reason string is already known. */
268 mapped_reason
= dict_find ( blacklist_reasons
, reason
, NULL
);
269 if (! mapped_reason
) {
270 mapped_reason
= strdup ( reason
);
271 dict_insert ( blacklist_reasons
, mapped_reason
, ( char *) mapped_reason
);
274 /* Store the blacklist entry. */
275 dict_insert ( blacklist_hosts
, strdup ( linebuf
), mapped_reason
);
281 dnsbl_zone_free ( void * pointer
)
283 struct dnsbl_zone
* zone
;
285 free ( zone
-> reasons
. list
);
290 blacklist_conf_read ( void )
297 dict_delete ( blacklist_zones
);
298 blacklist_zones
= dict_new ();
299 dict_set_free_data ( blacklist_zones
, dnsbl_zone_free
);
301 dict_delete ( blacklist_hosts
);
302 blacklist_hosts
= dict_new ();
303 dict_set_free_keys ( blacklist_hosts
, free
);
305 dict_delete ( blacklist_reasons
);
306 blacklist_reasons
= dict_new ();
307 dict_set_free_keys ( blacklist_reasons
, free
);
309 node
= conf_get_data ( "modules/blacklist" , RECDB_OBJECT
);
313 str1
= database_get_data ( node
, "debug_bot" , RECDB_QSTRING
);
315 conf
. debug_bot
= GetUserH ( str1
);
317 str1
= database_get_data ( node
, "debug_channel" , RECDB_QSTRING
);
318 if ( conf
. debug_bot
&& str1
) {
319 str2
= database_get_data ( node
, "debug_channel_modes" , RECDB_QSTRING
);
322 conf
. debug_channel
= AddChannel ( str1
, now
, str2
, NULL
, NULL
);
323 AddChannelUser ( conf
. debug_bot
, conf
. debug_channel
)-> modes
|= MODE_CHANOP
;
325 conf
. debug_channel
= NULL
;
328 str1
= database_get_data ( node
, "file" , RECDB_QSTRING
);
329 str2
= database_get_data ( node
, "file_reason" , RECDB_QSTRING
);
330 blacklist_load_file ( str1
, str2
);
332 str1
= database_get_data ( node
, "gline_duration" , RECDB_QSTRING
);
335 conf
. gline_duration
= ParseInterval ( str1
);
337 subnode
= database_get_data ( node
, "dnsbl" , RECDB_OBJECT
);
339 static const char * reason_prefix
= "reason_" ;
340 static const unsigned int max_id
= 255 ;
341 struct dnsbl_zone
* zone
;
347 for ( it
= dict_first ( subnode
); it
; it
= iter_next ( it
)) {
348 dnsbl
= GET_RECORD_OBJECT (( struct record_data
*) iter_data ( it
));
352 zone
= malloc ( sizeof (* zone
) + strlen ( iter_key ( it
)));
353 strcpy ( zone
-> zone
, iter_key ( it
));
354 zone
-> description
= database_get_data ( dnsbl
, "description" , RECDB_QSTRING
);
355 zone
-> reason
= database_get_data ( dnsbl
, "reason" , RECDB_QSTRING
);
356 str1
= database_get_data ( dnsbl
, "duration" , RECDB_QSTRING
);
357 zone
-> duration
= str1
? ParseInterval ( str1
) : 3600 ;
358 str1
= database_get_data ( dnsbl
, "mask" , RECDB_QSTRING
);
359 zone
-> mask
= str1
? strtoul ( str1
, NULL
, 0 ) : ~ 0u ;
360 str1
= database_get_data ( dnsbl
, "debug" , RECDB_QSTRING
);
361 zone
-> debug
= str1
? enabled_string ( str1
) : 0 ;
362 zone
-> reasons
. used
= 0 ;
363 zone
-> reasons
. size
= 0 ;
364 zone
-> reasons
. list
= NULL
;
365 dict_insert ( blacklist_zones
, zone
-> zone
, zone
);
367 for ( it2
= dict_first ( dnsbl
); it2
; it2
= iter_next ( it2
)) {
368 str1
= GET_RECORD_QSTRING (( struct record_data
*)( iter_data ( it2
)));
369 if (! str1
|| memcmp ( iter_key ( it2
), reason_prefix
, strlen ( reason_prefix
)))
371 id
= strtoul ( iter_key ( it2
) + strlen ( reason_prefix
), NULL
, 0 );
373 log_module ( bl_log
, LOG_ERROR
, "Invalid code for DNSBL %s %s -- only %d responses supported." , iter_key ( it
), iter_key ( it2
), max_id
);
376 if ( zone
-> reasons
. size
< id
+ 1 ) {
377 zone
-> reasons
. size
= id
+ 1 ;
378 zone
-> reasons
. list
= realloc ( zone
-> reasons
. list
, zone
-> reasons
. size
* sizeof ( zone
-> reasons
. list
[ 0 ]));
380 zone
-> reasons
. list
[ id
] = ( char *) str1
;
381 if ( zone
-> reasons
. used
< id
+ 1 )
382 zone
-> reasons
. used
= id
+ 1 ;
389 blacklist_cleanup ( void )
391 dict_delete ( blacklist_zones
);
392 dict_delete ( blacklist_hosts
);
393 dict_delete ( blacklist_reasons
);
399 bl_log
= log_register_type ( "blacklist" , "file:blacklist.log" );
400 conf_register_reload ( blacklist_conf_read
);
401 reg_new_user_func ( blacklist_check_user
);
402 reg_exit_func ( blacklist_cleanup
);
407 blacklist_finalize ( void )