<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Gray Soft / The Gateway</title>
  <id>tag:graysoftinc.com,2014-03-20:/categories/10</id>
  <updated>2014-04-04T19:16:17Z</updated>
  <link rel="self" href="http://graysoftinc.com/the-gateway/feed.xml"/>
  <link rel="alternate" href="http://graysoftinc.com/the-gateway"/>
  <author>
    <name>James Edward Gray II</name>
  </author>
  <entry>
    <title>news_to_mail.rb</title>
    <link rel="alternate" href="http://graysoftinc.com/the-gateway/news-to-mailrb"/>
    <id>tag:graysoftinc.com,2006-12-12:/posts/27</id>
    <updated>2014-04-04T19:16:17Z</updated>
    <summary>Here's the code for the other half of the Ruby Talk Gateway.  This code pulls posts from Usenet and emails them to the Ruby Talk mailing list.</summary>
    <content type="html">&lt;p&gt;&lt;em&gt;[&lt;strong&gt;Note&lt;/strong&gt;:  You need to know &lt;a href="/the-gateway/what-is-the-ruby-talk-gateway"&gt;what the Gateway is&lt;/a&gt; before reading this article.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second half of the Ruby Gateway runs as a cron job every five minutes and is expected to move all new newsgroup messages from the NNTP host to the Ruby Talk mailing list.  The cron invocation is simply:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="sr"&gt;/path/&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;news_to_mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="sr"&gt;/path/&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;news_to_mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This side of the Gateway is not piped any messages and exit codes from it are not monitored.  It needs to tend its own affairs.&lt;/p&gt;

&lt;h4&gt;The Code&lt;/h4&gt;

&lt;p&gt;Here's the source:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;".."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;freeze&lt;/span&gt;
&lt;span class="no"&gt;DATA_DIR&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;freeze&lt;/span&gt;
&lt;span class="no"&gt;LAST_ID_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATA_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"last_news_id.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;freeze&lt;/span&gt;
&lt;span class="no"&gt;PID_FILE&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATA_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"pid.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;freeze&lt;/span&gt;

&lt;span class="vg"&gt;$LOAD_PATH&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The script begins by locating the root directory of the Gateway source and the data sub directory, building constants for two external files it will interact with, and adjusting the &lt;code&gt;$LOAD_PATH&lt;/code&gt; so that it can &lt;code&gt;require&lt;/code&gt; needed resources.&lt;/p&gt;

&lt;p&gt;Here are those &lt;code&gt;require&lt;/code&gt;s:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"servers_config"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"nntp"&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"net/smtp"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"logger"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"timeout"&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The first two requires are the same code used by &lt;a href="/the-gateway/mail-to-newsrb"&gt;the other half of the Gateway&lt;/a&gt;.  The last three are standard Ruby libraries the code will make use of.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# prepare log&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ARGV&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shift&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="vg"&gt;$stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"%Y-%m-%d %H:%M "&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This code opens a log file to record its status and adjusts the date and time formatting.&lt;/p&gt;

&lt;p&gt;With setup complete, it's now vital to ensure that the current execution is the only copy of the code we are running.  Should the Gateway get behind and fail to complete before the cron job launches another copy, duplicate messages could be sent to the list or worse.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# make sure only one copy is ever running at a time&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PID_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CREAT&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EXCL&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WRONLY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="vg"&gt;$$&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;at_exit&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PID_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Unable to unlink pid file:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PID_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt; &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warn&lt;/span&gt; &lt;span class="s2"&gt;"Process &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; was already running"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The script attempts to write its process ID number to a file.  The &lt;code&gt;File::CREAT|File:EXCL&lt;/code&gt; options ensure that the file is created or an exception is thrown, if it already existed.  When the exception comes it is assumed that an earlier execution is still running and the script exits.  If able to create the process ID file, the scripts knows it is the only copy running and it is safe to proceed.  The code then immediately arranges for Ruby to remove the file on exit.&lt;/p&gt;

&lt;p&gt;Note that to check for the file then create it if missing would introduce a race condition.  Another execution could create the file between check and creation.  As unlikely as that is for a five minute cron job, it's better to play things safe and just try the creation directly.&lt;/p&gt;

&lt;p&gt;Having made it this far, the script needs to refresh its memory of where it was in the list of newsgroup messages:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# find out what the last news item sent was&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="n"&gt;last_id_sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;LAST_ID_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to get message ID of last news post:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A simple text file is used to hold the message number of the last file seen.  The code reads that file back to know where to resume its work.&lt;/p&gt;

&lt;p&gt;It's now time to connect to Usenet.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# &lt;/span&gt;
&lt;span class="c1"&gt;# connect to NNTP host, switch to newgroup, and see how many messages are&lt;/span&gt;
&lt;span class="c1"&gt;# available to read&lt;/span&gt;
&lt;span class="c1"&gt;# &lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_id_available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;nntp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP_PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_PASS&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;last_id_available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWSGROUP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"The NNTP connection timed out."&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to establish connection to NNTP host:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This code tries to establish a connection to the NNTP host and switch to the comp.lang.ruby group being monitored.  A reasonable timeout is imposed on this code and will be for all network operations attempted.  If the connection is slow for some reason, another attempt can be made by the next cron execution.&lt;/p&gt;

&lt;p&gt;The script now advances the NNTP reader to the last message seen, which turns out to be complicated to get right:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# switch to the last message we sent (or the first real message before that)&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_id_sent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"The NNTP message shift timed out."&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Bad article number"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;last_id_sent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nonzero?&lt;/span&gt;
    &lt;span class="n"&gt;last_id_sent&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;retry&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to switch to the last message:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The problem here is that Usenet messages can be revoked.  When that happens, the last message the script saw may no longer exist.  The real goal is the message &lt;em&gt;after&lt;/em&gt; the last seen post though and an NNTP &lt;code&gt;NEXT&lt;/code&gt; command will skip missing IDs.  Given that, the code backs up in the count until it can find an existing message.  &lt;code&gt;NEXT&lt;/code&gt;ing forward from that will skip the vanished IDs and land the reader at the desired later message.&lt;/p&gt;

&lt;p&gt;To make that leap forward in the message count, the script enters an infinite loop of message processing:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# main event loop&lt;/span&gt;
&lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# advance to the next message, if there is one&lt;/span&gt;
  &lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;more_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;new_last_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Advancing the current NNTP message timed out."&lt;/span&gt;
      &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;more_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;more_messages&lt;/span&gt;

  &lt;span class="c1"&gt;# reality check that we are moving forward&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;new_last_id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;last_id_sent&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"new_last_id (&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) &amp;lt;= last_id_sent "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
              &lt;span class="s2"&gt;"(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;last_id_sent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;), quitting to prevent sending duplicates"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As I said, a &lt;code&gt;NEXT&lt;/code&gt; command is sent to the host bringing the reader to an unseen message.  A quick sanity check is then made to ensure the count is rising.&lt;/p&gt;

&lt;p&gt;Note that this is also the code to determine when there are no more messages.  If the &lt;code&gt;NEXT&lt;/code&gt; command fails, the message processing loop terminates.  You really need to used this combination of an infinite loop and last message detection when working with Usenet.  Any method of counting is pointless, thanks to the numbering issues I described.&lt;/p&gt;

&lt;p&gt;At this point, it's time to actually start reading the post:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# pull the headers for the current messae&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Fetching the message headers timed out."&lt;/span&gt;
      &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Unable to retrieve headers for message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
              &lt;span class="s2"&gt;"will try again later:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# &lt;/span&gt;
  &lt;span class="c1"&gt;# don't send articles that the mail_to_news program has previously forwarded&lt;/span&gt;
  &lt;span class="c1"&gt;# to the newsgroup (or we'd loop)&lt;/span&gt;
  &lt;span class="c1"&gt;# &lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^X-rubymirror:/&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Skipping message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, sent by mail_to_news"&lt;/span&gt;
    &lt;span class="k"&gt;next&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This code pulls the message headers and performs the loop check to ensure that messages sent by &lt;code&gt;mail_to_news.rb&lt;/code&gt; are skipped.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# pull the body for the current message&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Fetching the message body timed out."&lt;/span&gt;
      &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Unable to retrieve body for message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
              &lt;span class="s2"&gt;"will try again later:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# scan headers for message data&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^Subject:\s+([^\r\n]*)/&lt;/span&gt;     &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"?"&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^From:\s+([^\r\n]*)/&lt;/span&gt;        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"?"&lt;/span&gt;

  &lt;span class="c1"&gt;# ensure the message is properly addressed&lt;/span&gt;
  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^To:/&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"To: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAILING_LIST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Sending message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -- &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The body is then pulled.  The script then scans for some message details in the headers to allow us to log accurately about the post.  The message is also addressed.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# extracting path information (poster's host, first nntp hop)&lt;/span&gt;
  &lt;span class="n"&gt;posting_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^NNTP-Posting-Host:\s+(\S[^\r\n]*)/i&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;

  &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^Path:\s+([^\r\n]*)/i&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
  &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete_if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;hop&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;hop&lt;/span&gt; &lt;span class="o"&gt;!~&lt;/span&gt; &lt;span class="sr"&gt;/^(([^.]+)\.)+([^.]+)$/&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;first_hop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;

  &lt;span class="c1"&gt;# adding pseudo "Received:" header and rubymirror header&lt;/span&gt;
  &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;END_RECEIVED&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="sh"&gt;Received: from #{first_hop} (#{first_hop})&lt;/span&gt;
&lt;span class="sh"&gt;     by #{ServersConfig::NEWSGROUP} with NTTP id #{new_last_id}&lt;/span&gt;
&lt;span class="sh"&gt;     for &amp;lt;#{ServersConfig::MAILING_LIST}&amp;gt;; #{Time.now.to_s}&lt;/span&gt;
&lt;span class="sh"&gt;Received: from [#{posting_host}]&lt;/span&gt;
&lt;span class="sh"&gt;     by #{first_hop} (unknown) with NNTP id #{new_last_id}&lt;/span&gt;
&lt;span class="sh"&gt;     for &amp;lt;#{ServersConfig::NEWSGROUP}&amp;gt;; #{Time.now.to_s}&lt;/span&gt;
&lt;span class="sh"&gt;Received: from Usenet via a Usenet to mail gateway located at&lt;/span&gt;
&lt;span class="sh"&gt;     #{ServersConfig::NEWSGROUP}.  This service provided as a courtesy&lt;/span&gt;
&lt;span class="sh"&gt;     to the ruby-talk mailing list.  If this message is SPAM, its&lt;/span&gt;
&lt;span class="sh"&gt;     ultimate origin is Usenet, not this gateway program.  All&lt;/span&gt;
&lt;span class="sh"&gt;     subscribers to the ruby-talk mailing list agree to receive the&lt;/span&gt;
&lt;span class="sh"&gt;     Usenet postings made to comp.lang.ruby via this gateway.  Please&lt;/span&gt;
&lt;span class="sh"&gt;     see http://www.ruby-lang.org/ruby-talk-usenet-policy.html.&lt;/span&gt;
&lt;span class="sh"&gt;X-From-Usenet: see Received: header above.&lt;/span&gt;
&lt;span class="sh"&gt;X-rubymirror: yes&lt;/span&gt;

&lt;span class="no"&gt;END_RECEIVED&lt;/span&gt;

  &lt;span class="c1"&gt;# build final message&lt;/span&gt;
  &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Message looks like: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The above adds a header explaining the service and constructs the final message.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# attempt to send email&lt;/span&gt;
  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vg"&gt;$DEBUG&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SMTP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SMTP_SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;smtp&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;smtp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAIL_SENDER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAILING_LIST&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"The SMTP connection timed out."&lt;/span&gt;
        &lt;span class="nb"&gt;exit&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to send email:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"...  Sent."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here a connection is established to the SMTP server and the email is sent.  The &lt;code&gt;$DEBUG&lt;/code&gt; flag allows me to spot check Gateway functionality without actually sending emails to Ruby Talk.&lt;/p&gt;

&lt;p&gt;On to the final step:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# record new high-water mark&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vg"&gt;$DEBUG&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;new_last_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;last_id_sent&lt;/span&gt;
      &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;LAST_ID_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;new_last_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Unable to write message ID to file:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;last_id_sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_last_id&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"New last message sent ID:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;last_id_sent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Getting this far means the script sent an email successfully, so it records the new last seen ID for future runs.  That final &lt;code&gt;end&lt;/code&gt; signals the end of the message processing event loop.&lt;/p&gt;

&lt;h4&gt;Possible Improvements&lt;/h4&gt;

&lt;p&gt;This code is pretty feature complete.&lt;/p&gt;

&lt;p&gt;About the only enhancement I can think of would be to DRY up some of the timeout and error handling code.  That's trickier that you might guess because of the extra layers of scoping plus the error messages and exit codes.  We almost need a multi-block syntax here.  If someone poses a reasonable alternative I'll consider it, but I've tried to keep the code pretty straight forward and the truth is that is just may not be worth the effort.&lt;/p&gt;

&lt;p&gt;Any comments or suggestions for the Gateway can be left as comments below.&lt;/p&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
  <entry>
    <title>mail_to_news.rb</title>
    <link rel="alternate" href="http://graysoftinc.com/the-gateway/mail-to-newsrb"/>
    <id>tag:graysoftinc.com,2006-12-05:/posts/26</id>
    <updated>2014-04-04T19:20:41Z</updated>
    <summary>Here's a breakdown for half of the Ruby Talk Gateway code.  This script pushes mail messages up to our Usenet host.</summary>
    <content type="html">&lt;p&gt;&lt;em&gt;[&lt;strong&gt;Note&lt;/strong&gt;:  You need to know &lt;a href="/the-gateway/what-is-the-ruby-talk-gateway"&gt;what the Gateway is&lt;/a&gt; before reading this article.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are two halves to the Ruby Gateway.  One half runs as a qmail filter for an email address on the Ruby Talk mailing list.  Every message sent to that address is piped through this filter with a shell script like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ruby /path/to/gateway/bin/mail_to_news.rb /path/to/mail_to_news.log
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The email is piped to the filter via the standard input and the code is expected to handle the message by posting it to comp.lang.ruby or choosing to ignore it.  If the filter exits normally, qmail considers the matter handled.  A non-zero exit code will cause the filter to be called with that same message again later.&lt;/p&gt;

&lt;h4&gt;The Code&lt;/h4&gt;

&lt;p&gt;Let's dive right into the source of this half of the Gateway:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;".."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;freeze&lt;/span&gt;

&lt;span class="vg"&gt;$LOAD_PATH&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The code above just sets things up so this script can &lt;code&gt;require&lt;/code&gt; some other files in the project normally.  Here are those &lt;code&gt;require&lt;/code&gt;s:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"servers_config"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"nntp"&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"net/smtp"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"logger"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"timeout"&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The last three requires are standard Ruby libraries.  The first two are not.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;servers_config.rb&lt;/code&gt; file sets up a &lt;code&gt;ServerConfig&lt;/code&gt; &lt;code&gt;Module&lt;/code&gt; with information needed to connect to our email and Usenet hosts.  I will not show this file, but the references should be obvious when see them.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;nntp.rb&lt;/code&gt; is pretty much a vendored copy of the &lt;a href="http://rubygems.org/gems/ruby-net-nntp"&gt;net-nntp library&lt;/a&gt;.  I've made some minor changes for debugging output purposes, but the library functions the same.&lt;/p&gt;

&lt;p&gt;We're now ready to initiate logging.  Here's the code that starts that process:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# prepare log&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ARGV&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shift&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="vg"&gt;$stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"%Y-%m-%d %H:%M "&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That just builds a &lt;code&gt;Logger&lt;/code&gt; object and cleans up the default date and time formatting.&lt;/p&gt;

&lt;p&gt;Now it's time to start setting variables in preparation for the coming email parse:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# only allow certain headers through&lt;/span&gt;
&lt;span class="no"&gt;VALID_HEADERS&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[ From Subject References In-Reply-To Message-Id Content-Type&lt;/span&gt;
&lt;span class="sx"&gt;                       Content-Transfer-Encoding Date X-ML-Name X-Mail-Count&lt;/span&gt;
&lt;span class="sx"&gt;                       X-X-Sender ]&lt;/span&gt;
&lt;span class="n"&gt;valid_headers_re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^(?:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;VALID_HEADERS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"|"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;):/i&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Gateway passes through only a subset of the email headers for the Usenet post.  The above is the list of those headers and the &lt;code&gt;Regexp&lt;/code&gt; that will locate them.&lt;/p&gt;

&lt;p&gt;Now, there are two types of messages we do not wish to forward:  spam and a message sent to Ruby Talk by the other half of the Gateway (causing an infinite loop of sending).  The following code prepares flags for these conditions:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# message flags&lt;/span&gt;
&lt;span class="n"&gt;spam&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="n"&gt;mirrored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The following code allocates variables to hold key header information parsed from the message:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# header data&lt;/span&gt;
&lt;span class="n"&gt;msg_id&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="n"&gt;subject&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="n"&gt;reply_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="n"&gt;ref&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="n"&gt;head&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;END_RECEIVED&lt;/span&gt;  &lt;span class="c1"&gt;# build received header, including loop flag&lt;/span&gt;
&lt;span class="sh"&gt;Newsgroups: #{ServersConfig::NEWSGROUP}&lt;/span&gt;
&lt;span class="sh"&gt;X-received-from: This message has been automatically forwarded from the&lt;/span&gt;
&lt;span class="sh"&gt;   ruby-talk mailing list by a gateway at #{ServersConfig::NEWSGROUP}. If it is&lt;/span&gt;
&lt;span class="sh"&gt;   SPAM, it did not originate at #{ServersConfig::NEWSGROUP}. Please report the&lt;/span&gt;
&lt;span class="sh"&gt;   original sender, and not us. Thanks!&lt;/span&gt;
&lt;span class="sh"&gt;   Please see http://hypermetrics.com/rubyhacker/clrFAQ.html#tag24 too.&lt;/span&gt;
&lt;span class="sh"&gt;X-rubymirror: yes&lt;/span&gt;
&lt;span class="no"&gt;END_RECEIVED&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that we get the headers started with an &lt;code&gt;X-received-from&lt;/code&gt; explaining our service and add the &lt;code&gt;X-rubymirror&lt;/code&gt; flag the other half of the Gateway will use to detect that this half of the Gateway sent this new post we are creating.&lt;/p&gt;

&lt;p&gt;Now we need to parse the email headers:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# process message headers&lt;/span&gt;
&lt;span class="n"&gt;valid_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="vg"&gt;$stdin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^X-Spam-Status: Yes/&lt;/span&gt;      &lt;span class="c1"&gt;# flag message as spam&lt;/span&gt;
    &lt;span class="n"&gt;spam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^X-rubymirror: yes/&lt;/span&gt;       &lt;span class="c1"&gt;# flag messages from news_to_mail&lt;/span&gt;
    &lt;span class="n"&gt;mirrored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^\s*$/&lt;/span&gt;                    &lt;span class="c1"&gt;# end of headers&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^\s/&lt;/span&gt;                      &lt;span class="c1"&gt;# continuation line&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valid_header&lt;/span&gt;  &lt;span class="c1"&gt;# only allow after valid headers&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;valid_headers_re&lt;/span&gt;           &lt;span class="c1"&gt;# valid header&lt;/span&gt;
    &lt;span class="n"&gt;valid_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

    &lt;span class="c1"&gt;# parse header data&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/Message-Id:\s+(.*)/i&lt;/span&gt;
      &lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\.+&amp;gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/In-Reply-To:\s*(.*)/i&lt;/span&gt;
      &lt;span class="n"&gt;reply_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/References:\s*(.*)/i&lt;/span&gt;
      &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^Subject:\s*(.*)/i&lt;/span&gt;
      &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^From:\s*(.*)/i&lt;/span&gt;
      &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;                            &lt;span class="c1"&gt;# invalid header, discard&lt;/span&gt;
    &lt;span class="n"&gt;valid_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There's nothing too tricky in the above code.  We match headers with simple expressions, pulling the information we need into variables.  We also set flags as appropriate and add to the headers we have started for the newsgroup post.  This code stops reading at the blank line signaling the end of the email headers.&lt;/p&gt;

&lt;p&gt;The code above didn't address flagged messages immediately, because we wanted to be able to log the key details about them.  We now have those details, so it's time to address the flags:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# skip any flagged messages&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mirrored&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Skipping message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, sent by news_to_mail"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;spam&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Ignoring Spam #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -- &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, flagged messages are noted in the log and we exit cleanly without further processing.&lt;/p&gt;

&lt;p&gt;The Gateway does some final header doctoring in an attempt to set a reasonable References header and also includes the Ruby Talk message id for reader reference:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# doctor headers for Ruby Talk&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nil?&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reply_to&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nil?&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^Re:/&lt;/span&gt;
            &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"In-Reply-To: &amp;lt;this_is_a_dummy_message-id@rubygate&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"References: &amp;lt;this_is_a_dummy_message-id@rubygate&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"References: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;reply_to&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"X-ruby-talk: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We are finally ready to construct a complete Usenet post:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# construct final message&lt;/span&gt;
&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$stdin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;
&lt;span class="n"&gt;msg&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gsub!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\r?\n/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Sending message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -- &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Message looks like: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The above code just joins the headers and existing message body, cleans up the newlines and logs our progress.&lt;/p&gt;

&lt;p&gt;Actually sending the message is a two-step process.  First we connect to our Usenet host:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# connect to NNTP host&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="n"&gt;nntp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;nntp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP_PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_PASS&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"The NNTP connection timed out."&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to establish connection to NNTP host:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Above you can see several references to the &lt;code&gt;ServerConfig&lt;/code&gt; &lt;code&gt;Module&lt;/code&gt; I spoke of earlier.  These constants contain exactly what their names indicate.&lt;/p&gt;

&lt;p&gt;Note that we exit with an error code if anything goes wrong here, assuming the problem is temporary and allowing qmail to try again later.&lt;/p&gt;

&lt;p&gt;The final step is to send the message:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# attempt to send newsgroup post&lt;/span&gt;
&lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vg"&gt;$DEBUG&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"The NNTP post timed out."&lt;/span&gt;
      &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to post to NNTP host:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"...  Sent.  nntp.post() result = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The above code makes the post and logs what we have accomplished.  Again we exit with error codes if something goes wrong, to signal retries.  The &lt;code&gt;$DEBUG&lt;/code&gt; check allows me to test Gateway operation with everything but the actual post send when needed.&lt;/p&gt;

&lt;h4&gt;Possible Improvements&lt;/h4&gt;

&lt;p&gt;Usenet and email are two different worlds with opposing rules.  Our Usenet host, like many, does not allow the posting of multipart/alternative messages (used to send HTML email).  Some have expressed a desire for the Gateway to &lt;em&gt;convert&lt;/em&gt; these messages into a Usenet safe format.  This could possibly be done by using the text/plain variant of content, when provided, and stripping the HTML when it is not.  This change is of low importance to me, since I don't believe posters should be sending HTML email to Ruby Talk.&lt;/p&gt;

&lt;p&gt;In a similar vein, some Usenet hosts reject certain types of multipart/mixed messages (used to send email attachments), generally those that have Base 64 encoded portions to avoid allowing binary content through.  Our host allows such posts, but they may not be well circulated on Usenet for these reasons.  Again we might be able to inline the content for these files, but this could get pretty tricky for some attachments.  For example, imagine a post with a zip archive of files.  This problem interests me more than HTML email.&lt;/p&gt;

&lt;p&gt;The first step to either off these changes is probably to switch to a real email parsing library.  I imagine the original code didn't use one because the choices weren't convenient when the Gateway was designed.  I just cleaned up the code in my rewrite and don't have enough experience with such libraries to select the proper replacement.  Odds are this could simplify a fair portion of the Gateway code though, if we find the right one.  We are looking for a library that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Makes it easy to read email headers.&lt;/li&gt;
&lt;li&gt;Allow us to set new headers, for things like the no-mirror flag.&lt;/li&gt;
&lt;li&gt;Allows us to remove unwanted headers.  Alternately I guess we could build a new message object and copy over the headers we wish to keep.&lt;/li&gt;
&lt;li&gt;Supports easy manipulation of multipart/alternative and multipart/mixed content.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;If you have experience with such a library, please leave a comment below showing how this could be used to simplify the code above.&lt;/p&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
  <entry>
    <title>Hacking the Gateway</title>
    <link rel="alternate" href="http://graysoftinc.com/the-gateway/hacking-the-gateway"/>
    <id>tag:graysoftinc.com,2006-12-04:/posts/25</id>
    <updated>2014-04-04T14:45:37Z</updated>
    <summary>Here are the rules for getting changes into the Ruby Talk Gateway code.</summary>
    <content type="html">&lt;p&gt;&lt;em&gt;[&lt;strong&gt;Update&lt;/strong&gt;:  The Ruby Gateway was retired in June of 2011.  Our community simply grew past the point were we needed to combine the various groups, in my opinion.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Though I rewrote the current Gateway and I handle the maintenance, it really belongs to the Ruby community.  Because of that, I'm going to release the two primary source files on this blog for all to view and critique.  This may have value to those who want to know how the Gateway works, those who would like to implement similar technologies, and those who would like to purpose changes to the Gateway code.&lt;/p&gt;

&lt;p&gt;I do welcome purposed changes to the Gateway, but let's set some ground rules for the right way to make suggestions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I will show the important elements of the Gateway code and do my best to explain it as I go.  In return, please take the time to read what I write about the code and try to understand how it works.  Poorly developed change requests increase my maintenance time with the Gateway, which all comes out of my free time, so please be considerate.&lt;/li&gt;
&lt;li&gt;You purpose changes to the Gateway by commenting on the code articles.  This is intended to be a public discussion with all of us working together.  Don't email me or Ruby Talk ideas, I'm monitoring the comments here.&lt;/li&gt;
&lt;li&gt;Show code in your requests.  I don't want to throw the Gateway in a publicly accessible Subversion repository and start taking patches for several reasons.  If you want a change, convince me to implement it.  The best way to do that is to throw around some code showing me how we would build your request and how it would make the Gateway better.&lt;/li&gt;
&lt;li&gt;I am thinking about some elements of the Gateway you are not, like the fact that I run this code on a server provided by my work where security is a consideration and the level of maintenance a change will inflict on me.  I ask only that you keep this in mind as we debate changes.  In return, I will be as open minded to improvements as possible.&lt;/li&gt;
&lt;li&gt;Gateway changes will not happen overnight.  (See note about free time above.)  Please be patient.&lt;/li&gt;
&lt;/ul&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
  <entry>
    <title>What is the Ruby Talk Gateway?</title>
    <link rel="alternate" href="http://graysoftinc.com/the-gateway/what-is-the-ruby-talk-gateway"/>
    <id>tag:graysoftinc.com,2006-12-01:/posts/24</id>
    <updated>2014-04-04T01:49:45Z</updated>
    <summary>The article describes and old, but still in common use, feature of the Ruby community:  The Ruby Talk/comp.lang.ruby Gateway.</summary>
    <content type="html">&lt;p&gt;&lt;em&gt;[&lt;strong&gt;Update&lt;/strong&gt;:  The Ruby Gateway was retired in June of 2011.  Our community simply grew past the point were we needed to combine the various groups, in my opinion.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Ruby community makes use of both email and Usenet communication, in addition to other resources.  The primary mailing list is &lt;a href="http://www.ruby-lang.org/en/community/mailing-lists/"&gt;Ruby Talk&lt;/a&gt; and the primary Usenet group is &lt;a href="http://groups-beta.google.com/group/comp.lang.ruby/topics"&gt;comp.lang.ruby&lt;/a&gt;.  These two services are joined by the Ruby Gateway.&lt;/p&gt;

&lt;p&gt;In 2001 &lt;a href="http://www.pragmaticprogrammer.com/"&gt;The Pragmatic Programmers&lt;/a&gt; wrote the initial version of the Ruby Gateway to ferry messages back and forth between these two resources.  Emails sent to Ruby Talk are posted as Usenet messages and Usenet posts are forwarded to Ruby Talk by the Gateway.  The Gateway has had a few guardians and code changes since then, but the functionality remains the same.&lt;/p&gt;

&lt;p&gt;I'm am the current caretaker of the Ruby Gateway.  Highgroove Studios generously provides hosting for it and I monitor the system for problems.  I also wrote the current version of the Gateway.&lt;/p&gt;

&lt;p&gt;You are free to &lt;a href="mailto:james@graysoftinc.com?subject=Ruby%20Gateway%20Issue"&gt;report Gateway problems&lt;/a&gt; for me to look into.  Before you do though, please read the following notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; I rewrote the entire Gateway after I assumed control of it.  My code was deployed on December 4th, 2006, so anything before that is history.  If you raise issues, please make sure they involve posts after that date.&lt;/li&gt;
&lt;li&gt;I now have very detailed logs on everything the Gateway does, so please be specific.  For example, please send me links to exact messages that appeared on one side of the Gateway, but not the other.&lt;/li&gt;
&lt;li&gt;Our Usenet host does not allow us to post HTML emails (multipart/alternative).  This is not changing.  These messages are not supported.  (Yes, that means you should &lt;strong&gt;not&lt;/strong&gt; be sending HTML email to Ruby Talk.)&lt;/li&gt;
&lt;li&gt;Our Gateway is an NNTP ↔ email Gateway.  There has been at least one instance of a Usenet post using ancient header formatting predating NNTP.  These messages are not supported.&lt;/li&gt;
&lt;/ul&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
</feed>
