Yesterday I had a lot of fun tracking down a spammer.
Sesh Kamachi IMed the Linux group asking how to find a location of an object that keep spamming messages. I gave him a tool I made for the purpose, which shows the last speaker’s name, owner (if it’s an object) and location. Sesh reported that this didn’t work, so I came there to check it out myself.
My theory was that perhaps this was some object he owned that used llOwnerSay. Just in case, I decided to hang around for a while. Finally I got the message:
[2007/08/08 17:51] VoG: Very early in the morning, the chief priests, with the elders, the teachers of the law and the whole Sanhedrin, reached a decision. They bound Jesus, led him away and handed him over to Pilate.”Are you the king of the Jews?” asked Pilate.”Yes, it is as you say,” Jesus replied. The chief priests accused him of many things. So again Pilate asked him, “Aren’t you going to answer? See how many things they are accusing you of.” But Jesus still made no reply, and Pilate was amazed. Now it was the custom at the Feast to release a prisoner whom the people requested. A man called Barabbas was in prison with the insurrectionists who had committed murder in the uprising. The crowd came up and asked Pilate to do for them what he usually did. “Do you want me to release to you the king of the Jews?” asked Pilate,knowing it was out of envy that the chief priests had handed Jesus over to him. But the chief priests stirred up the crowd to have Pilate release Barabbas instead. “What shall I do, then, with the one you call the
Turns out, this thing sends IMs to a list of avatars (I’m so lucky that they added me to it!), so objects listening for chat can’t hear it. There’s no good way of tracking this down with the standard tools or any scripts residents have available (though Lindens can). Fortunately, Linden Labs released the source to the viewer, and that makes it possible to do some really useful changes to the viewer.
From here there will be technical details on the SL Viewer code, but I’ll try to keep it understandable.
The function that processes IM messages in the viewer’s code is called process_improved_im and is located in the llviewermessage.cpp file. This function decodes the message from the grid with the IM data, and does what’s needed to show it to the user. Turns out, there is a lot of useful info in there that the user never sees. Here’s a part of it:
//XUI:translate - need to fix the full name to first/last
msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, from_id);
msg->getBOOLFast(_PREHASH_MessageBlock, _PREHASH_FromGroup, from_group);
msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_ToAgentID, to_id);
msg->getU8Fast( _PREHASH_MessageBlock, _PREHASH_Offline, offline);
msg->getU8Fast( _PREHASH_MessageBlock, _PREHASH_Dialog, d);
msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_ID, session_id);
msg->getU32Fast( _PREHASH_MessageBlock, _PREHASH_Timestamp, t);
//msg->getData("MessageBlock", "Count", &count);
msg->getStringFast(_PREHASH_MessageBlock, _PREHASH_FromAgentName, DB_FULL_NAME_BUF_SIZE, name);
msg->getStringFast(_PREHASH_MessageBlock, _PREHASH_Message, DB_IM_MSG_BUF_SIZE, message);
msg->getU32Fast(_PREHASH_MessageBlock, _PREHASH_ParentEstateID, parent_estate_id);
msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_RegionID, region_id);
msg->getVector3Fast(_PREHASH_MessageBlock, _PREHASH_Position, position);
msg->getBinaryDataFast( _PREHASH_MessageBlock, _PREHASH_BinaryBucket, binary_bucket, 0, 0, MTUBYTES);
binary_bucket_size = msg->getSizeFast(_PREHASH_MessageBlock, _PREHASH_BinaryBucket);
EInstantMessage dialog = (EInstantMessage)d;
time_t timestamp = (time_t)t;
This code is the part of the function that reads the data stored in the message sent from the grid. There are some very useful things in there: from_id is actually the key of the avatar speaking. For objects, it’s the key of the owner, not the object’s own key. This already allows figuring out who is the spammer.
The position variable holds the coordinates of the speaker. This is very useful, now I can go right to the source and take a look at it. I added some code to dump this data to the log:
llinfos << "IM: from " << from_id << ": " << message << llendl;
llinfos << "Agent name: " << name << llendl;
llinfos << "Group: " << from_group << llendl;
llinfos << "Position: " << position << llendl;
I logged back into SL with the updated viewer and waited. I got another IM, which gave me the coordinates. I went there but failed to find anything. Then I realized that the spamming object wasn’t even in the same sim as I. Fortunately there’s another useful bit of data in there, region_id. This holds the ID of the sim the speaker is in. A slight problem is that this contains a key, which is a number identifying the sim, but there doesn’t seem to be any way to convert this into the sim’s name.
Something that helped here is that the viewer gets both the sim’s key and name as it moves from one to another. So it was easy enough to log both the speaker’s sim’s ID and the one I was in, and compare. It also helped a lot that the object happened to be in a nearby sim, as otherwise locating it would have been more challenging. I added some more code for dumping this info:
llinfos << "Region id: " << region_id << llendl;
llinfos << "Estate id: " << parent_estate_id << llendl;
llinfos << "Agent region: " << gAgent.getRegion()->getRegionID() << llendl;
Armed with this, I waited until I got another message. When I did, it was just the matter of checking out nearby sims, to find one with the same region ID. And indeed, the thing was right there:
Now that I had found it, I and some very annoyed residents who had been spammed for days could submit an abuse report on it.
I’m going to finish what I started and make it a feature of my viewer. The final version will be somewhat different. It’ll probably be an addition to the IM window, instead of messages dumped into the log. Meanwhile, anybody wanting to do this can just use the code in this entry, it’s really all that’s needed.
Edit: Fixed some code that apparently got broken by WordPress. Grr.