<?xml version="1.0" encoding="UTF-8"?> <rss
version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
> <channel><title>kahunaburger &#187; Perl</title> <atom:link href="http://www.kahunaburger.com/category/perl/feed/" rel="self" type="application/rss+xml" /><link>http://www.kahunaburger.com</link> <description>home of pia, max, frisco and tobias</description> <lastBuildDate>Wed, 08 Feb 2012 16:20:09 +0000</lastBuildDate> <language>en</language> <sy:updatePeriod>hourly</sy:updatePeriod> <sy:updateFrequency>1</sy:updateFrequency> <generator>http://wordpress.org/?v=3.3.1</generator> <item><title>Google Earth Forensics</title><link>http://www.kahunaburger.com/2009/04/07/google-earth-forensics/</link> <comments>http://www.kahunaburger.com/2009/04/07/google-earth-forensics/#comments</comments> <pubDate>Tue, 07 Apr 2009 16:18:45 +0000</pubDate> <dc:creator>Tobias</dc:creator> <category><![CDATA[Google Earth]]></category> <category><![CDATA[Perl]]></category> <guid
isPermaLink="false">http://www.kahunaburger.com/?p=1452</guid> <description><![CDATA[[ Update 04/16/2009: The code for the tool is now available for download. Look at the end of this post ... ] A lot of log files on my system contain ip-addresses: sshd logs attack attempts (and successful logins), snort logs common intrusion tactics, apache logs errors and accesses, etc. Using my new found love [...]]]></description> <content:encoded><![CDATA[<p>[ <strong>Update 04/16/2009: <em>The code for the tool is now available for download. Look at the end of this post ...</em></strong> ]<br
/> A lot of log files on my system contain ip-addresses: <a
href="http://en.wikipedia.org/wiki/Secure_Shell">sshd</a> logs attack attempts (and successful logins), <a
href="http://en.wikipedia.org/wiki/Snort_(software)">snort</a> logs common intrusion tactics, <a
href="http://www.apache.org/">apache</a> logs errors and accesses, etc.</p><p>Using my new found love for Google Earth and some perl hacking, I created a little tool that allows me to monitor the log information above and put threats, errors and accesses on the map (literally!).</p><p>Here&#8217;s how it works: a perl script runs on a regular basis and scans a number of log files on my system for new information. If new information is found, it generates a KML file with placemarks that point to the location that is responsible for the log information. Say snort complains about a potential SQL injection attempt from address a.b.c.d, then the script will look up the location of a.b.c.d (again using <a
href="http://blogama.org/node/58">Marc&#8217;s free ip2location database</a>), add a placemark for it (with some details from the log files) and repeats the same sequence for other new log information. Everything is bundled up in a KML file, a tour is added and the stuff is shipped to Google Earth.</p><p>Inside Google Earth, I see the following under &#8220;Places&#8221;:</p><p><img
src="http://www.kahunaburger.com/wp-content/uploads/2009-04-07-geforensics2.png" alt="Places" title="Places" width="338" height="260" class="aligncenter size-full wp-image-1454" /></p><p>There are 6 entries that sshd generated, one from snort and one from <a
href="http://www.fail2ban.org/">fail2ban</a>. Here&#8217;s one example:</p><p><a
href="http://www.kahunaburger.com/wp-content/uploads/2009-04-07-geforensics.jpg"><img
src="http://www.kahunaburger.com/wp-content/uploads/2009-04-07-geforensics-300x139.jpg" alt="2009-04-07-geforensics" title="2009-04-07-geforensics" width="300" height="139" class="aligncenter size-medium wp-image-1456" /></a></p><p>And here&#8217;s another one from the US:</p><p><a
href="http://www.kahunaburger.com/wp-content/uploads/2009-04-07-geforensics3.jpg"><img
src="http://www.kahunaburger.com/wp-content/uploads/2009-04-07-geforensics3-300x135.jpg" alt="2009-04-07-geforensics3" title="2009-04-07-geforensics3" width="300" height="135" class="aligncenter size-medium wp-image-1457" /></a></p><p>For each entry one can also get a traceroute, whois and black-list information. And to top it all off, there&#8217;s an animated tour feature that visits all the threats automatically (wish that KML would support auto-play and auto-repeat features).</p><p>The script can run from the command line and it&#8217;s output can be piped into a new kml-file like this:</p><div
class="wp_syntax"><div
class="code"><pre class="bash" style="font-family:monospace;">$ <span style="color: #c20cb9; font-weight: bold;">perl</span> geforensics.pl <span style="color: #000000; font-weight: bold;">&gt;</span> foo.kml</pre></div></div><p>Then take foo.kml and place it somewhere on a server or load it up directly in Google Earth.</p><p>The script also detects if it is running as a CGI. In that case it will use KML&#8217;s <a
href="http://code.google.com/apis/kml/documentation/kml_tut.html#network_links">NetworkLink</a> feature with refreshMode set to &#8220;onInterval&#8221; to refresh the placemarks automatically after a given period (I use 5 mins here). Pretty cool to see GE refresh the map automatically <img
src='http://www.kahunaburger.com/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /></p><p>And here&#8217;s a screencast that puts it all together (watch it in full-screen): I start the tour of incidents, then select a specific host in China, initiate a traceroute and while the traceroute is running I check if the guy is listed in some black-lists (he is), once the traceroute comes back I go to the first hop (my ISP), then over to the destination and check the WHOIS information for the ip-address.</p><p><center><object
id='stU0JXSkBIR1pWQVxYUlpeUl9W' width='425' height='344' type='application/x-shockwave-flash' data='http://www.screentoaster.com/swf/STPlayer.swf'  codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0'><param
name='movie' value='http://www.screentoaster.com/swf/STPlayer.swf'/><param
name='allowFullScreen' value='true'/><param
name='allowScriptAccess' value='always'/><param
name='flashvars' value='video=stU0JXSkBIR1pWQVxYUlpeUl9W'/></object><div
style='width: 425px; text-align: right;'><a
href='http://www.screentoaster.com/'>Free online screencasting tool</a></div><p></center></p><p>And here&#8217;s a sample KML file if you want to see it for yourself (in Google Earth):<br
/> <a
href='http://www.kahunaburger.com/wp-content/uploads/2009-04-07-gef.kml'>2009-04-07-gef.kml</a></p><p>The perl code for the little tool is now available at: <a
href="/wp-content/uploads/2009-04-16-gforensics.pl.gz">2009-04-16-gforensics.pl.gz (gzip compressed perl file &#8211; 4KB)</a>.</p> ]]></content:encoded> <wfw:commentRss>http://www.kahunaburger.com/2009/04/07/google-earth-forensics/feed/</wfw:commentRss> <slash:comments>2</slash:comments> </item> <item><title>Google Earth as a traceroute viewer</title><link>http://www.kahunaburger.com/2009/03/28/google-earth-as-a-traceroute-viewer/</link> <comments>http://www.kahunaburger.com/2009/03/28/google-earth-as-a-traceroute-viewer/#comments</comments> <pubDate>Sat, 28 Mar 2009 17:55:32 +0000</pubDate> <dc:creator>Tobias</dc:creator> <category><![CDATA[Google Earth]]></category> <category><![CDATA[News]]></category> <category><![CDATA[Perl]]></category> <guid
isPermaLink="false">http://www.kahunaburger.com/?p=1389</guid> <description><![CDATA[[Update 03/30/2009: After making sure that people won't be able to bring down my server, you can try a live example, by clicking the following link (you will still need Google Earth on your computer): Trace the path from my server to your system ] [Update 04/07/2009: If you like this post, you may also [...]]]></description> <content:encoded><![CDATA[<p>[<strong><em>Update 03/30/2009</em>: After making sure that people won't be able to bring down my server, you can try a live example, by clicking the following link (you will still need <a
href="http://earth.google.com/">Google Earth</a> on your computer): <a
href="http://www.kahunaburger.com/perl/g.pl">Trace the path from my server to your system</a></strong> ]</p><p>[<strong><em>Update 04/07/2009</em>: If you like this post, you may also want to take a look at the <a
href="http://www.kahunaburger.com/2009/04/07/google-earth-forensics/">Google Earth Forensics post</a>, which is IMHO a lot cooler <img
src='http://www.kahunaburger.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </strong> ]</p><p>[<strong><em>Update 04/16/2009</em>: I added a link to source code at the end of this post. </strong> ]</p><p>I played a bit more with the idea that I presented in <a
href="http://www.kahunaburger.com/2009/03/25/apache-access_log-to-google-earth-kml/">&#8220;Apache access_log to Google Earth KML&#8221;</a> and, I think, I came up with something extremely cool.</p><p>When you surf around on the Internet (or use any TCP/IP service), your data is being routed through an endless list of gateways. Your packets are hopping from one system to the next one, until the final destination has been reached. And data served by the remote destination is hopping it&#8217;s way back to you.</p><p>On Unix we have the <a
href="http://en.wikipedia.org/wiki/Traceroute">traceroute</a> utility (on Windows it&#8217;s called &#8216;tracert&#8217;) that allows you to figure out the route that your packets are taking to a remote destination.</p><p>In my web server&#8217;s access_log I can see a line where a host at the ip-address 67.195.37.190 accessed a certain url on my server. Resolving that ip-address to a name (via &#8220;host 67.195.37.190&#8243;) reveals that it is one of Yahoo.com&#8217;s crawlers. Using traceroute on the same address shows the following (I changed my internal network address to aaa.bbb.ccc.ddd below):</p><pre>
$ traceroute -q 1 -e -I 67.195.37.190
traceroute to 67.195.37.190 (67.195.37.190), 64 hops max, 60 byte packets
 1  netgear (aaa.bbb.ccc.ddd)  0.781 ms
 2  208-3-81-1.cnsp.net (208.3.81.1)  12.871 ms
 3  144.223.172.81 (144.223.172.81)  35.880 ms
 4  sl-bb20-ana-0-0.sprintlink.net (144.232.1.241)  54.828 ms
 5  sl-crs2-ana-0-13-5-0.sprintlink.net (144.232.1.177)  40.534 ms
 6  192.205.33.189 (192.205.33.189)  39.042 ms
 7  cr1.la2ca.ip.att.net (12.122.128.14)  54.288 ms
 8  cr1.sffca.ip.att.net (12.122.3.121)  51.418 ms
 9  12.122.137.97 (12.122.137.97)  45.766 ms
10  12.86.154.18 (12.86.154.18)  49.140 ms
11  so-1-0-0.pat1.swp.yahoo.com (216.115.110.43)  72.021 ms
12  as0.pat1.gqb.yahoo.com (216.115.96.45)  73.443 ms
13  xe-5-0-0.msr1.gq1.yahoo.com (66.196.67.1)  73.514 ms
14  xe-8-0-0.clr2-a-sat.gq1.yahoo.com (67.195.0.19)  76.818 ms
15  te-6-0.bas5-2-con.gq1.yahoo.com (98.137.31.34)  79.453 ms
16  llf320059.crawl.yahoo.net (67.195.37.190)  76.748 ms
$
</pre><p>This output tells me that it takes roughly 80 milliseconds to get packets from my system to the final destination at Yahoo. However, it does not tell me what geographical path my packets take.</p><p>Hold on to your socks, because here&#8217;s the same path after I ran it through my little traceroute visualization tool (displayed in Google Earth):</p><p><a
href="http://www.kahunaburger.com/wp-content/uploads/2009-03-28-trace.jpg"><img
src="http://www.kahunaburger.com/wp-content/uploads/2009-03-28-trace.jpg" alt="Traceroute in Google Earth" title="Traceroute in Google Earth" width="600" height="278" class="aligncenter size-full wp-image-1391" /></a></p><p>The tool I created this morning, will automatically run a traceroute to any ip-address and it will create a <a
href="http://code.google.com/apis/kml/documentation/">Google Earth compatible KML file</a>. Again we are using <a
href="http://blogama.org/node/58">Marc&#8217;s free database</a> to map ip-addresses to locations on the map. For each hop it records the hop&#8217;s ip-address, name and, if available, location. The tool also creates a tour that allows to jump from hop to hop in an animated fashion, until you arrive at your final destination where even more information (<a
href="http://en.wikipedia.org/wiki/Whois">Whois</a>) is displayed.</p><p>When you try one of the sample files below in Google Earth, just double-click the &#8220;Animate Route (play me)&#8221; item in the &#8220;Places&#8221; area:</p><p><a
href="http://www.kahunaburger.com/wp-content/uploads/2009-03-28-trace2.png"><img
src="http://www.kahunaburger.com/wp-content/uploads/2009-03-28-trace2.png" alt="Animate Route" title="Animate Route" width="382" height="314" class="aligncenter size-full wp-image-1393" /></a></p><p>As you jump from hop to hop, information about the current gateway is being displayed roughly in the geo location where that gateway is physically located (the free database mentioned above does have some hosts that are not mapped and I&#8217;m skipping those automatically).</p><p>And here are two KMZ files that you can download to Google Earth in order to see the stuff:</p><ul><li> <a
href='http://www.kahunaburger.com/wp-content/uploads/6719537190.kmz'>Complete Trace to Yahoo.net (1.8KB)</a></li><li> <a
href='http://www.kahunaburger.com/wp-content/uploads/174129101225.kmz'>Incomplete Trace to Amazon.com (1.9KB)</a></li></ul><p>&#8220;Complete&#8221; above means that I was able to trace the route all the way to the destination host. And &#8220;Incomplete&#8221; means that I aborted the trace after a number of systems along the path did not respond to my trace queries.</p><p>And if you want to see a live example, click the following link to see the <a
href="http://www.kahunaburger.com/perl/g.pl">path from my server to your system</a>.</p><p>The perl code for the little tool is now available at: <a
href="/wp-content/uploads/2009-04-16-gtrace.pl.gz">2009-04-16-gtrace.pl.gz (gzip compressed perl file &#8211; 2.5KB)</a>.</p> ]]></content:encoded> <wfw:commentRss>http://www.kahunaburger.com/2009/03/28/google-earth-as-a-traceroute-viewer/feed/</wfw:commentRss> <slash:comments>9</slash:comments> </item> <item><title>Apache access_log to Google Earth KML</title><link>http://www.kahunaburger.com/2009/03/25/apache-access_log-to-google-earth-kml/</link> <comments>http://www.kahunaburger.com/2009/03/25/apache-access_log-to-google-earth-kml/#comments</comments> <pubDate>Wed, 25 Mar 2009 12:58:06 +0000</pubDate> <dc:creator>Tobias</dc:creator> <category><![CDATA[Google Earth]]></category> <category><![CDATA[News]]></category> <category><![CDATA[Perl]]></category> <guid
isPermaLink="false">http://www.kahunaburger.com/?p=1351</guid> <description><![CDATA[Where are the visitors to kahunaburger.com coming from? I admitted in the past that I&#8217;m log-file-junkie. There&#8217;s usually a terminal window on desktop that runs multitail (or my own airlog) against a number of log-files on various servers. Line after line scrolls by as people hit those servers. The ip-address does not tell you too [...]]]></description> <content:encoded><![CDATA[<p>Where are the visitors to kahunaburger.com coming from? I admitted in the past that I&#8217;m log-file-junkie. There&#8217;s usually a terminal window on desktop that runs <a
href="http://www.vanheusden.com/multitail/">multitail</a> (or my own <a
href="http://www.kahunaburger.com/2009/03/25/airlog/">airlog</a>) against a number of log-files on various servers. Line after line scrolls by as people hit those servers. The ip-address does not tell you too much about the visitor and I always wanted to see where those ip-addresses are located.</p><p>Just the other day I saw link to a free <a
href="http://blogama.org/node/58">IP address geolocation SQL database</a> (thanks Marc for making that one available). I downloaded the 11MB file and added the database to my mysql server.</p><p>Next, I created a simple perl script, that walks over my web servers (<a
href="http://www.apache.org/">apache</a>) access_log, extracts ip-addresses, access-date/time and url and finally converts all those items (using above mentioned database) into a <a
href="http://en.wikipedia.org/wiki/Keyhole_Markup_Language">KML</a> file that can be fed to <a
href="http://earth.google.com/">Google Earth</a>.</p><p>The result looks like this in Google Earth:<br
/> <img
src="http://www.kahunaburger.com/wp-content/uploads/2009-03-25-log2kml.jpg" alt="2009-03-25-log2kml" title="2009-03-25-log2kml" width="600" height="376" class="aligncenter size-full wp-image-1369" /></p><p>And here&#8217;s the script that does the magic (it assumes that you have stored the database in &#8220;ipinfo&#8221;):</p><div
class="wp_syntax"><table><tr><td
class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
</pre></td><td
class="code"><pre class="perl" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">use</span> strict<span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">use</span> DBI<span style="color: #339933;">;</span>
&nbsp;
<span style="color: #b1b100;">my</span> <span style="color: #0000ff;">%seen</span><span style="color: #339933;">;</span>
<span style="color: #b1b100;">my</span> <span style="color: #0000ff;">$dbh</span> <span style="color: #339933;">=</span> DBI<span style="color: #339933;">-&gt;</span><span style="color: #006600;">connect</span><span style="color: #009900;">&#40;</span><span style="color: #ff0000;">&quot;dbi:mysql:ipinfo&quot;</span><span style="color: #339933;">,</span><span style="color: #ff0000;">&quot;username&quot;</span><span style="color: #339933;">,</span><span style="color: #ff0000;">&quot;password&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000066;">die</span> <span style="color: #ff0000;">&quot;unable to connect to database&quot;</span> <span style="color: #b1b100;">unless</span> <span style="color: #009900;">&#40;</span><span style="color: #0000ff;">$dbh</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #666666; font-style: italic;"># kml header</span>
<span style="color: #000066;">print</span> <span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #339933;">&lt;</span> <span style="color: #339933;">?</span>xml version<span style="color: #339933;">=</span><span style="color: #ff0000;">&quot;1.0&quot;</span> encoding<span style="color: #339933;">=</span><span style="color: #ff0000;">&quot;UTF-8&quot;</span><span style="color: #339933;">?&gt;</span>
<span style="color: #339933;">&lt;</span>kml xmlns<span style="color: #339933;">=</span><span style="color: #ff0000;">&quot;http://www.opengis.net/kml/2.2&quot;</span><span style="color: #339933;">&gt;</span>
<span style="color: #0000ff;">\t</span><span style="color: #009999;">&lt;document&gt;</span>
<span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
<span style="color: #666666; font-style: italic;"># loop over access_log lines</span>
<span style="color: #b1b100;">while</span><span style="color: #009900;">&#40;</span><span style="color: #339933;">&lt;&gt;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #666666; font-style: italic;"># does it look like an access log entry?</span>
    <span style="color: #b1b100;">next</span> <span style="color: #b1b100;">unless</span> <span style="color: #009900;">&#40;</span><span style="color: #009966; font-style: italic;">/^(\d+)\.(\d+)\.(\d+)\.(\d+).*\[([^\]]+)\]\s+&quot;(\S+)\s+(\S+)\s+HTTP\/\d.\d/</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">my</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">$a</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$b</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$c</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$d</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$date</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$method</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$uri</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">=</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">$1</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$2</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$3</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$4</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$5</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$6</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$7</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #666666; font-style: italic;"># make sure we have a good ip address</span>
    <span style="color: #b1b100;">next</span> <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #0000ff;">$a</span> <span style="color: #339933;">&lt;</span> <span style="color: #cc66cc;">0</span> <span style="color: #339933;">||</span> <span style="color: #0000ff;">$a</span> <span style="color: #339933;">&gt;</span> <span style="color: #cc66cc;">255</span> <span style="color: #339933;">||</span> <span style="color: #0000ff;">$b</span> <span style="color: #339933;">&lt;</span> <span style="color: #cc66cc;">0</span> <span style="color: #339933;">||</span> <span style="color: #0000ff;">$b</span> <span style="color: #339933;">&gt;</span> <span style="color: #cc66cc;">255</span> <span style="color: #339933;">||</span>
             <span style="color: #0000ff;">$c</span> <span style="color: #339933;">&lt;</span> <span style="color: #cc66cc;">0</span> <span style="color: #339933;">||</span> <span style="color: #0000ff;">$c</span> <span style="color: #339933;">&gt;</span> <span style="color: #cc66cc;">255</span> <span style="color: #339933;">||</span> <span style="color: #0000ff;">$d</span> <span style="color: #339933;">&lt;</span> <span style="color: #cc66cc;">0</span> <span style="color: #339933;">||</span> <span style="color: #0000ff;">$d</span> <span style="color: #339933;">&gt;</span> <span style="color: #cc66cc;">255</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #666666; font-style: italic;"># did we see this IP already?</span>
    <span style="color: #b1b100;">next</span> <span style="color: #b1b100;">if</span> <span style="color: #0000ff;">$seen</span><span style="color: #009900;">&#123;</span><span style="color: #ff0000;">&quot;$a.$b.$c.$d&quot;</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">++;</span>
    <span style="color: #666666; font-style: italic;"># compute value for ipinfo lookup</span>
    <span style="color: #b1b100;">my</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">$val</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">=</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">$a</span><span style="color: #339933;">*</span><span style="color: #cc66cc;">256</span><span style="color: #339933;">+</span><span style="color: #0000ff;">$b</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">*</span><span style="color: #cc66cc;">256</span><span style="color: #339933;">+</span><span style="color: #0000ff;">$c</span><span style="color: #339933;">;</span>
    <span style="color: #666666; font-style: italic;"># fetch ipinfo data</span>
    <span style="color: #666666; font-style: italic;"># WARNING! for whatever reason the code-beautifier adds an extra space between the &lt; and = below</span>
    <span style="color: #666666; font-style: italic;"># WARNING! that space has to be removed in your code!</span>
    <span style="color: #b1b100;">my</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">$r</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">=</span><span style="color: #0000ff;">$dbh</span><span style="color: #339933;">-&gt;</span><span style="color: #006600;">selectall_arrayref</span><span style="color: #009900;">&#40;</span><span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #000066;">select</span> country_code<span style="color: #339933;">,</span>region_code<span style="color: #339933;">,</span>city<span style="color: #339933;">,</span>latitude<span style="color: #339933;">,</span>longitude
        from ip_group_city where ip_start<span style="color: #339933;">&lt;</span> <span style="color: #339933;">=</span><span style="color: #0000ff;">$val</span> order by ip_start desc limit <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #666666; font-style: italic;"># no information? no placemark!</span>
    <span style="color: #b1b100;">next</span> <span style="color: #b1b100;">if</span> <span style="color: #339933;">!</span><span style="color: #000066;">defined</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">$r</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000066;">print</span> <span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #009999;">&lt;Placemark&gt;</span><span style="color: #0000ff;">\n</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
    <span style="color: #000066;">print</span> <span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #009999;">&lt;name&gt;</span><span style="color: #0000ff;">$a</span><span style="color: #339933;">.</span><span style="color: #0000ff;">$b</span><span style="color: #339933;">.</span><span style="color: #0000ff;">$c</span><span style="color: #339933;">.</span><span style="color: #0000ff;">$d</span><span style="color: #339933;">&lt;/</span>name<span style="color: #339933;">&gt;</span><span style="color: #0000ff;">\n</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
    <span style="color: #000066;">print</span> <span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #009999;">&lt;description&gt;</span><span style="color: #0000ff;">\n</span><span style="color: #339933;">&lt;</span> <span style="color: #339933;">!</span><span style="color: #009900;">&#91;</span>CDATA<span style="color: #009900;">&#91;</span><span style="color: #0000ff;">\n</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
    <span style="color: #000066;">print</span> <span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #009999;">&lt;b&gt;</span><span style="color: #0000ff;">$method</span> <span style="color: #0000ff;">$uri</span> from <span style="color: #0000ff;">$r</span><span style="color: #339933;">-&gt;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">-&gt;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">/</span><span style="color: #0000ff;">$r</span><span style="color: #339933;">-&gt;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">-&gt;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">/</span><span style="color: #0000ff;">$r</span><span style="color: #339933;">-&gt;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">-&gt;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">2</span><span style="color: #009900;">&#93;</span> at <span style="color: #009999;">&lt;i&gt;</span><span style="color: #0000ff;">$date</span><span style="color: #339933;">&lt;/</span>i<span style="color: #339933;">&gt;&lt;</span>br <span style="color: #339933;">/&gt;</span><span style="color: #0000ff;">\n</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
    <span style="color: #000066;">print</span> <span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">&gt;</span><span style="color: #0000ff;">\n</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #339933;">&lt;/</span>description<span style="color: #339933;">&gt;</span><span style="color: #0000ff;">\n</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
    <span style="color: #000066;">print</span> <span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #009999;">&lt;point&gt;</span><span style="color: #0000ff;">\n</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
    <span style="color: #000066;">print</span> <span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #009999;">&lt;coordinates&gt;</span><span style="color: #0000ff;">$r</span><span style="color: #339933;">-&gt;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">-&gt;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">4</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">,</span><span style="color: #0000ff;">$r</span><span style="color: #339933;">-&gt;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">-&gt;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">3</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">&lt;/</span>coordinates<span style="color: #339933;">&gt;</span><span style="color: #0000ff;">\n</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
    <span style="color: #000066;">print</span> <span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #339933;">&lt;/</span>point<span style="color: #339933;">&gt;</span><span style="color: #0000ff;">\n</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
    <span style="color: #000066;">print</span> <span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\t</span><span style="color: #0000ff;">\n</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
<span style="color: #666666; font-style: italic;"># kml trailer</span>
<span style="color: #000066;">print</span> <span style="color: #000066;">qq</span><span style="color: #009900;">&#123;</span><span style="color: #0000ff;">\t</span><span style="color: #339933;">&lt;/</span>document<span style="color: #339933;">&gt;</span>
<span style="color: #339933;">&lt;/</span>kml<span style="color: #339933;">&gt;</span>
<span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
<span style="color: #0000ff;">$dbh</span><span style="color: #339933;">-&gt;</span><span style="color: #006600;">disconnect</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></td></tr></table></div><p>And you run the above script via:</p><pre code="bash">
perl log2kml.pl < access_log > output.kml
</pre><p>Here&#8217;s a little sample file from my web-server. Each ip-address is only recorded once, so if you have the same person visit your site several times, only the first hit will be shown and subsequent ones are ignored: <a
href='http://www.kahunaburger.com/wp-content/uploads/log2kml.kmz'>log2kml.kmz (51K &#8211; click to open it in Google Earth)</a></p><p>Next up is a version that does live-tracking: as the web server is hit, Google Earth will automatically rotate to the location associated with the ip-address <img
src='http://www.kahunaburger.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /></p> ]]></content:encoded> <wfw:commentRss>http://www.kahunaburger.com/2009/03/25/apache-access_log-to-google-earth-kml/feed/</wfw:commentRss> <slash:comments>10</slash:comments> </item> <item><title>mt-proxyplug shut down comments through proxies</title><link>http://www.kahunaburger.com/2005/01/15/mt-proxyplug-shut-down-comments-through-proxies/</link> <comments>http://www.kahunaburger.com/2005/01/15/mt-proxyplug-shut-down-comments-through-proxies/#comments</comments> <pubDate>Sat, 15 Jan 2005 20:12:57 +0000</pubDate> <dc:creator>Tobias</dc:creator> <category><![CDATA[Perl]]></category> <category><![CDATA[Spam]]></category> <guid
isPermaLink="false">http://www.kahunaburger.com/2005/01/15/mt-proxyplug-shut-down-comments-through-proxies/</guid> <description><![CDATA[A few days ago I posted Deny Comment Spam from open proxies in MovableType which showed a technique to limit comment submissions through proxies. Unfortunately there are a number of issues with the small plugin, which made me create mt-proxyplug, presented in this article. How it all started I am a longtime user of Jay [...]]]></description> <content:encoded><![CDATA[<p>A few days ago I posted <a
href="/blog/archives/000191.html">Deny Comment Spam from open proxies in MovableType</a> which showed a technique to limit comment submissions through proxies. Unfortunately there are a number of issues with the small plugin, which made me create <b>mt-proxyplug</b>, presented in this article.</p><p><i>How it all started</i></p><p>I am a longtime user of <a
href="http://mt-plugins.org/archives/entry/blacklist.php">Jay Allen&#8217;s Blacklist</a> and was happy with it for a long time. Recently I could not keep up with adding new keywords/urls to the black list. There seem to be a million variations of &#8220;Texas Hold&#8217;em&#8221; out there and I ended up adding those items slowly to the Blacklist system (in the end I actually added &#8220;texas&#8221; as a Blacklist item only to find a &#8220;texa$ H0ld&#8217;em&#8221; the next day in my list of moderated comments).</p><p>I started to look more carefully at the offending posts and investigated the submitting IP addresses in detail. Soon I realized that most of the stuff was coming from public proxy servers. Companies stupid enough to run public proxies and hijacked user systems are on top of the list of systems that submitted spam to my server.</p><p><i>Monitoring the proxies</i></p><p>My apache configuration was changed to include some proxy specific information in my access_logs. I changed the line:<br
/> <code>LogFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" combined</code><br
/> to<br
/> <code>LogFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i" %{HTTP_X_FORWARDED_FOR}e" combined</code></p><p>This means that the apache server will also log the contents of the environment variable &#8220;HTTP_X_FORWARDED_FOR&#8221; to the access_log, whenever that environment variable is present. And the environment variable is present if the current request contains a &#8220;X-Forwarded-For:&#8221; header item. The presence of this item is almost always a clear indication that the request was handled by a proxy server. Items that would have previously been logged like this:</p><p><code>200.242.249.70 - - [15/Jan/2005:11:31:52 -0700] "POST /blog/mt-comments.cgi HTTP/1.0" 302 0 "http://www.kahunaburger.com/blog/mt-comments.cgi?entry_id=113" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.1.4322)"</code></p><p>suddenly looked like this:</p><p><code>200.242.249.70 - - [15/Jan/2005:11:31:52 -0700] "POST /blog/mt-comments.cgi HTTP/1.0" 302 0 "http://www.kahunaburger.com/blog/mt-comments.cgi?entry_id=113" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.1.4322)" 168.41.192.0</code></p><p>(note the addition of the IP address at the end of the line)</p><p>That&#8217;s when I started focusing on proxies and developed mt-commentproxyblock, which evolved into mt-proxyplug.</p><p><i>What does it do?</i></p><p>mt-proxyplug when installed on a MovableType system will act as a &#8216;CommentFilter&#8217;. Every comment submission is passed through it, before it is committed to the database. The plugin will inspect the remote IP address of the system that submits the comment. First it will check if there is a &#8220;X-Forwarded-For:&#8221; header item in the current comment submission request. The mere presence of the header field is an indication that stuff is being submitted through a proxy server. It will then query the <a
href="http://www.dsbl.org/">Distributed Sender Blackhole List</a> and the <a
href="http://opm.blitzed.org/">Blitzed Open Proxy Monitor List</a> for entries for the submitting remote IP address. If either one knows about the IP address, then we assume that the comment comes from a known public proxy system.</p><p>In a last test the remote system is probed on a number of common proxy ports. We try to get access through the system to a well-known and relatively stable host on the internet. If this request is processed successfully on any of the probed ports we also assume that the remote end is indeed a public proxy.</p><p>If any one of the above tests is positive we are not executing the other tests and simply flag the current comment submission as suspicious.</p><p>A configuration section at the top of the file allows for customization of the list of tests the plugin should run.<br
/> There is also a <code>CACHE_COUNT</code> definition that specifies how many found proxies the plugin should keep track of (this will make it much quicker on subsequent requests, if a proxy is used numerous times in a row).</p><p>A log of the plugin&#8217;s actions is also provided in MT&#8217;s Activity Log. Here&#8217;s just a small section from my current log:</p><p><img
src="/images/2005-01-15-mt-activity-log.gif"/></p><p>Since I installed the plugin on kahunaburger.com&#8217;s blog it has caught 121 of 122 comment submissions. The one that slipped through was actually caught by <a
href="/blog/archives/000189.html">mt-spamassassin</a>. During the same time period I also received 4 good comment submissions which made it through the system without any problems.</p><p><i>How to use mt-proxyplug</i></p><p>Just drop the file below into your MovableType&#8217;s plugins folder. Modify the &#8220;settings&#8221; section to your liking (the default values are the recommended values) and you&#8217;re set.<br
/> No other modules are required (I assume that IO::Socket is available on all newer perl installations). HTTP::CheckProxy (as used in the previous version of the plugin) has been dropped, because it would report false positives (or is it &#8220;true negatives&#8221;?).</p><p><b>Update 01/20/2005:</b> I&#8217;ve updated the plugin below to version 0.6. Two changes since the original version:<br
/> 1) I set CHECK_LIST_DSBL_ORG to &#8220;0&#8243; by default, based on ioerror.us&#8217;s comments <a
href="/blog/mt-comments.cgi?entry_id=191">here</a><br
/> 2) I fixed the require list at the top of the file to include &#8220;LWP::UserAgent&#8221; after receiving a problem report from <a
href="http://cj69collins.us/blog/">Chris</a>.</p><p>You can download the plugin here: <a
href="/tmp/mt-proxyplug.pl.gz">mt-proxyplug.pl.gz (2.5KB, gzip)</a></p> ]]></content:encoded> <wfw:commentRss>http://www.kahunaburger.com/2005/01/15/mt-proxyplug-shut-down-comments-through-proxies/feed/</wfw:commentRss> <slash:comments>10</slash:comments> </item> <item><title>Deny Comment Spam from open proxies in MovableType</title><link>http://www.kahunaburger.com/2005/01/13/deny-comment-spam-from-open-proxies-in-movabletype/</link> <comments>http://www.kahunaburger.com/2005/01/13/deny-comment-spam-from-open-proxies-in-movabletype/#comments</comments> <pubDate>Thu, 13 Jan 2005 14:40:37 +0000</pubDate> <dc:creator>Tobias</dc:creator> <category><![CDATA[Perl]]></category> <category><![CDATA[Spam]]></category> <guid
isPermaLink="false">http://www.kahunaburger.com/2005/01/13/deny-comment-spam-from-open-proxies-in-movabletype/</guid> <description><![CDATA[Update 2005/01/15: Please consider using mt-proxyplug instead of this plugin below. Along the same lines as yesterday&#8217;s SpamAssassin and MovableType entry, here&#8217;s another weapon against the &#8220;texas hold&#8217;em&#8221; and &#8220;football&#8211;betting&#8221; idiots. Those morons have a tendency to conceal their identity. They are hiding behind public proxy servers and bombard your servers with their crap from [...]]]></description> <content:encoded><![CDATA[<p><b><i>Update 2005/01/15</i>: Please consider using <a
href="/blog/archives/000192.html">mt-proxyplug</a> instead of this plugin below</b>.</p><p>Along the same lines as yesterday&#8217;s <a
href="/blog/archives/000189.html">SpamAssassin and MovableType</a> entry, here&#8217;s another weapon against the &#8220;texas hold&#8217;em&#8221; and &#8220;football&#8211;betting&#8221; idiots.</p><p>Those morons have a tendency to conceal their identity. They are hiding behind public proxy servers and bombard your servers with their crap from there. So all we have to do is to check for a public proxy when somebody tries to submit a comment. If I detect a submission through a public proxy server it will not end up on the site, but will be ignored silently.</p><p>The proxy check is done in two stages:<ul><li> if we find the HTTP_X_FORWARDED_FOR environment variable we assume that a proxy has handled the request and we don&#8217;t even look any further.</li><li> if no HTTP_X_FORWARDED_FOR is found, we grab Apache&#8217;s REMOTE_ADDR environment variable (the ip-address of the system who sent the current request) and use the <a
href="http://cpan.uwinnipeg.ca/dist/HTTP-CheckProxy" target="_blank">HTTP::CheckProxy</a> module to test whether the submitting system is a public proxy server</li></ul><p>If a comment submission is coming from a public proxy we drop a line in the server&#8217;s error_log. Here are just some of the entries I captured since I installed it:</p><pre>
[Thu Jan 13 08:35:21 2005] comment denied - using proxy: 216.49.49.118 80.25.156.151 -
poker/bushmills1614@rocketmail.com/80.58.4.111
[Thu Jan 13 08:35:46 2005] comment denied - using proxy: 24.215.40.47 -
football betting/bob@y6322o.com/63.110.140.28
[Thu Jan 13 08:39:00 2005] comment denied - using proxy: 58.40.89.127 -
phentermine/gocha9985@see.it/203.199.92.158
[Thu Jan 13 08:48:59 2005] comment denied - using proxy: 115.120.174.78, 127.0.0.1 -
online poker/absolut5129@freemail.com/80.255.49.222
</pre><p>And before somebody points me at Brad&#8217;s <a
href="http://mt-plugins.org/archives/entry/dbsl.php" target="_blank">DBSL</a> plugin: I&#8217;ve tested all the proxies listed above and they do not appear in the DSBL.</p><p>In order to use the plugin you will need to have the perl module HTTP::CheckProxy on your system. Drop the perl-code below into your MT plugins folder and you should be ready to go. Again, this has only been tested under Apache!</p><p>You can download the compressed version here: <a
href="/tmp/mt-commentproxyblock.pl.gz">mt-commentproxyblock.pl.gz (1 Kb,gzip)</a></p><pre>
#!/usr/bin/perl -w
package MT::Plugin::CommentProxyBlock;
use strict;
use lib '../lib';
use vars qw ($VERSION);
$VERSION='0.2';
use constant ACCEPT_RESPONSE =&gt; 1;
use constant DENY_RESPONSE   =&gt; 0;
use MT;
use MT::App::Comments;
use HTTP::CheckProxy;
eval{ require MT::Plugin };
unless ($@) {
&nbsp;&nbsp;&nbsp;&nbsp;my $plugin = {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name =&gt; qq{Comment Proxy Block for Movable Type v$VERSION},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;description =&gt; qq{Will block attempts to post a comment via a proxy server},
&nbsp;&nbsp;&nbsp;&nbsp;};
&nbsp;&nbsp;&nbsp;&nbsp;MT-&gt;add_plugin(new MT::Plugin($plugin));
&nbsp;&nbsp;&nbsp;&nbsp;# tell MT that we want to be called to filter comments
&nbsp;&nbsp;&nbsp;&nbsp;MT-&gt;add_callback('CommentFilter', 1, $plugin, \&amp;proxyCheck_filter);
}
# proxyCheck_filter
#
# checks environment for an entry which indicates we are handling a request that
# came from a proxy server (HTTP_X_FORWARDED_FOR). If environment does not give
# any indication, check REMOTE_ADDR and see if it proxies requests for us. If
# either is true we deny the comment posting attempt. Tested in Apache only!
sub proxyCheck_filter {
&nbsp;&nbsp;&nbsp;&nbsp;my($eh,$app,$comment)=@_;
&nbsp;&nbsp;&nbsp;&nbsp;my($isProxy,$proxy)=(0,'');
&nbsp;&nbsp;&nbsp;&nbsp;# uncomment to get a complete dump of the environment
&nbsp;&nbsp;&nbsp;&nbsp;# dumpEnv();
&nbsp;&nbsp;&nbsp;&nbsp;# check environment
&nbsp;&nbsp;&nbsp;&nbsp;$proxy=$ENV{HTTP_X_FORWARDED_FOR};
&nbsp;&nbsp;&nbsp;&nbsp;if(defined($proxy) &amp;&amp; length($proxy)) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print STDERR "[".scalar(localtime())."] proxy request forwarded for: $proxy\n";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# if we have a X-Forwarded-For header, it was most likely
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# added by the system that sent the request
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$proxy=$ENV{REMOTE_ADDR};
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$isProxy++;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;# check remote address
&nbsp;&nbsp;&nbsp;&nbsp;unless($isProxy) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$proxy=$ENV{REMOTE_ADDR};
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print STDERR "[".scalar(localtime())."] probing for open proxy: $proxy\n";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my $p=HTTP::CheckProxy-&gt;new($proxy,qq{http://www.google.com/});
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$isProxy++ if($p-&gt;guilty());
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;if($isProxy) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print STDERR "[".scalar(localtime())."] comment denied - " .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"using proxy: $proxy - " .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;join("/",$comment-&gt;author,$comment-&gt;email,$comment-&gt;ip) .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"\n";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return DENY_RESPONSE;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;return ACCEPT_RESPONSE;
}
sub dumpEnv {
&nbsp;&nbsp;&nbsp;&nbsp;print STDERR "[".scalar(localtime())."] environment for $0:\n";
&nbsp;&nbsp;&nbsp;&nbsp;foreach my $key (sort keys %ENV) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print STDERR "[".scalar(localtime())."]   $key = ",$ENV{$key},"\n";
&nbsp;&nbsp;&nbsp;&nbsp;}
}
1;
</pre>]]></content:encoded> <wfw:commentRss>http://www.kahunaburger.com/2005/01/13/deny-comment-spam-from-open-proxies-in-movabletype/feed/</wfw:commentRss> <slash:comments>2</slash:comments> </item> <item><title>Spam Assassin and Movable Type</title><link>http://www.kahunaburger.com/2005/01/11/spam-assassin-and-movable-type/</link> <comments>http://www.kahunaburger.com/2005/01/11/spam-assassin-and-movable-type/#comments</comments> <pubDate>Tue, 11 Jan 2005 16:34:25 +0000</pubDate> <dc:creator>Tobias</dc:creator> <category><![CDATA[Perl]]></category> <category><![CDATA[Spam]]></category> <guid
isPermaLink="false">http://www.kahunaburger.com/2005/01/11/spam-assassin-and-movable-type/</guid> <description><![CDATA[Update 2005/01/15: Please consider combining the plugin below with mt-proxyplug for best results. A few days ago I saw the post on ioerror.us which details a solution to link WordPress&#8217;s comment checking system with Spam Assassin. I run MovableType and a WordPress solution does not work for me. The code needed to change a bit [...]]]></description> <content:encoded><![CDATA[<p><b><i>Update 2005/01/15</i>: Please consider combining the plugin below with <a
href="/blog/archives/000192.html">mt-proxyplug</a> for best results</b>.</p><p>A few days ago I saw the <a
href="http://www.ioerror.us/wp-spamassassin/" target="_blank">post on ioerror.us</a> which details a solution to link WordPress&#8217;s comment checking system with Spam Assassin. I run MovableType and a WordPress solution does not work for me. The code needed to change a bit before it was usable on my system.</p><p>After enabling it last night and disabling mt-blacklist, I&#8217;m happy to report that it has caught every single comment spam attempt (a total of 32 attempts were registered). Spam indications appear in my server&#8217;s error_log like this:</p><pre>
[Tue Jan 11 08:33:34 2005] spam from diet pills/jane_doe7082@work.com/
148.244.150.58: score 10.7 (limit 5.0)
</pre><p>A message like this indicates that the &#8216;CommentFilter&#8217; implemented in mt-spamassassin.pl has received notification from the Spam Assassin daemon that the current comment is over the Spam Assassin threshold.</p><p>In order to use the mt-spamassassin.pl plugin you will need to have Spam Assassin&#8217;s spamd running on your own network or need access to spamd running on a remote system. Enter the name of the system that runs spamd in <code>$sa_spamd_host</code> (use &#8216;localhost&#8217; if it&#8217;s running on the same host as MovableType) and also enter the port number where spamd can be reached in <code>$sa_spamd_port</code>. And because I did not find a way to retrieve a blog owners email address from within the MoveableType plugin, please also enter your email address in <code>$mt_owner</code>. For SpamAssassin&#8217;s user_prefs to work, you should also set your real (unix) userid in <code>$mt_userid</code>. Drop the modified file in your blog&#8217;s plugins folder and it should be ready to go.</p><p>Thanks to <a
href="http://www.ioerror.us/" target="_blank">http://www.ioerror.us/</a> for the cool idea!</p><p>You can download the compressed version here: <a
href="/tmp/mt-spamassassin.pl.gz">mt-spamassassin.pl.gz (1.5 Kb,gzip)</a></p><p><i><b>Update 01/14/2005:</b> I&#8217;ve since added another plugin called <a
href="/blog/archives/000191.html">mt-commentproxyblock</a>, which has detected <b>every single spam submission on 01/13/2005</b> before it was passed through mt-spamassassin. It seems that the majority of spammers do use public proxies and those are easy to detect.</i></p><p><i><b>Update 01/20/2005:</b> I just posed a new version of the plugin with a few enhancements. If you have both mt-spamassassin and <a
href="/blog/archives/000192.html">mt-proxyplug</a> on your system, a comment will be shortcut if mt-proxyplug has already determined that it comes from an open proxy. Specifically, mt-spamassassin will look at the visible-flag of the comment and will not work on comments which are not visible. This will cut down on processing time for spam comments.<br
/> Second, <a
href="http://taint.org/">Justin</a> was nice enough to correct the fake Message-header I&#8217;ve been sending to spamd to make it more RFC-2822 compliant. Thanks!<br
/> Third, you can now specify a <code>$mt_moderate</code> threshold value. This means that if a comment submission is below the Spam threshold (defined in Spam Assassin), but above the <code>$mt_moderate</code> value, it will be moderated instead of being allowed all the way through to the blog. </i></p><pre>
#!/usr/bin/perl -w
package MT::Plugin::SpamAssassin;
use strict;
use lib '../lib';
use vars qw ($VERSION);
$VERSION='0.4';
# (CHANGE ME) what host is running spamd?
my $sa_spamd_host = q{localhost};
# (CHANGE ME) what port is spamd listening on?
my $sa_spamd_port = 783;
# (CHANGE ME) who is the owner of the blog?
my $mt_owner      = q{me@localhost.com};
# (CHANGE ME) what is the userid for SpamAssassin?
my $mt_userid     = q{me};
# (CHANGE ME) what is the moderate threshold?
my $mt_moderate   = 1.5;
use constant ACCEPT_RESPONSE =&gt; 1;
use constant DENY_RESPONSE   =&gt; 0;
use MT;
use MT::App::Comments;
use IO::Socket;
use Time::Local qw(timegm);
use POSIX;
eval{ require MT::Plugin };
unless ($@) {
&nbsp;&nbsp;&nbsp;&nbsp;my $plugin = {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name =&gt; qq{Spamassassin for Movable Type v$VERSION},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;description =&gt; qq{Spamassassin for Movable Type},
&nbsp;&nbsp;&nbsp;&nbsp;};
&nbsp;&nbsp;&nbsp;&nbsp;MT-&gt;add_plugin(new MT::Plugin($plugin));
&nbsp;&nbsp;&nbsp;&nbsp;# tell MT that we want to be called to filter comments
&nbsp;&nbsp;&nbsp;&nbsp;MT-&gt;add_callback('CommentFilter', 10, $plugin, \&amp;sa_filter);
}
# sa_filter
#
# 'CommentFilter' that is called for each attempt to post a comment
# on your blog. We'll pass the incoming comment to spamd running on
# $sa_spamd_host:$sa_spamd_port. If spamd responds with an indication
# that the comment was spam, then we'll repond with DENY_RESPONSE.
# If spamd says it's no spam or we can't get a good connection to
# spamd, we'll respond with ACCEPT_RESPONSE
sub sa_filter {
&nbsp;&nbsp;&nbsp;&nbsp;my($eh,$app,$comment)=@_;
&nbsp;&nbsp;&nbsp;&nbsp;unless($comment-&gt;visible()) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ACCEPT_RESPONSE;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;#print STDERR "[".scalar(localtime())."] mt-spamassassin: " .
&nbsp;&nbsp;&nbsp;&nbsp;#  join("/",$comment-&gt;author,$comment-&gt;email,$comment-&gt;url,$comment-&gt;ip) . "\n";
&nbsp;&nbsp;&nbsp;&nbsp;my $now=rfc822_date();
&nbsp;&nbsp;&nbsp;&nbsp;my $hostname=gethostbyaddr(inet_aton($comment-&gt;ip), AF_INET);
&nbsp;&nbsp;&nbsp;&nbsp;my $message="From " . $comment-&gt;email . " " . $now . "\n" .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Received: from client ([" . $comment-&gt;ip . "] ".
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;($hostname?$hostname:$comment-&gt;ip) . ")" .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" by " . $ENV{HTTP_HOST} . " via MovableType; " . $now . "\n" .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Message-id: &lt;". sprintf("%x\$%x",time,rand(65535)) .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"\@" . ($hostname?$hostname:sprintf("[%s]",$comment-&gt;ip)) . "&gt;\n" .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"From: " . $comment-&gt;author .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" &lt;" . $comment-&gt;email . "&gt;\nDate: " . $now . "\n" .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Subject: MovableType comment\n" .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"To: $mt_owner\n\n" .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$comment-&gt;url . "\n".
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$comment-&gt;text;
&nbsp;&nbsp;&nbsp;&nbsp;# make sure all lines end in "\r\n";
&nbsp;&nbsp;&nbsp;&nbsp;$message =~ s/\r\n/\n/gs;
&nbsp;&nbsp;&nbsp;&nbsp;$message =~ s/\r/\n/gs;
&nbsp;&nbsp;&nbsp;&nbsp;$message =~ s/\n/\r\n/gs;
&nbsp;&nbsp;&nbsp;&nbsp;# now send it off to Spamassassin
&nbsp;&nbsp;&nbsp;&nbsp;my $socket=IO::Socket::INET-&gt;new(PeerAddr =&gt; $sa_spamd_host,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PeerPort =&gt; $sa_spamd_port,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Proto    =&gt; "tcp",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Type     =&gt; SOCK_STREAM);
&nbsp;&nbsp;&nbsp;&nbsp;# no socket - no spam check
&nbsp;&nbsp;&nbsp;&nbsp;return ACCEPT_RESPONSE unless($socket);
&nbsp;&nbsp;&nbsp;&nbsp;# create the CHECK message for spamd
&nbsp;&nbsp;&nbsp;&nbsp;$message = "CHECK SPAMC/1.2\r\n" .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"User: $mt_userid\r\n" .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Content-Length: ".length($message).
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"\r\n\r\n".
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$message;
&nbsp;&nbsp;&nbsp;&nbsp;# print STDERR "[".scalar(localtime())."] sending to spamd:\n$message\n";
&nbsp;&nbsp;&nbsp;&nbsp;# send it to spamd
&nbsp;&nbsp;&nbsp;&nbsp;my $toSend=$message;
&nbsp;&nbsp;&nbsp;&nbsp;while(length($toSend)) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my $written = $socket-&gt;send($toSend);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless(defined($written)) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# oh no, something went wrong <img src='http://www.kahunaburger.com/wp-includes/images/smilies/icon_sad.gif' alt=':-(' class='wp-smiley' />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ACCEPT_RESPONSE;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$toSend=substr($toSend,$written);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;# close writing end of socket
&nbsp;&nbsp;&nbsp;&nbsp;$socket-&gt;shutdown(1);
&nbsp;&nbsp;&nbsp;&nbsp;# suck in response from SpamAssassin
&nbsp;&nbsp;&nbsp;&nbsp;my $response;
&nbsp;&nbsp;&nbsp;&nbsp;while(1) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my $buffer;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless(defined($socket-&gt;recv($buffer, 1024))) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ACCEPT_RESPONSE;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;last unless(length($buffer));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$response .= $buffer;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;# trim  whitespace off the beginning of the response
&nbsp;&nbsp;&nbsp;&nbsp;$response =~ s/^\s*//;
&nbsp;&nbsp;&nbsp;&nbsp;# check if it is really a SpamAssassin response
&nbsp;&nbsp;&nbsp;&nbsp;return ACCEPT_RESPONSE unless ($response =~ /^spamd\/[\d\.]+/i);
&nbsp;&nbsp;&nbsp;&nbsp;# now find "Spam: True|False ; score / limit" header
&nbsp;&nbsp;&nbsp;&nbsp;return ACCEPT_RESPONSE
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless ($response =~ /spam:\s*(\S+)\s*;\s*([\d\.]+)\s*\/\s*([\d\.]+)/is);
&nbsp;&nbsp;&nbsp;&nbsp;my($flag,$score,$limit)=($1,$2,$3);
&nbsp;&nbsp;&nbsp;&nbsp;#if($flag =~ /false/i) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#print STDERR "[".scalar(localtime())."] no spam:\n$message\n";
&nbsp;&nbsp;&nbsp;&nbsp;#}
&nbsp;&nbsp;&nbsp;&nbsp;print STDERR "[".scalar(localtime())."] spam $flag from " .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;join("/",$comment-&gt;author,$comment-&gt;email,$comment-&gt;ip) .
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;": score $score (limit $limit)\n";
&nbsp;&nbsp;&nbsp;&nbsp;if($flag =~ /false/i) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if($score &gt; $mt_moderate) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print STDERR "[".scalar(localtime())."] moderating comment\n";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$comment-&gt;visible(0);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ACCEPT_RESPONSE;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;# log a line to the error_log
&nbsp;&nbsp;&nbsp;&nbsp;return DENY_RESPONSE;
}
# rfc822_date
#
# generate a GMT date according to rfc822
sub rfc822_date {
&nbsp;&nbsp;&nbsp;&nbsp;# offset in hours (from Mail::Sendmail)
&nbsp;&nbsp;&nbsp;&nbsp;my $offset  = sprintf "%.1f", (timegm(localtime) - time) / 3600;
&nbsp;&nbsp;&nbsp;&nbsp;my $minutes = sprintf "%02d", abs( $offset - int($offset) ) * 60;
&nbsp;&nbsp;&nbsp;&nbsp;my $TZ  = sprintf("%+03d", int($offset)) . $minutes;
&nbsp;&nbsp;&nbsp;&nbsp;return POSIX::strftime("%a, %d %b %Y %T $TZ",localtime(time()));
}
1;
</pre>]]></content:encoded> <wfw:commentRss>http://www.kahunaburger.com/2005/01/11/spam-assassin-and-movable-type/feed/</wfw:commentRss> <slash:comments>4</slash:comments> </item> <item><title>Caching RSS Proxy in perl</title><link>http://www.kahunaburger.com/2004/12/10/caching-rss-proxy-in-perl/</link> <comments>http://www.kahunaburger.com/2004/12/10/caching-rss-proxy-in-perl/#comments</comments> <pubDate>Fri, 10 Dec 2004 13:14:49 +0000</pubDate> <dc:creator>Tobias</dc:creator> <category><![CDATA[Perl]]></category> <guid
isPermaLink="false">http://www.kahunaburger.com/2004/12/10/caching-rss-proxy-in-perl/</guid> <description><![CDATA[Just recently somebody asked me how I would go about creating a caching RSS proxy and here&#8217;s one potential solution to the problem. All it requires is a web server, some perl and access to a mysql database. The background for this script was the following situation: Imagine a big company with a few thousand [...]]]></description> <content:encoded><![CDATA[<p>Just recently somebody asked me how I would go about creating a caching RSS proxy and here&#8217;s one potential solution to the problem. All it requires is a web server, some perl and access to a mysql database.<br
/> <span
id="more-185"></span><br
/> The background for this script was the following situation: Imagine a big company with a few thousand people. All those people work behind a firewall and there are only a few public (transparent) proxies that take all the traffic from the inside of the firewall to the outside. This means that only those few IP-addresses of the proxies will be visible to web servers accessed from all employees.</p><p>Now add RSS to the mix. Imagine a few hundred of those employees point at popular RSS feeds out there. Even if each individual employee follows the rules set by the RSS feed provider and only checks the feed every 1-2 hrs (or whatever else the minimum update time may be), the RSS server will still see a few hundred hits from the same set of IP-addresses over time. And there have been situations where a server administrator has shut down access to the RSS feed because of obvious violation of the rules (too many accesses to the feed in too little time from the same IP-address).</p><p>What&#8217;s needed in this case is a caching proxy server where all people behind the firewall funnel their feed requests to an internal system. The internal system will check (based on the URL of the feed) whether it has downloaded the RSS feed recently and will provide the cached copy instead of bothering the RSS server. Once the minimum time between updates has passed, the proxy will get a fresh copy of the RSS feed.</p><p>The script below will do exactly what just mentioned. Besides it&#8217;ll also do some house-keeping on the feeds, display a list of all available feeds when hit without any argument and compress feed contents before storing them in the database.</p><p>In order to use it, you would do the following:<br
/> 1) let&#8217;s assume you install the script at http://rssproxy.mycompany.com/rssproxy.pl<br
/> 2) you want to provide a proxy for the slashdot rss feed at <a
href="http://slashdot.org/index.rss">http://slashdot.org/index.rss</a><br
/> 3) you would ask your users to use the feed http://rssproxy.mycompany.com/rssproxy.pl/slashdot.org/index.rss instead of the original feed.</p><p>Hitting the proxy via http://rssproxy.mycompany.com/rssproxy.pl would display a list of all known feeds and how often they&#8217;ve been used, etc.</p><p>Just after I had finished this I found out that <a
href="http://www.rsscache.com/">http://www.rsscache.com/</a> is providing the same functionality as a public service.</p><p>Hope one or the other person still finds it useful.</p><div
class="codebox"> #!/usr/bin/perl</p><p>use strict;<br
/> use CGI;<br
/> use Compress::Zlib;<br
/> use DBI;<br
/> use LWP::UserAgent;<br
/> use HTTP::Response;</p><p># how often do we fetch the rss feed from a site? (every hour in this case)<br
/> use constant CHECKTIME =&gt; 1*60*60;<br
/> # what is our own URL?<br
/> use constant SELF =&gt; qq{http://rssproxy.mycompany.com/rssproxy.pl};<br
/> # what user agent string should we use if the client does not pass one?<br
/> use constant USERAGENT =&gt; qq{SharpReader/0.9.4.1 (.NET CLR 1.1.4322.573; WinNT 5.1.2600.0)};</p><p># how do we access the database?<br
/> my $dbi_db             =&#8221;rssproxy&#8221;;<br
/> my $dbi_user           =&#8221;username&#8221;;<br
/> my $dbi_passwd         =&#8221;password&#8221;;<br
/> my $dbi_datasource     =&#8221;dbi:mysql:$dbi_db&#8221;;</p><p>#<br
/> # database definition (mysql 4.1.3):<br
/> #<br
/> # CREATE TABLE `rssproxy` (<br
/> #   `id` int(11) NOT NULL auto_increment,<br
/> #   `url` varchar(255) NOT NULL default &#8221;,<br
/> #   `lastcheck` int(10) unsigned default &#8217;0&#8242;,<br
/> #   `lastrequest` int(10) unsigned default &#8217;0&#8242;,<br
/> #   `hits` int(10) unsigned default &#8217;0&#8242;,<br
/> #   `added` int(10) unsigned default &#8217;0&#8242;,<br
/> #   `rss` mediumtext NOT NULL,<br
/> #   PRIMARY KEY  (`id`),<br
/> #   UNIQUE KEY `index_url` (`url`)<br
/> # ) ENGINE=MyISAM DEFAULT CHARSET=latin1;</p><p># &#8212; main &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br
/> my $cgi=new CGI;<br
/> # get path_info and substract initial &#8220;/&#8221;<br
/> my $pinfo=$cgi-&gt;path_info(); $pinfo =~ s/^\///;<br
/> # connect to database<br
/> my $dbh=DBI-&gt;connect($dbi_datasource,<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$dbi_user,<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$dbi_passwd,<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ PrintError =&gt; 0 }<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;) || die &#8220;Can&#8217;t connect to database! &#8211; $DBI::errstr&#8221;;<br
/> # did somebody supply path_info?<br
/> unless(defined($pinfo) &amp;&amp; length($pinfo)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# no path_info &#8211; let&#8217;s list all RSS feeds we know about<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print $cgi-&gt;header();<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print qq{&lt;table border=&#8221;1&#8243;&gt;&lt;tr bgcolor=&#8221;#cccccc&#8221;&gt;&lt;td colspan=&#8221;7&#8243;&gt;}.<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;qq{&lt;center&gt;&lt;b&gt;rss proxy entries (tobias\@kahunaburger.com)&lt;/b&gt;&lt;/center&gt;&lt;/td&gt;&lt;/tr&gt;\n};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# table header<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print qq{&lt;tr bgcolor=&#8221;#dddddd&#8221;&gt;&lt;td&gt;id&lt;/td&gt;&lt;td&gt;added&lt;/td&gt;&lt;td&gt;lastcheck&lt;/td&gt;};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print qq{&lt;td&gt;lastrequest&lt;/td&gt;&lt;td&gt;hits&lt;/td&gt;&lt;td&gt;rss length&lt;/td&gt;&lt;td&gt;url&lt;/td&gt;&lt;/tr&gt;\n};</p><p>&nbsp;&nbsp;&nbsp;&nbsp;# list all database entries<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my $sth=$dbh-&gt;prepare_cached(qq{select id, url,<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;           added, lastcheck, lastrequest,<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hits, length(rss)<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    from rssproxy});<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$sth-&gt;execute();<br
/> &nbsp;&nbsp;&nbsp;&nbsp;while(my $a = $sth-&gt;fetchrow_arrayref()) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# &#8216;id&#8217; and &#8216;added&#8217;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my $line=qq{&lt;tr&gt;&lt;td&gt;$a-&gt;[0]&lt;/td&gt;&lt;td&gt;}.scalar(localtime($a-&gt;[2])).qq{&lt;/td&gt;};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# &#8216;lastcheck&#8217; and &#8216;lastrequest&#8217;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$line .=qq{&lt;td&gt;}.scalar(localtime($a-&gt;[3])).qq{&lt;/td&gt;};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$line .=qq{&lt;td&gt;}.scalar(localtime($a-&gt;[4])).qq{&lt;/td&gt;};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# &#8216;hits&#8217; and &#8216;rss length&#8217;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$line .=qq{&lt;td&gt;}.$a-&gt;[5].qq{&lt;/td&gt;&lt;td&gt;$a-&gt;[6]&lt;/td&gt;};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# &#8216;url&#8217;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$line .=qq{&lt;td&gt;&lt;a href=&#8221;}.SELF.qq{/$a-&gt;[1]&#8220;&gt;$a-&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;\n};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print $line;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$sth-&gt;finish();<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$dbh-&gt;disconnect();<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print qq{&lt;/table&gt;\n};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# nothing more to do in this case<br
/> &nbsp;&nbsp;&nbsp;&nbsp;exit(0);<br
/> }</p><p># path_info supplied &#8211; let&#8217;s see if we know about the rss feed<br
/> my ($rss); # this will hold a HTTP::Response object<br
/> my $sth=$dbh-&gt;prepare_cached(qq{select id, lastcheck, rss, hits<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;from rssproxy<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;where url = ?});<br
/> $sth-&gt;execute(lc($pinfo));<br
/> my @row = $sth-&gt;fetchrow_array;<br
/> $sth-&gt;finish();<br
/> # if @row is defined it means that we have an entry for this feed<br
/> if(defined(@row) &amp;&amp; scalar(@row)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# is it more than CHECKTIME seconds since we last fetched the feed?<br
/> &nbsp;&nbsp;&nbsp;&nbsp;if(time() &#8211; $row[1] &gt; CHECKTIME) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# time to fetch a new copy<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$rss = fetchRSS(qq{http://}.$pinfo,$cgi-&gt;user_agent() || USERAGENT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;if(defined($rss)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;    # successfully fetched the feed<br
/> &nbsp;&nbsp;&nbsp;&nbsp;    my $sth=$dbh-&gt;prepare_cached(qq{update rssproxy set lastcheck = ?, rss = ?<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    where id = ?});<br
/> &nbsp;&nbsp;&nbsp;&nbsp;    $sth-&gt;execute(time(), compress($rss-&gt;as_string), $row[0]);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;    $sth-&gt;finish();<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}else{<br
/> &nbsp;&nbsp;&nbsp;&nbsp;    # not successful &#8211; use old copy<br
/> &nbsp;&nbsp;&nbsp;&nbsp;    $rss=HTTP::Response-&gt;parse(uncompress($row[2]));<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}else{<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# it&#8217;s not time to fetch a new feed &#8211; use existing copy from DB<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$rss=HTTP::Response-&gt;parse(uncompress($row[2]));<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# mark in database when record was last requested and increase hit counter<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my $sth=$dbh-&gt;prepare_cached(qq{update rssproxy set lastrequest = ?, hits = ?<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    where id = ?});<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$sth-&gt;execute(time(), $row[3]+1, $row[0]);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$sth-&gt;finish();<br
/> }else{<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# no such record yet &#8211; see if we can get rss<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$rss=fetchRSS(qq{http://}.$pinfo,$cgi-&gt;user_agent() || USERAGENT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;if(defined($rss)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# yes &#8211; we were successful in downloading the rss feed<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my $now=time();<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# create a new database record for this feed<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my $sth=$dbh-&gt;prepare_cached(qq{insert into rssproxy (url,lastcheck,lastrequest,added,rss,hits)<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;values (?,?,?,?,?,?)});<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$sth-&gt;execute(lc($pinfo),$now,$now,$now,compress($rss-&gt;as_string),1);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$sth-&gt;finish();<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> }</p><p>$dbh-&gt;disconnect();<br
/> # if we have $rss (HTTP::Response) send it back to the user<br
/> if(defined($rss)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print $rss-&gt;as_string;<br
/> } else {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print $cgi-&gt;header(-status =&gt; 401);<br
/> }</p><p># fetchRSS<br
/> #<br
/> # GET a given URL and return HTTP::Response if successful, otherwise return undef</p><p>sub fetchRSS {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my($url,$impersonate)=@_;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# create a new user-agent<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my $ua=new LWP::UserAgent();<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# we are going to wait for 2 mins max<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$ua-&gt;timeout(120);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# pretend to be someone else<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$ua-&gt;agent($impersonate);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# get the rss feed<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my $response=$ua-&gt;get($url);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# were we successful?<br
/> &nbsp;&nbsp;&nbsp;&nbsp;if($response-&gt;is_success) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return $response;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}else{<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return undef;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> }</p></div> ]]></content:encoded> <wfw:commentRss>http://www.kahunaburger.com/2004/12/10/caching-rss-proxy-in-perl/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>Veo.pm &#8211; a perl module to talk to the Veo Observer Network Cameras</title><link>http://www.kahunaburger.com/2004/08/12/veopm-a-perl-module-to-talk-to-the-veo-observer-network-cameras/</link> <comments>http://www.kahunaburger.com/2004/08/12/veopm-a-perl-module-to-talk-to-the-veo-observer-network-cameras/#comments</comments> <pubDate>Fri, 13 Aug 2004 01:34:21 +0000</pubDate> <dc:creator>Tobias</dc:creator> <category><![CDATA[Perl]]></category> <guid
isPermaLink="false">http://www.kahunaburger.com/2004/08/12/veopm-a-perl-module-to-talk-to-the-veo-observer-network-cameras/</guid> <description><![CDATA[Thanks to Brian G I finally found a way to talk to the Veo Observer Network Cameras from any platform that supports perl. As mentioned before I tried to stream images from the camera using a FreeBSD system, however the software supplied by Veo only supports Windows. I spent some time trying to decode the [...]]]></description> <content:encoded><![CDATA[<p>Thanks to <a
href="http://www.brianosaurus.org/" target="_blank">Brian G</a> I finally found a way to talk to the <a
href="http://www.veo.com/" target="_blank">Veo Observer Network Cameras</a> from any platform that supports perl. As mentioned <a
href="/blog/archives/000100.html">before</a> I tried to stream images from the camera using a FreeBSD system, however the software supplied by Veo only supports Windows. I spent some time trying to decode the data I received from the camera, but did not have any luck.</p><p>Yesterday, Brian G emailed me and asked me a question related to the article above. In passing he mentioned that he had received a response from Veo about the file-format. One email later I had the Veo-response in my hands and guess how suprised I was when I read this: <b>the data delivered from the camera is in JPEG format, it only misses a JPEG header!!!</b> DUH!!!</p><p>Along with the email I also received a 400 byte binary file with the header. After a few more hours of work (tacking on the header was not enough), I received my first full frame from the camera. I was happy to see that my decoding efforts were pretty darn close to the information that Brian forwarded.</p><p>So now I (and you) can access those cameras from any platform that supports perl and the <b>IO::Select</b> and <b>IO::Socket</b> modules.</p><p>Here&#8217;s a little sample script that saves 10 frames at 640 x 480 at 1 frame/sec:</p><blockquote><div
class="codebox"> #!/usr/bin/perl -w</p><p>use strict;<br
/> use Veo;</p><p>my $veo=Veo-&gt;new(host =&gt; &#8217;192.168.1.1&#8242;, port =&gt; 1600);<br
/> $veo-&gt;login(user =&gt; &#8216;admin&#8217;, password =&gt; &#8216;password&#8217;);<br
/> my($images)=10;<br
/> $veo-&gt;selectStream(Veo::VEO_STREAM_640X480,1);<br
/> $veo-&gt;stream(\&amp;cb);</p><p>sub cb {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my($type,$frame,$data)=@_;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print STDERR qq{image $frame with },length($data),qq{ bytes\n};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;open(OUT,&#8221;&gt;&#8221;.sprintf(&#8220;veo-%02d.jpg&#8221;,$images));<br
/> &nbsp;&nbsp;&nbsp;&nbsp;binmode(OUT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print OUT $data;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;close(OUT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# return value &gt; 0 indicates &#8220;keep on streaming&#8221;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# return value = 0 indicates &#8220;stop streaming&#8221;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;return $images&#8211;;<br
/> }</div></blockquote><p>Easy &#8211; isn&#8217;t it?</p><p>And here&#8217;s another sample. This time we use the move() method to pan the camera head and take pictures after each step. This script will logon to the camera, move the head all the way to the left and then slowly move to the right while taking a single frame after each step to the right.</p><blockquote><div
class="codebox"> #!/usr/bin/perl -w</p><p>use strict;<br
/> use lib &#8220;.&#8221;;<br
/> use Veo;</p><p>my $veo=Veo-&gt;new(host =&gt; &#8217;192.168.1.1&#8242;, port =&gt; 1600);<br
/> $veo-&gt;login(user =&gt; &#8216;admin&#8217;, password =&gt; &#8216;password&#8217;);<br
/> $veo-&gt;selectStream(Veo::VEO_STREAM_320X240,1);<br
/> my($rc);<br
/> # move all the way to the left<br
/> while($rc=$veo-&gt;move(Veo::VEO_MOVE_FULL_LEFT)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;last if($rc != Veo::OK);<br
/> }<br
/> # now take one frame at a time and move right<br
/> do {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$veo-&gt;stream(sub {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# args are: frametype, framenumber, framedata<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;open(OUT,&#8221;&gt;frame-&#8221;.sprintf(&#8220;%04d&#8221;,$_[1]).&#8221;.jpg&#8221;) || return 0;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;binmode(OUT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print OUT $_[2];<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;close(OUT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$rc=$veo-&gt;move(Veo::VEO_MOVE_RIGHT);<br
/> } while($rc == Veo::OK);</div></blockquote><p>As it says in the documentation of the module, I&#8217;m going to finish this up over the next few days, but I&#8217;ve been asked soo often that I decided to release this piece half-baked. Have fun with the <a
href="/tmp/Veo.pm">Veo.pm</a> (right-click, save as &#8230;, the usual) perl module and let me know if it works or doesn&#8217;t work for you.</p><p><b><i>Update 8/16/2004:</i></b></p><p>James A. Russo quickly converted the perl-module into something Java-people can use. Hop on over to <a
href="http://www.halo3.net/">http://www.halo3.net/</a> to find the Java-classes and some more information. James also used the Veo.pm perl module in a quick perl CGI to retrieve images from the camera and move the camera position. Here it is (thanks James):</p><blockquote><div
class="codebox"> #!/usr/bin/perl</p><p>#       webveo.pl &#8211; James A. Russo jr@halo3.net &#8211; August 14th, 2004.<br
/> # <br
/> #       Quick and dirty script allowing a Veo Observer Network Camera to be used from a simple web page. <br
/> #<br
/> #       It will redirect you to the $URL variable when moving. So a simple webpage like this should work: <br
/> #<br
/> #               &#8230;<br
/> #               &lt;img src=&#8221;/path/to/webveo.cgi?action=getimage&#8221;&gt;<br
/> #               &lt;a href=&#8221;/path/to/webveo.cgi?move=fullup&#8221;&gt;Move Up&lt;/a&gt;<br
/> #               &#8230;<br
/> #<br
/> #<br
/> #       This script uses Veo.pm from Tobias Hoellrich. See http://www.kahunaburger.com/blog/archives/000157.html for more info.<br
/> #</p><p>use CGI;<br
/> use Veo;<br
/> use Fcntl qw/ :flock /;</p><p>my $USER = &#8220;admin&#8221;;<br
/> my $PASSWORD = &#8220;password&#8221;;<br
/> my $HOST = &#8220;192.168.1.142&#8243;;<br
/> my $PORT = 1600;</p><p># Other options for STREAM.<br
/> # Veo::VEO_STREAM_640X480;<br
/> # Veo::VEO_STREAM_160X120;<br
/> my $STREAM = Veo::VEO_STREAM_320X240;<br
/> my $URL= &#8220;index.html&#8221;;</p><p>## Nothing below this line needs editing..</p><p>my $CGI = new CGI;<br
/> my $ACTION = $CGI-&gt;param(&#8216;action&#8217;);<br
/> my $MOVE = $CGI-&gt;param(&#8216;move&#8217;);</p><p># We synchronize since two admins can&#8217;t log in at once.<br
/> open FL, &#8220;&gt;.lock&#8221; || die &#8220;Unable to open lock file.&#8221;;<br
/> flock(FL,LOCK_EX);</p><p>my $VEO = Veo-&gt;new(host =&gt; $HOST, port =&gt; $PORT);</p><p>$VEO-&gt;login(user =&gt; $USER, password =&gt; $PASSWORD);</p><p>if ($MOVE) { <br
/> &nbsp;&nbsp;&nbsp;&nbsp;if ($MOVE eq &#8220;up&#8221;) { <br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;move(Veo::VEO_MOVE_UP);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;} elsif ($MOVE eq &#8220;fullup&#8221;) { <br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;move(Veo::VEO_MOVE_FULL_UP);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;} elsif ($MOVE eq &#8220;down&#8221;) { <br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;move(Veo::VEO_MOVE_DOWN);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;} elsif ($MOVE eq &#8220;fulldown&#8221;) { <br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;move(Veo::VEO_MOVE_FULL_DOWN);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;} elsif  ($MOVE eq &#8220;left&#8221;) { <br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;move(Veo::VEO_MOVE_LEFT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;} elsif ($MOVE eq &#8220;fullleft&#8221;) { <br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;move(Veo::VEO_MOVE_FULL_LEFT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;} elsif  ($MOVE eq &#8220;right&#8221;) { <br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;move(Veo::VEO_MOVE_RIGHT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;} elsif  ($MOVE eq &#8220;fullright&#8221;) { <br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;move(Veo::VEO_MOVE_FULL_RIGHT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;logout;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print &#8220;Location: $URL\n\n&#8221;;<br
/> } elsif ($ACTION eq &#8220;getimage&#8221;) { <br
/> &nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;selectStream($STREAM,1);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;stream(\&amp;image_cb);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# callback will be called once, and then we will get here..<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$VEO-&gt;logout;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# unflock happens on exit..<br
/> &nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br
/> } else { <br
/> &nbsp;&nbsp;&nbsp;&nbsp;print &#8220;Content-type: text/html\n\n&#8221;;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print &#8220;Unknown action: $ACTION\n&#8221;;<br
/> }</p><p>sub image_cb {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my($type,$frame,$data)=@_;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print &#8220;Content-type: image/jpeg\n\n&#8221;;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print $data;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;# We only want the one image&#8230;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;return 0;<br
/> }</div></blockquote> ]]></content:encoded> <wfw:commentRss>http://www.kahunaburger.com/2004/08/12/veopm-a-perl-module-to-talk-to-the-veo-observer-network-cameras/feed/</wfw:commentRss> <slash:comments>28</slash:comments> </item> <item><title>Perl bringing Pia to the web</title><link>http://www.kahunaburger.com/2004/02/27/perl-bringing-pia-to-the-web/</link> <comments>http://www.kahunaburger.com/2004/02/27/perl-bringing-pia-to-the-web/#comments</comments> <pubDate>Fri, 27 Feb 2004 12:58:32 +0000</pubDate> <dc:creator>Tobias</dc:creator> <category><![CDATA[Perl]]></category> <guid
isPermaLink="false">http://www.kahunaburger.com/2004/02/27/perl-bringing-pia-to-the-web/</guid> <description><![CDATA[In Big brother watching over little sister I mentioned a primitive motion detection application that grabs frames from the wireless network camera in Pia&#8217;s nursery and publishes them through (a secret page on) KahunaBurger. I&#8217;ve got a number of requests for details about this application. Here they are &#8230; In Pia&#8217;s nursery we have a [...]]]></description> <content:encoded><![CDATA[<p>In <a
href="/blog/archives/000086.html">Big brother watching over little sister</a> I mentioned a primitive motion detection application that grabs frames from the wireless network camera in <a
href="/photo/favorites/pia">Pia&#8217;s</a> nursery and publishes them through (a secret page on) KahunaBurger. I&#8217;ve got a number of requests for details about this application. Here they are &#8230;<br
/> <span
id="more-114"></span><br
/> In Pia&#8217;s nursery we have a <a
href="http://www.veo.com/Observer-Wireless/default.asp" target="_blank">Veo Observer Wireless Network Camera</a>. Great camera, but it has two <b>major</b> flaws: The supplied software only allows you to view the camera from a PC (using an Active-X control) and only one person at a time is allowed to connect to the camera. These restrictions make the camera essentially unusable for the kind of task I&#8217;m using it for.</p><p>After downloading the Camera SDK from Veo&#8217;s web site I created a minimal application that would connect to the camera on fixed interverals and store a JPG frame on the harddisk. This happens on one of my Windows PCs, because the SDK is only available for Windows (I&#8217;m still looking for a way to decode the XJPG frames). The JPG file is then shipped to my FreeBSD box. The FreeBSD system also happens to be the web server for kahunaburger.com.</p><p>Again on a fixed interval, the FreeBSD system will look at the new file and compare the file to the &#8220;lastest&#8221; camera grab. If it detects a certain amount of difference between the two frames, it will start the process of saving the current frame. &#8220;Saving the current frame&#8221; involves:<ul><li>copying the &#8220;latest&#8221; image to the &#8220;last&#8221; image</p><li>copying the &#8220;current&#8221; image to the &#8220;latest&#8221; image<li>generating a thumbnail for the &#8220;latest&#8221; image<li>maintaining a list of up to 80 saved frames<li>maintaining a list of up to 80 thumbnails<li>regenerating a HTML page that shows those thumbnails and saved frames</ul><p>Here is a small section from the HTML-page generated by the scaript. This section shows the table that holds the thumbnails for all saved frames. As you hover over each thumbnail, the big picture is automatically updated, showing you a full-size picture of the frame the mouse is currently over.</p><p><img
src="/images/motionscreen.jpg" alt="Screenshot of Pia's page"></p><p>The code uses pretty much only standard stuff besides the <code>Imager</code> and the <code>HTML::Template</code> modules. Both of them are available on CPAN and/or through ActiveState&#8217;s ppm.</p><p>I&#8217;m not supplying the index.template file mentioned in the script, because mine is tuned for the kahunaburger.com environment and will not work for you anyway. Just make sure you have a template variable <code>content</code> in the body and a variable <code>js</js> in the head.</p><p>Let's me know, if you have questions.</p><blockquote><div
class="codebox"><p><i><b>#!/usr/bin/perl -w</b></i></p><p><i><b># we are always strict - aren't we?</b></i><br
/> use strict;</p><p>use File::Spec;<br
/> use File::stat;<br
/> use File::Copy;</p><p>use Imager;<br
/> use HTML::Template;</p><p><i><b># use the FORCE to generate the HTML page even if no change was detected</b></i><br
/> use constant FORCE        =&gt; 0;</p><p><i><b># what's the URL for the page?</b></i><br
/> use constant THISURL      =&gt; qq{http://www.yourserver.com/motion/};<br
/> <i><b># what's the filesystem path for the page?</b></i><br
/> use constant PATH         =&gt; qq{/mnt/apache/www.yourserver.com/htdocs/motion};</p><p><i><b># constants that determine when we keep an image</b></i><br
/> use constant DARKNESS     =&gt; 0.190;<br
/> use constant DIFFERENCE   =&gt; 1.000;<br
/> use constant GRAYNOISE    =&gt; 40;<br
/> use constant DIFFWIDTH    =&gt; 160;</p><p><i><b># where are the "latest" and "last" files kept?</b></i><br
/> use constant IMAGEDIR     =&gt; qq{pia};<br
/> use constant SOURCE       =&gt; qq{webcam.jpg};<br
/> use constant LATEST       =&gt; qq{latest.jpg};<br
/> use constant LAST         =&gt; qq{last.jpg};<br
/> use constant TIMESTAMP    =&gt; qq{.timestamp};</p><p><i><b># how many frames/thumbnails do we keep around?</b></i><br
/> use constant SAVEFRAMES   =&gt; 80;<br
/> use constant SAVEDIR      =&gt; qq{save};<br
/> use constant SAVEPREFIX   =&gt; qq{saved-};</p><p>use constant THUMBDIR     =&gt; qq{thumb};<br
/> <i><b># how wide are our thumbnails?</b></i><br
/> use constant THUMBWIDTH   =&gt; 80;</p><p><i><b># what's the filename for the templat file?</b></i><br
/> use constant HTMLTEMPLATE =&gt; qq{index.template};<br
/> <i><b># and where does the final html file go?</b></i><br
/> use constant HTMLFINAL    =&gt; qq{index.html};</p><p><i><b># go to "our" directory</b></i><br
/> unless(chdir(PATH)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print STDERR "unable to change directory to ",PATH,"\n";<br
/> &nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br
/> }</p><p><i><b># check if camera source exists</b></i><br
/> if(!-f SOURCE || -z _) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print STDERR "no source or empty source file\n";<br
/> &nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br
/> }<br
/> <i><b># check modification time of old source against</b></i><br
/> <i><b># camera source</b></i><br
/> my ($lastcheck)=0;<br
/> my $newsource=File::Spec-&gt;catfile(IMAGEDIR,SOURCE);<br
/> my $last=File::Spec-&gt;catfile(IMAGEDIR,LAST);<br
/> my $latest=File::Spec-&gt;catfile(IMAGEDIR,LATEST);<br
/> if(-f $latest) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my $tstat=stat($latest);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$lastcheck=$tstat-&gt;mtime;<br
/> }<br
/> my $sstat=stat(SOURCE);<br
/> if($lastcheck &gt;= $sstat-&gt;mtime) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print STDERR "no new data\n";<br
/> &nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br
/> }<br
/> <i><b># copy current source to latest and verify</b></i><br
/> unless(copy(SOURCE,$newsource) &amp;&amp; -f $newsource &amp;&amp; -s _ == $sstat-&gt;size) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print STDERR "invalid new source after copy\n";<br
/> &nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br
/> }<br
/> <i><b># copy access/modtime</b></i><br
/> utime($sstat-&gt;atime,$sstat-&gt;mtime,$newsource);<br
/> <i><b># we are good to go ... remove last (ignore errors)</b></i><br
/> unlink($last);<br
/> <i><b># move latest to last (ignore errors)</b></i><br
/> move($latest,$last);<br
/> <i><b># move newsource to latest (ignore errors)</b></i><br
/> move($newsource,$latest);<br
/> <i><b># open the image files</b></i><br
/> my $latestImage=Imager-&gt;new();<br
/> unless($latestImage-&gt;open(file=&gt;$latest)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print STDERR "cannot open $latest - ",$latestImage-&gt;errstr(),"\n";<br
/> &nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br
/> }<br
/> my $lastImage=Imager-&gt;new();<br
/> unless($lastImage-&gt;open(file=&gt;$last)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print STDERR "cannot open $last - ",$lastImage-&gt;errstr(),"\n";<br
/> &nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br
/> }</p><p>my(@savedFiles);<br
/> unless(opendir(DIR,SAVEDIR)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print STDERR "cannot open save directory\n";<br
/> }<br
/> while(my $entry=readdir(DIR)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my $filename=File::Spec-&gt;catfile(SAVEDIR,$entry);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;next unless (-f $filename);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;push(@savedFiles,[$entry,stat($filename)-&gt;mtime]);<br
/> }<br
/> close(DIR);<br
/> <i><b># sort files by modification time (newest first)</b></i><br
/> @savedFiles=sort { $b-&gt;[1] &lt;=&gt; $a-&gt;[1] } @savedFiles;</p><p><i><b># now check if this image is worth saving</b></i><br
/> if(FORCE || compareImages($lastImage,$latestImage)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;<i><b># looks like latest fits the criteria, let's save it to the</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;<i><b># motion directory</b></i></p><p>&nbsp;&nbsp;&nbsp;&nbsp;<i><b># trim number of saved files to SAVEFRAMES-1 (we are going to add one)</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;if(scalar(@savedFiles) &gt;= SAVEFRAMES - 1) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my(@oldFiles)=splice(@savedFiles,SAVEFRAMES - 1);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foreach (@oldFiles) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<i><b># remove saved frame</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unlink(File::Spec-&gt;catfile(SAVEDIR,$_-&gt;[0]));<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<i><b># remove thumbnail</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unlink(File::Spec-&gt;catfile(THUMBDIR,$_-&gt;[0]));<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;<i><b># generate a new filename</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;my $savedFile=SAVEPREFIX.time.qq{.jpg};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;<i><b># add to list of saved files</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;unshift(@savedFiles,[$savedFile,time]);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$latestImage-&gt;write(file=&gt;File::Spec-&gt;catfile(SAVEDIR,$savedFile));<br
/> &nbsp;&nbsp;&nbsp;&nbsp;<i><b># generate thumbnail</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;$latestImage=$latestImage-&gt;scale(xpixels=&gt;THUMBWIDTH);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$latestImage-&gt;write(file=&gt;File::Spec-&gt;catfile(THUMBDIR,$savedFile));<br
/> }<br
/> <i><b># synchronize the save/thumb folders to make sure we are not wasting space</b></i><br
/> <i><b># in the thumb dir</b></i><br
/> if(opendir(DIR,THUMBDIR)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my(@thumbsToRemove);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my(@filesInSaved)=(map { $_-&gt;[0] } @savedFiles);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;while(my $entry=readdir(DIR)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my $filename=File::Spec-&gt;catfile(THUMBDIR,$entry);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next unless (-f $filename);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<i><b># is the thumb also in the save directory?</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next if(grep(/^$entry$/i,@filesInSaved));<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;push(@thumbsToRemove,$filename);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;close(DIR);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;foreach(@thumbsToRemove) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unlink($_);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> }</p><p><i><b># generate the table for the images</b></i><br
/> my $t=qq{&lt;table border="1"&gt;};<br
/> my($rows)=0;<br
/> my($saved)=SAVEFRAMES;<br
/> while($saved) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;if(!($saved % 8)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if($saved != SAVEFRAMES) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$t.=qq{&lt;/tr&gt;\n};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if($rows++ == 4) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$t.=qq{&lt;tr&gt;&lt;td colspan="8"&gt;&lt;center&gt;&lt;img name="latest" src="$latest"&gt;&lt;div id="label"&gt;}.<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scalar(localtime()).qq{&lt;/div&gt;&lt;/center&gt;&lt;/td&gt;&lt;/tr&gt;\n};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$t.=qq{&lt;tr&gt;};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;if(scalar(@savedFiles)) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my $entry=shift(@savedFiles);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my $imgname=THUMBDIR.qq{/}.$entry-&gt;[0];<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my $simgname=SAVEDIR.qq{/}.$entry-&gt;[0];<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my $datestamp=scalar(localtime($entry-&gt;[1]));<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$t.=qq{&lt;td&gt;&lt;center&gt;&lt;a href="#" onmouseover="update('latest','$simgname','$datestamp')"&gt;}.<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;qq{&lt;img border="0" src="$imgname" alt="$datestamp"&gt;&lt;/a&gt;&lt;/center&gt;&lt;/td&gt;};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}else{<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$t.=qq{&lt;td&gt;&amp;nbsp;&lt;/td&gt;};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$saved--;<br
/> }<br
/> $t.=qq{&lt;/tr&gt;&lt;/table&gt;\n};<br
/> <i><b># we are ready to generate the HTML file</b></i><br
/> my $template = HTML::Template-&gt;new(<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;filename =&gt; HTMLTEMPLATE,<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;die_on_bad_params =&gt; 0,<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);<br
/> my $htmlfile=HTMLFINAL.$$;<br
/> unless(open(OUT,"&gt;$htmlfile")) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print STDERR "cannot write to $htmlfile - $!";<br
/> &nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br
/> }<br
/> <i><b># use this and a meta variable in your template to automatically refresh the page (300=5 mins)</b></i><br
/> $template-&gt;param('meta' =&gt; qq{&lt;META HTTP-EQUIV="refresh" content="300;URL=}.THISURL.qq{"&gt;});<br
/> $template-&gt;param('title' =&gt; "yourserver.com - motion cam - last update ".scalar(localtime()));<br
/> $template-&gt;param('content' =&gt; $t);<br
/> $template-&gt;param('js' =&gt; qq{<br
/> &lt;SCRIPT language="JavaScript"&gt;<br
/> &lt;!-- Begin<br
/> &nbsp;&nbsp;&nbsp;&nbsp;function browserdetect(){<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//returns 1 for IE, 2 for N6,3 for N4 and others<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(navigator.appName=="Microsoft Internet Explorer"){return 1;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if(navigator.appName=="Netscape" &amp;&amp; parseInt(navigator.appVersion)==5){return 2;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else{return 3;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}</p><p>&nbsp;&nbsp;&nbsp;&nbsp;function update(imgname,source,date_stamp) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var browsertype; browsertype=browserdetect();<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(browsertype==2){ //if N6<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;document.getElementById(imgname).src=source;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;document.all[imgname].src=source;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;el = document.all ? document.all('label') :<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;document.getElementById ? document.getElementById('label') : null;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (el) el.innerHTML = date_stamp;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> // End --&gt;<br
/> &lt;/SCRIPT&gt;});<br
/> print OUT $template-&gt;output;<br
/> close(OUT);<br
/> <i><b># remove current file</b></i><br
/> unlink(HTMLFINAL);<br
/> <i><b># and bring current one into place</b></i><br
/> move($htmlfile,HTMLFINAL);<br
/> exit(0);</p><p>sub compareImages {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my($img1,$img2)=@_;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;<i><b># scale down for comparison</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;$img1=$img1-&gt;scale(xpixels=&gt;DIFFWIDTH);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$img2=$img2-&gt;scale(xpixels=&gt;DIFFWIDTH);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;<i><b># convert to grayscale</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;$img1=$img1-&gt;convert(preset=&gt;'grey');<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$img2=$img2-&gt;convert(preset=&gt;'grey');<br
/> &nbsp;&nbsp;&nbsp;&nbsp;return diffImages($img2, $img1, DARKNESS, DIFFERENCE);<br
/> }</p><p>sub diffImages {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my($older,$newer,$darkness,$difference)=@_;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my($white)=$older-&gt;getwidth()*$older-&gt;getheight()*255;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my($d2,$s2)=("",0);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$newer-&gt;write(data=&gt;\$d2,type=&gt;'raw');<br
/> &nbsp;&nbsp;&nbsp;&nbsp;<i><b># check darkness of frame</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;map { $s2+=ord($_) } split(//,$d2);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;<i><b># do we have a dark frame?</b></i><br
/> &nbsp;&nbsp;&nbsp;&nbsp;if($s2/$white &lt;= $darkness) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my($d1,$s1)=("",0);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;$older-&gt;write(data=&gt;\$d1,type=&gt;'raw');<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my($i)=0;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;map { my $x=abs(ord($_)-vec($d2,$i++,8));<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$s1+=($x*$x) if($x&gt;=GRAYNOISE);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} split(//,$d1);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my $d=$s1/$white;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;if($d &lt; $difference) {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;}<br
/> &nbsp;&nbsp;&nbsp;&nbsp;return 1;<br
/> }</p></div></blockquote> ]]></content:encoded> <wfw:commentRss>http://www.kahunaburger.com/2004/02/27/perl-bringing-pia-to-the-web/feed/</wfw:commentRss> <slash:comments>3</slash:comments> </item> <item><title>Deconstructing the Veo Observer Net Camera</title><link>http://www.kahunaburger.com/2004/01/04/deconstructing-the-veo-observer-net-camera/</link> <comments>http://www.kahunaburger.com/2004/01/04/deconstructing-the-veo-observer-net-camera/#comments</comments> <pubDate>Mon, 05 Jan 2004 02:48:35 +0000</pubDate> <dc:creator>Tobias</dc:creator> <category><![CDATA[Perl]]></category> <guid
isPermaLink="false">http://www.kahunaburger.com/2004/01/04/deconstructing-the-veo-observer-net-camera/</guid> <description><![CDATA[I still want my daughter to appear on a network camera and I&#8217;m not giving up that easily (see Big Brother watching over little sister). The D-Link DCS-1000W has been replaced by a Veo Observer Wireless Network Camera. The camera quality is a lot better, but the software works on Windows only The Veo Observer [...]]]></description> <content:encoded><![CDATA[<p>I still want my daughter to appear on a network camera and I&#8217;m not giving up that easily (see <a
href="/blog/archives/000086.html">Big Brother watching over little sister</a>). The D-Link DCS-1000W has been replaced by a Veo Observer Wireless Network Camera. The camera quality is a lot better, but the software works on Windows only <img
src='http://www.kahunaburger.com/wp-includes/images/smilies/icon_sad.gif' alt=':-(' class='wp-smiley' /><br
/> <span
id="more-100"></span><br
/> The Veo Observer camera is controlled from within your browser using an Active-X control. No Unix solution. No Mac solution. Only one person at a time can access the camera. Those are some serious drawbacks and I believe that those limitations are &#8220;artificially manufactured&#8221; into the camera.</p><p>I need a solution that sits on a Unix system and grabs a frame at fixed time intervals. My software will then check whether it&#8217;s worth keeping the frame. The D-Link did that just fine and I&#8217;m determined to make that work with the Veo Observer as well.</p><p><b>Step 1</b> was to contact Veo&#8217;s manufacturers via a number of different channels. No matter where I sent my emails to, they were left unanswered. After trying to get some sort of contact for three times, I decided that this was not the correct route to solve my problem. Shame on Veo for not even responding with a simple &#8220;Sorry &#8211; we are too busy &#8230;&#8221;.</p><p><b>Step 2</b> was to download the camera SDK from the <a
href="http://www.veo.com/downloads/download.asp?software=sdkObserver" target="_blank">Veo Observer SDK</a> page. This SDK (again) is only available for Windows. I used it to create a minimal C-program that would connect to the camera, setup 640&#215;480 resolution, grab a frame, save it to a JPEG file and exit. As there was only one camera on the network, I decided to hard-code IP address, port, username and password in the program (you have got to change the defines at the top of the code). <b><i>If you&#8217;re planning to use the SDK, be prepared that there are some serious differences between the documentation and the way that the SDK actually works (somebody at Veo may want to clean this up &#8230;)</i></b></p><p>Here is the code using the Veo Observer Windows C++ SDK. After compiling the software and linking it with the SDK-libraries/DLLs, you end up with a program that sends a snapshot at 640&#215;480 to a filename specified on the program command line.</p><blockquote><div
class="codebox"> #include&nbsp;&lt;stdio.h&gt;<br
/> #include&nbsp;&lt;stdlib.h&gt;<br
/> #include&nbsp;&lt;windows.h&gt;</p><p>#include&nbsp;&#8221;xrlknc.h&#8221;</p><p>#define&nbsp;VEO_NETNAME&nbsp;&nbsp;&#8221;192.168.1.1&#8243;<br
/> #define&nbsp;VEO_PORT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1600<br
/> #define&nbsp;VEO_USER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8221;username&#8221;<br
/> #define&nbsp;VEO_PASSWORD&nbsp;&#8221;password&#8221;</p><p>int&nbsp;main(int&nbsp;argc,&nbsp;char&nbsp;*argv[])<br
/> {<br
/> &nbsp;&nbsp;PNET_CAM&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;veo;<br
/> &nbsp;&nbsp;PCAM_FORMAT&nbsp;&nbsp;&nbsp;&nbsp;fList;<br
/> &nbsp;&nbsp;ULONG&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fCount;<br
/> &nbsp;&nbsp;CAM_STREAM_DEF&nbsp;fCurrent;<br
/> &nbsp;&nbsp;CAMERR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;err;<br
/> &nbsp;&nbsp;CAM_SNAPSHOT_PARMS&nbsp;snapParams;<br
/> &nbsp;&nbsp;CAM_STREAMING_PARMS&nbsp;streamParms;</p><p>&nbsp;&nbsp;if(argc&nbsp;!=&nbsp;2)&nbsp;{<br
/> &nbsp;&nbsp;&nbsp;&nbsp;printf(&#8220;Usage:&nbsp;%s&nbsp;&lt;snapshot-filename&gt;\n&#8221;,argv[0]);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br
/> &nbsp;&nbsp;}</p><p>&nbsp;&nbsp;err&nbsp;=&nbsp;CamLogon(VEO_NETNAME,&nbsp;VEO_PORT,&nbsp;VEO_USER,&nbsp;VEO_PASSWORD,&nbsp;GetDesktopWindow(),&nbsp;WM_APP,&nbsp;&#038;veo);<br
/> &nbsp;&nbsp;if(err&nbsp;!=&nbsp;CAMERR_OKAY)&nbsp;{<br
/> &nbsp;&nbsp;&nbsp;&nbsp;printf(&#8220;Unable&nbsp;to&nbsp;logon&nbsp;to&nbsp;camera&nbsp;-&nbsp;error&nbsp;code&nbsp;%d\n&#8221;,err);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br
/> &nbsp;&nbsp;}</p><p>&nbsp;&nbsp;fCurrent.Format=2;<br
/> &nbsp;&nbsp;fCurrent.TimePerFrame=1000000;<br
/> &nbsp;&nbsp;err&nbsp;=&nbsp;CamSetStreamDef(veo,&#038;fCurrent);<br
/> &nbsp;&nbsp;if(err&nbsp;!=&nbsp;CAMERR_OKAY)&nbsp;{<br
/> &nbsp;&nbsp;&nbsp;&nbsp;printf(&#8220;Unable&nbsp;to&nbsp;set&nbsp;stream&nbsp;def&nbsp;-&nbsp;error&nbsp;code&nbsp;%d\n&#8221;,err);<br
/> &nbsp;&nbsp;}</p><p>&nbsp;&nbsp;streamParms.VideoRect.top=0;<br
/> &nbsp;&nbsp;streamParms.VideoRect.left=0;<br
/> &nbsp;&nbsp;streamParms.VideoRect.right=640;<br
/> &nbsp;&nbsp;streamParms.VideoRect.bottom=480;<br
/> &nbsp;&nbsp;streamParms.Flags=0;<br
/> &nbsp;&nbsp;streamParms.StreamId=fCurrent.StreamId;<br
/> &nbsp;&nbsp;err=CamStart(veo,&nbsp;&#038;streamParms);<br
/> &nbsp;&nbsp;if(err&nbsp;!=&nbsp;CAMERR_OKAY)&nbsp;{<br
/> &nbsp;&nbsp;&nbsp;&nbsp;printf(&#8220;Unable&nbsp;to&nbsp;start&nbsp;streaming&nbsp;-&nbsp;error&nbsp;code&nbsp;%d\n&#8221;,err);<br
/> &nbsp;&nbsp;}</p><p>&nbsp;&nbsp;snapParams.pBufferDef=NULL;<br
/> &nbsp;&nbsp;snapParams.FileName=argv[1];<br
/> &nbsp;&nbsp;snapParams.Format=CAM_SNAPSHOT_FORMAT_JPEG;<br
/> &nbsp;&nbsp;err&nbsp;=&nbsp;CamSnapshot(veo,&#038;snapParams);<br
/> &nbsp;&nbsp;if(err&nbsp;!=&nbsp;CAMERR_OKAY)&nbsp;{<br
/> &nbsp;&nbsp;&nbsp;&nbsp;printf(&#8220;Unable&nbsp;to&nbsp;take&nbsp;snapshot&nbsp;-&nbsp;error&nbsp;code&nbsp;%d\n&#8221;,err);<br
/> &nbsp;&nbsp;}</p><p>&nbsp;&nbsp;CamStop(veo);<br
/> &nbsp;&nbsp;CamLogoff(veo);<br
/> &nbsp;&nbsp;return&nbsp;0;<br
/> }</p></div></blockquote><p>Nice &#8211; now I have a similar situation as before, but I&#8217;m hating the fact that the frames are grabbed from the Windows system and are transported to the Unix server via Samba. I need a Unix-only solution.</p><p><b>Step 3</b> was to switch on tcpdump (<a
href="http://www.tcpdump.org/" target="_blank">http://www.tcpdump.org/</a>) and analyze the network traffic between the Active-X Control and the Veo Observer Camera. After looking at tons of packet traces, I created a perl-module that allowed me to communicate with the camera: And this works on Windows, Unix and the Mac.</p><p>A typical mini-application to connect to the camera would look like this below. This connects to the camera (using the default username/password), sets light and brightness levels, selects 640&#215;480 resolution and grabs 20 frames from the camera:</p><blockquote><div
class="codebox"> use strict;<br
/> use Veo;</p><p>my $veo=Veo->new(host => &#8217;192.168.1.1&#8242;, port => 1600);<br
/> $veo->login();<br
/> my($name,$loc)=$veo->info();<br
/> print &#8220;Name: $name\nLocation: $loc\n&#8221;;<br
/> print &#8220;Light: &#8220;,$veo->light(),&#8221;\n&#8221;;<br
/> $veo->light(Veo::VEO_LIGHT_NORMAL);<br
/> print &#8220;Light: &#8220;,$veo->light(),&#8221;\n&#8221;;<br
/> print &#8220;Brightness: &#8220;,$veo->brightness(),&#8221;\n&#8221;;<br
/> $veo->brightness(Veo::VEO_BRIGHT_NORMAL);<br
/> print &#8220;Brightness: &#8220;,$veo->brightness(),&#8221;\n&#8221;;<br
/> $veo->selectStream(Veo::VEO_STREAM_640X480,5);<br
/> $veo->stream(\&#038;cb);<br
/> $veo->reset();</p><p>my($images)=0;<br
/> sub cb {<br
/> &nbsp;&nbsp;&nbsp;&nbsp;my($data)=@_;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print STDERR qq{stream callback with },length($data),qq{ bytes of data\n};<br
/> &nbsp;&nbsp;&nbsp;&nbsp;open(OUT,&#8221;>&#8221;.sprintf(&#8220;veo%02d.out&#8221;,$images++));<br
/> &nbsp;&nbsp;&nbsp;&nbsp;binmode(OUT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;print OUT $data;<br
/> &nbsp;&nbsp;&nbsp;&nbsp;close(OUT);<br
/> &nbsp;&nbsp;&nbsp;&nbsp;return $images>=20?0:1;<br
/> }</p></div></blockquote><p>Please keep in mind that this was all done without any information from Veo: All I did, was decoding the packet stream between the Active-X control and the camera.</p><p>The last hurdle in this puzzle is the image format that the camera dumps. What arrives in the callback is not in a standard format. Veo (with it&#8217;s Xirlink history) decided to encode the frames in a (proprietary) format called XJPG and I have not been able to find information about the make-up of XJPG files.<br
/> All I can tell is that those are YUV encoded frames that arrive from the camera; it&#8217;s not a differential format with key-frames, because the size of the data for each frame is very similar.</p><p>I decoded an entirely black frame with this:</p><blockquote><div
class="codebox"> 0000000 0000 0000 1f03 73c6 ff00 ffff e3ff e001<br
/> 0000010 8002 baf0 a228 288a 8aa2 a228 288a 8aa2<br
/> 0000020 a228 288a 8aa2 a228 288a 8aa2 a228 288a<br
/> 0000030 8aa2 a228 288a 8aa2 a228 288a 8aa2 a228<br
/> 0000040 288a 8aa2 a228 288a 8aa2 a228 288a 8aa2<br
/> 0000050 a228 288a 8aa2 a228 288a 8aa2 a228 288a<br
/> &#8230;<br
/> 0001bf0 8aa2 a228 288a 8aa2 a228 288a 8aa2 a228<br
/> 0001c00 288a 8aa2 a228 288a 8aa2 a228 288a 8aa2<br
/> 0001c10 a228 288a 8aa2 a228 288a 8aa2 a228 288a<br
/> 0001c20 8aa2 a228 288a 8aa2 a228 288a 8aa2 a228<br
/> 0001c30 288a bfa2 d9ff</div></blockquote><p>The &#8220;d9 ff&#8221; at the end looks a lot like a JPEG marker, but this is as much JPEG as I can see in the dump.</p><p>An entirely white frame (or close to white) looks a lot different:</p><blockquote><div
class="codebox"> 0000000 0000 0000 091f ee47 ff00 ffff e3ff e001<br
/> 0000010 8002 ddf4 98a1 4e1e 543b a3ea 3a1d 3474<br
/> 0000020 ada7 9c02 6354 fd38 c129 5f01 6a4f 294a<br
/> 0000030 42c1 3080 2a3f 4071 033a 71f9 1445 0ebc<br
/> 0000040 184d b6e7 a04f 8aa2 0770 38d3 29fd f74a<br
/> 0000050 ebef 1445 7360 4e81 c00b 0638 298a 0cc0<br
/> &#8230;<br
/> 0003990 9ce9 ce51 513b 2145 40ce 1439 e799 e49c<br
/> 00039a0 4551 f721 6734 1f03 8aca 0929 e93d 0032<br
/> 00039b0 62e0 6294 9a92 3d39 fa08 3152 e23f 2829<br
/> 00039c0 d234 324e 2946 b2a5 0639 4c8a 74d2 53fa<br
/> 00039d0 fc49 69a9 4633 b47e ceab 8a73 5a5a d9ff</div></blockquote><p>So, if the two dumps above look familiar to anybody (especially the 28a sequences in the black dump) or if you happen to know about the XJPG format, I&#8217;d be happy to hear from you using: <i>tobias -at- kahunaburger.com</i></p><p>Otherwise, expect to hear back from me in while when I had the time to dig deeper into the Veo frames and find out how to decode the data.</p><p><b><i>Update 2/22/2004:</i></b> Please take a look at the updated entry at <a
href="/blog/archives/000112.html">Binary for Veo Observer Snapshot tool</a>.</p><p><b><i>Update 8/12/2004:</i></b> Finally I have a module that allows capturing the images from the Veo Observer Network Camera on any platform. See <a
href="/blog/archives/000157.html">this entry</a>.</p> ]]></content:encoded> <wfw:commentRss>http://www.kahunaburger.com/2004/01/04/deconstructing-the-veo-observer-net-camera/feed/</wfw:commentRss> <slash:comments>57</slash:comments> </item> </channel> </rss>
