CommonPlaces Gazebo

Where Are We Using That Module Again?

Posted June 23rd, 2009 · by Mike · 3 Comments

Like many people, I’m often looking for ways to “make our lives easier.” At CommonPlaces, we have many different Drupal projects and contributed modules in use in those projects. When a new advisory is released for a module — often an important security advisory requiring immediate attention — we would have to look through all the projects to find out which ones are using the module and what version of the module is in use, so we could determine if that version needs to be upgraded.

In response, we have implemented the following pair of scripts that automate the collection of this information for us. It greatly reduces the time we need to spend tracking all of this down.

As more and more Drupal developers are moving their projects into a source control system like Subversion, we thought we would offer these scripts here in the hopes that they would help others with the same problem.

An overview:

  • A PHP script runs every six hours via cron on our development server where the Subversion repositories are stored. The script accesses the Drupal RSS feed for contributed module advisories and extracts the ones that have been issued in the last six hours, or since the last time the script was run.
  • If a new advisory is found, the PHP script needs to determine what module it was for. This actually isn’t in the RSS feed itself — but we can and do obtain it from the drupal.org node which has the complete advisory.
  • A bash script is called with the module name. The bash script, which can also be run by itself to check manually, looks in our repositories for the module and prints its location along with the version of the module from the .info file.
  • The PHP script takes the output of the bash script, packages it with the relevant information from the RSS feed and emails it to a set address which in our case is an internal engineering mailing list.

Let’s take a look at the code. First up is the PHP script, check-modules.php. Note that it is PHP5.

#!/usr/bin/env php
<?php
/**
 * Examines the Drupal contributed module RSS feed for latest security
 * alerts and sends email to notify of the alert.
 */

// Begin Configuration

// The URL to the RSS feed
$rss_url = "http://drupal.org/security/contrib/rss.xml";
// The number of hours between runs (must match cron for this to be effective)
$interval_hours = 6;
// email to_address, possibly a company mailing list
$to_address = "to-user@DOMAIN.com";
// email headers including from address
$headers = 'From: user@DOMAIN.com';

// End Configuration

// parse the rss feed
$doc = new DOMDocument();
$doc->load($rss_url);
foreach ($doc->getElementsByTagName('item') as $node) {

  $title = $node->getElementsByTagName('title')->item(0)->nodeValue;
  $desc = $node->getElementsByTagName('description')->item(0)->nodeValue;
  $link = $node->getElementsByTagName('link')->item(0)->nodeValue;
  $date = $node->getElementsByTagName('pubDate')->item(0)->nodeValue;
  // i.e.  <pubDate>Wed, 10 Jun 2009 21:07:08 +0000</pubDate>

  // Get the timestamp from the date and compare with current
  $ts = strtotime($date);
  $ts_now = strtotime("-".$interval_hours." hours");

  if ($ts + ($interval_hours*60*60) > $ts_now) {
    // This advisory was issued since the last run
    // Process it and send the alert email

    $subject = file_get_contents($link);

    // Extract the url for the project page and get the module name
    // The rss feed does not contain the url to the module project page,
    // so we have to access the vulnerability page and get the url, and
    // then the module name
    // i.e. "http://drupal.org/project/services"

    $num = preg_match_all("/http://drupal.org/project/([a-z_]+)/", $subject, $matches);
    $module = $matches[1][0];

    $output = "";
    $output .= "ATTENTION: The following Drupal contributed module advisory has been issued:nn";
    $output .= "$titlen";
    $output .= "$linkn";
    $output .= strip_tags($desc);
    $output .= "Date: ".date('D, d M Y h:i:s A',$ts)."n";
    //$output .= "Timestamp: $tsn";
    $output .= "Project page: ".$matches[0][0]."n";

    $output .= "nThis module was found in the following repositories. Click the "
      . "link at the top of this message and compare "
      . "the version of the module in the repository with the version affected by the "
      . "vulnerability to determine whether or not an upgrade is necessary. If no "
      . "repositories are listed, the module does not appear in any repositories.n";

    // Empty the output array
    $cmd_output = array();
    exec("/usr/local/bin/repo-module.sh $module", $cmd_output);
    foreach ($cmd_output as $key => $val) {
      $output .= $val . "n";
    }
    $output .= "nn";
    //print($output);

    // send mail
    if (!mail($to_address, $title, $output, $headers)) {
      // Message for testing
      //print("Error sending mailn");
    } else {
      //print("Sent mailn");
    }
  }
}
?>

Next is the bash script called by check-modules.php. This one is called repo-module.sh.

#!/bin/bash

# Takes a Drupal module name and determines what repositories it exists in,
# along with the version of the module according to the .info file.
#
# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

for repos in $(ls /your-svn-dir)
do
  for dir in $(svn ls "file:///your-svn-dir/$repos/branches" 2> /dev/null)
  do
    if svn ls "file:///your-svn-dir/$repos/branches/$dir/all/modules" 2> /dev/null | egrep -q ^$1/$
    then
      echo ""
      echo "Found in $repos : branches/$dir"
      svn cat "file:///your-svn-dir/$repos/branches/$dir/all/modules/$1/$1.info" | grep version
    fi
  done

  for dir in "trunk/all/modules" "trunk/sites/all/modules"
  do
    if svn ls "file:///your-svn-dir/$repos/$dir" 2> /dev/null | egrep -q ^$1/$
    then
      echo ""
      echo "Found in $repos : $dir"
      svn cat "file:///your-svn-dir/$repos/$dir/$1/$1.info" | grep version
    fi
  done
done

I should explain that at CommonPlaces, we have chosen to store each Drupal project in its own repository, using a two-tiered system. We do this for a variety of reasons, probably too many to go into here. But the important things to note for purposes of this discussion are:

  • Only the project sites directory is stored in a repository. Our two-tiered checkout grabs a copy of Drupal core from another repository, not referenced here.
  • The structure of those project repositories is the basic Subversion trunk-branches-tags structure. We are interested in what is in branches and trunk.
  • If your repository structure is different, or if you use another source control system such as cvs, you will need to modify the bash script accordingly. Also, be sure to replace “your-svn-dir” with the directory on your filesystem where you store your repositories.
  • To use these scripts, save the first one as “check-modules.php” and the second one as “repo-module.sh”, drop both into your /usr/local/bin, and make them executable. Also create a cron job like the following:
    • # Check drupal for module security advisories every 6 hrs and send mail
      
      0 0,6,12,18 * * * /usr/local/bin/check-modules.php > /dev/null 2>&1
    • If you decide to run the cron job at a different interval than six hours, be sure to edit check-modules.php and make $interval_hours match the cron timeframe.

Here’s what a sample email looks like:

Subject: SA-CONTRIB-2009-038 – Nodequeue – Multiple vulnerabilities
From: fromuser@DOMAIN.com
Date: June 11, 2009 12:00:36 PM EDT
To: touser@DOMAIN.com

ATTENTION: The following Drupal contributed module advisory has been issued:

SA-CONTRIB-2009-038 – Nodequeue – Multiple vulnerabilities

http://drupal.org/node/488092

Advisory ID: DRUPAL-SA-CONTRIB-2009-038
Project: Nodequeue (third-party module)
Version: 5.x, 6.x
Date: 2009-June-10
Security risk: Moderately critical
Exploitable from: Remote
Vulnerability: Multiple vulnerabilities

Date: Wed, 10 Jun 2009 06:15:59 PM
Project page: http://drupal.org/project/nodequeue

This module was found in the following repositories. Click the link at the top of this message and compare the version of the module in the repository with the version affected by the vulnerability to determine whether or not an upgrade is necessary. If no repositories are listed, the module does not appear in any repositories.

Found in repo1 : trunk/all/modules
version = "5.x-2.2″

Found in repo2 : trunk/all/modules
version = "5.x-2.6″

Tags: Drupal · Web Development

3 responses so far ↓

  • 1 Dave Hansen-Lange // Jun 24, 2009 at 1:48 am

    Ummm, or you could just have Update Status module on all your sites send you an email whenever there’s a module that needs a security update. Seems much easier to me.

    If you really want to get fancy you use hook_mail_alter to include the same SA information as in your example. But I’m not sure that it’s really that helpful.

  • 2 Andrew Berry // Jun 24, 2009 at 9:20 pm

    The problem with Update Status is that if d.o is down, it can really slow down loading pages in /admin as it hangs waiting for the update request to time out. I generally disable it on live sites, and in my “sitename_devel” module I enable Update Status for development sites.

  • 3 Mike // Jun 25, 2009 at 9:38 am

    Dave — thanks for your reply!

    update_status is a terrific tool, no question about it, and I’m glad it’s in core now. There are a few reasons why we decided not to use it for this purpose, however.

    One is that with the number of different Drupal projects and sites we have, we’d be getting flooded with emails every time an update came out for a module that is used on most of those sites. This solution allows us to reduce that to one email. Since we’re sending the email to one of our company mailing lists, our staff appreciates us cutting it down to one message. :)

    Also, we typically have multiple branches in each repository for various development efforts going on within each project. And, a branch is often checked out by a number of different people working on it. We don’t want each of those sandboxes sending mail either. But we do still need to know when a branch has modules needing updates.

    Once you get into having to remember to turn it on or off for some sites or sandboxes, you run the risk of configuring it incorrectly for some or just forgetting, and not getting notified about an update you need.

    In general, this solution provides us a quick and easy way of knowing we’ll get one list of everything everywhere we may need to update. We do still use update_status to monitor one site’s module statuses, but for checking across a large number of sites and developer sandboxes, we just found we needed something more.

Leave a Comment