Hi guys,

I managed to do what I wanted. I am just writing back to report the results of my endeavors ;-)

The problem we had - an installation of Trac 0.8.4 with world wide
ticket create & change permissions granted. We got a heavy spam attack -
many tickets generated pure spam, and many spam changes done to the
current database - more than 12000 of them.

With some programming in Python and sqlite's SQL dialect, I managed to
fix things. As mentioned previously in this thread - there is already a
plugin to prevent spam and another to undo spammers' changes. However
they demand a higher version of Trac + they are suited to the occasional
problem, not a 12000 spammers' changes disaster.

The solution given below works more or less fine for the purposes outlined above.

Drawbacks:
1. Code quality sucks - sorry, I am very new to Python.
2. Things are very unoptimized. I suppose that some indexes can improve the execution time. However - a faster disk might turn out to be a better overall solution. 3. Trac tickets' change workflow is not kept. This means: there are changes in the fields of a ticket that are impossible to be done via the web interface together, but are done via the scripts. However - the strictness of the current.new_value=future.old_value in the table ticket_change is kept where necessary (i.e. it is not kept for comments which always have old_value=''). 4. The code depends on pysqlite2, there are no plans to make it work with pysqlite 1.x.

OK. Here goes my solution:
1. First we determine the pure spam tickets - these are tickets that started their lives as spam. They will be deleted and regenerated with completely new history. We do this with prepare.sql. The numbers in the where clauses in SQL are the numbers of the spam tickets. 2. We generate a new table - called LARGE based on TICKET and TICKET_CHANGE. It corresponds to all states of the tickets. We do this with the script: expander.py 3. We start deleting those states of the tickets that we consider spam. One example of this is given in deleter.sql. Naturally - your deleter.sql can be very different. 4. Based on LARGE we generate NEW_TICKET & NEW_TICKET_CHANGE - which are analogues of TICKET & TICKET_CHANGE with the script: shrinker.py 5. We fix TICKET & TICKET_CHANGE and the expansion of the database file via the script collect.sql.

After all of this we get happy and reach nirvana. We hate the spammers that stole our precious[1].

Kind regards:
al_shopov

[1] time
delete from large where author!='anonymous' and author in
(
	select distinct author from large where
	comment glob '*coachbrand*' or
	comment glob '*diorbrand*' or
	comment glob '*bizland*' or
	comment glob '*balearweb*' or
	comment glob '*furniture.int.pl*'  or
	comment glob '*virtualave*' or
	comment glob '*lacomunitat*'     or
	comment glob '*freett*'      or
	comment glob '*blog.gala.net*' or
	comment glob '*quick-adult-links.com*' or
	comment glob '*ringtones*'  or
	comment glob '*myrolex*' or
	comment glob '*bitacoras*'   or
	comment glob '*viagra*'    or
	comment glob '*xanax*'
);

delete from large where 
	comment glob '*coachbrand*' or
	comment glob '*diorbrand*' or
	comment glob '*bizland*' or
	comment glob '*balearweb*' or
	comment glob '*furniture.int.pl*'  or
	comment glob '*virtualave*' or
	comment glob '*lacomunitat*'     or
	comment glob '*freett*'      or
	comment glob '*blog.gala.net*' or
	comment glob '*quick-adult-links.com*' or
	comment glob '*ringtones*'  or
	comment glob '*myrolex*' or
	comment glob '*bitacoras*'   or
	comment glob '*viagra*'    or
	comment glob '*xanax*' or
	comment glob '*hcy0515*' or
	comment glob '*jipiao365*' or
	comment glob '*LED*' or
	comment glob '*World of Warcraft*' or
	comment glob '*www.bogoo.da.ru*' or
	comment glob '*insurance.mpage.jp*' or
	comment glob '*www.hot.es.pn*' or
	comment glob '*www.vipp2p.ch.gg*' or
	comment glob '*www.divx.cn.pn*' or
	comment glob '*p2p.au.ms*' or
	comment glob '*www.joa.hk.ms*'
;


delete from large where id=319 and changetime=1149408700;
delete from large where id=321 and changetime=1148984261;
#!/usr/bin/python
# -*- coding: utf-8 -*-
from pysqlite2 import dbapi2 as sqlite

#import apis
#connect & get cursor
con = sqlite.connect("/home/ash/gtp-clean/db/trac.db")
cur = con.cursor()

# try to drop the tab, if not - pаss
try:
    cur.execute ("drop table large")
except sqlite.OperationalError:
    pass

try:
    cur.execute ("drop table new_ticket_change")
except sqlite.OperationalError:
    pass

try:
    cur.execute ("drop table new_ticket")
except sqlite.OperationalError:
    pass



# create da temp table
LARGE="""CREATE TABLE large (
        id              integer,  -- PRIMARY KEY,
        time            integer,        -- the time it was created
        changetime      integer,
        component       text,
        severity        text,
        priority        text,
        owner           text,           -- who is this ticket assigned to
        reporter        text,
        cc              text,           -- email addresses to notify
        url             text,           -- url related to this ticket
        version         text,           --
        milestone       text,           --
        status          text,
        resolution      text,
        summary         text,           -- one-line summary
        description     text,           -- problem description (long)
        keywords        text,
        comment         text,
        author          text
);
"""

cur.execute (LARGE)

con.commit()

# create new_ticket_change
NTC="""CREATE TABLE new_ticket_change (
      ticket          integer,
      time            integer,
      author          text,
      field           text,
      oldvalue        text,
      newvalue        text
);
"""                                                        

cur.execute (NTC)

con.commit()

NT="""CREATE TABLE new_ticket (
     id              integer ,
     time            integer,        -- the time it was created
     changetime      integer,
     component       text,
     severity        text,
     priority        text,
     owner           text,           -- who is this ticket assigned to
     reporter        text,
     cc              text,           -- email addresses to notify
     url             text,           -- url related to this ticket
     version         text,           --
     milestone       text,           --
     status          text,
     resolution      text,
     summary         text,           -- one-line summary
     description     text,           -- problem description (long)
     keywords        text
);
"""

cur.execute (NT)

con.commit()
                                                        

# initail populating with most current state of tickets
cur.execute ("insert into large select t.*, '', '' from ticket t")
#make all of the rows special
cur.execute ("update large set changetime=0")
con.commit()


#get the ticket numbers
cur.execute("select distinct ticket from ticket_change order by ticket")

tts=cur.fetchall()

for (ti,) in tts:
    #get changes per ticket
    cur.execute("select time, author, field, oldvalue, newvalue from ticket_change where ticket=? order by time", (ti,))
    changes=cur.fetchall()
    #get unique changetimes
    times=list(set([x[0] for x in changes]))
    times.sort()
    maxtime=times[-1]
    mintime=times[0]
    times.reverse()
    #go back in time
    for time in times:
        # get the chages
        cur_changes=[x for x in changes if x[0]==time]
        # get the author
        author=cur_changes[0][1]
        # if there is a special row with changetime=0 -> fix it
        cur.execute("update large set changetime=?,author=?,comment=? where id=? and changetime=0",(time,author,u'',ti))

        # check cur state, if error -> log
        for change in cur_changes:
            if change[2]=='comment':
                cur.execute("update large set comment=? where id=? and changetime=?",(change[4],ti,time,))
            else:
                cur.execute("select " + change[2] + " from large where id=? and changetime=?", (ti,time,))
                values=cur.fetchall()
                if values[0][0]!=change[4]:
                    print "ERROR", change
        
        #make a special row with changetime=0
        cur.execute("insert into large ( id, time, changetime, component, severity, priority, owner,reporter, cc, url, version,milestone,status, resolution, summary, description, keywords, comment, author) select  id, time, 0, component, severity, priority, owner,reporter, cc, url, version,milestone,status, resolution, summary, description, keywords, '', '' from large where id=? and changetime = (select min (l.changetime ) from large l where l.id=?)",(ti,ti,))


        #do oldvalue changes
        for change in cur_changes:
            cur.execute("update large set "+change[2]+"=? where id=? and changetime=0",(change[3],ti))

        
    # the last special row has the following as zero strings
    cur.execute("update large set author='', comment='' where id=? and changetime=0",(ti,))
        
    con.commit()
    print ti



delete from ticket;
delete from ticket_change;
insert into ticket select * from new_ticket;
insert into ticket_change select * from new_ticket_change;
insert into ticket select id, time, time+1, 'spam', 'trivial', 'lowest', 'ash', 'spammer', '', '', '', '', 'closed', 'invalid', 'spam', 'spam', 'spam' from spam_ticket;
insert into ticket_change select id, time+1, 'ash', 'status', 'new', 'closed' from spam_ticket;
insert into ticket_change select id, time+1, 'ash', 'resolution', '' , 'invalid' from spam_ticket;
drop table new_ticket;
drop table spam_ticket;
drop table new_ticket_change;
drop table spam_ticket_change;
drop table large;
vacuum;
create table spam_ticket as select * from ticket where id in (
337, 343, 344, 345, 346, 350, 351, 353, 354, 355, 358, 359,
360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371,
372, 373, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385,
386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397,
398, 399, 400, 401, 402, 403, 410, 411, 415, 416, 417, 418,
419, 420);

create table spam_ticket_change as select * from ticket_change where ticket in (
337, 343, 344, 345, 346, 350, 351, 353, 354, 355, 358, 359,
360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371,
372, 373, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385,
386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397,
398, 399, 400, 401, 402, 403, 410, 411, 415, 416, 417, 418,
419, 420);


delete from ticket where id in (
337, 343, 344, 345, 346, 350, 351, 353, 354, 355, 358, 359,
360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371,
372, 373, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385,
386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397,
398, 399, 400, 401, 402, 403, 410, 411, 415, 416, 417, 418,
419, 420);


delete from ticket_change where ticket in (
337, 343, 344, 345, 346, 350, 351, 353, 354, 355, 358, 359,
360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371,
372, 373, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385,
386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397,
398, 399, 400, 401, 402, 403, 410, 411, 415, 416, 417, 418,
419, 420);




#!/usr/bin/python
# -*- coding: utf-8 -*-
from pysqlite2 import dbapi2 as sqlite

#import apis
#connect & get cursor
con = sqlite.connect("/home/ash/gtp-clean/db/trac.db")
cur = con.cursor()


#get the ticket numbers
cur.execute("select distinct id from large order by id")

tts=cur.fetchall()

names=('id', 'time', 'changetime', 'component', 'severity', 'priority', 'owner', 'reporter', 'cc', 'url', 'version', 'milestone', 'status', 'resolution', 'summary', 'description', 'keywords', 'comment', 'author', )
names_end=len(names)-2

for (ti,) in tts: #[(311,)]: # tts:
    #get states per ticket
    cur.execute("select * from large where id=? order by changetime", (ti,))
    states=cur.fetchall()
    #print "states", states
    if len(states)==1:
        print ti, "pass"
    else:
        for k in range(1, len(states)):
            for ind in range(3, names_end):
                if states[k][ind]!=states[k-1][ind]:
                    cur.execute("insert into new_ticket_change values (?, ?, ?, ?, ?, ?)",(ti,states[k][2],states[k][-1],names[ind],states[k-1][ind],states[k][ind]))
            if states[k][-2]!=u'':
                cur.execute("insert into new_ticket_change values (?, ?, ?, ?, ?, ?)",(ti,states[k][2],states[k][-1],'comment','',states[k][-2]))
                con.commit()
    con.commit()
    print ti
        
for (ti,) in tts:
    cur.execute("""insert into new_ticket
                          select l.id, l.time, l.changetime, l.component,
                                 l.severity, l.priority, l.owner, l.reporter,
                                 l.cc, l.url, l.version, l.milestone, l.status,
                                 l.resolution, l.summary, l.description, l.keywords
                          from large l
                          where l.id=? and l.changetime= (
                                                          select max(g.changetime)
                                                          from large g
                                                          where g.id=?)
                 """, (ti,ti,))

cur.execute("update new_ticket set changetime=time where changetime=0")

con.commit()
_______________________________________________
Trac mailing list
Trac@lists.edgewall.com
http://lists.edgewall.com/mailman/listinfo/trac

Reply via email to