You are here

clamav.module in ClamAV 6

Same filename and directory in other branches
  1. 8 clamav.module
  2. 7 clamav.module
  3. 2.x clamav.module

Integrate ClamAV to allow uploaded files to be scanned for viruses.

File

clamav.module
View source
<?php

/**
 * @file
 * Integrate ClamAV to allow uploaded files to be scanned for viruses.
 */

// Scan using a ClamAV Daemon
define('CLAMAV_USE_DAEMON', 0);

// Scan using a ClamAV executable
define('CLAMAV_USE_EXECUTABLE', 1);

// Default: use Daemon-mode
define('CLAMAV_DEFAULT_MODE', CLAMAV_USE_EXECUTABLE);

// Behaviour if the ClamAV scanner is unavailable or does not respond.
// Prevent unchecked files from being uploaded
define('CLAMAV_BLOCK_UNCHECKED', 0);

// Allow unchecked files to be uploaded
define('CLAMAV_ALLOW_UNCHECKED', 1);

// Default behaviour for unchecked files - Block unchecked files.
define('CLAMAV_DEFAULT_UNCHECKED', CLAMAV_BLOCK_UNCHECKED);

// Default host (in Daemon-mode)
define('CLAMAV_DEFAULT_HOST', 'localhost');

// Default port (in Daemon-mode)
define('CLAMAV_DEFAULT_PORT', 3310);

// Default path (in Executable-mode)
define('CLAMAV_DEFAULT_PATH', '/usr/bin/clamscan');

// The file was not checked (e.g. because the AV daemon wasn't running).
define('CLAMAV_SCANRESULT_UNCHECKED', -1);

// The file was checked and found to be clean.
define('CLAMAV_SCANRESULT_CLEAN', 0);

// The file was checked and found to be infected.
define('CLAMAV_SCANRESULT_INFECTED', 1);

/**
 * Implementation of hook_help().
 */
function clamav_help($path, $arg) {
  $output = '';
  switch ($path) {
    case "admin/help#clamav":
      $output .= '<p>' . t('Clam AntiVirus is an open source anti-virus toolkit for UNIX.') . '</p>';
      $output .= '<p>' . t('The ClamAV module allows files which are uploaded to Drupal to be scanned by Clam AntiVirus.') . '<br />';
      $output .= t('The module does not install ClamAV - visit <a href="http://www.clamav.net/">the ClamAV website</a> for help installing ClamAV.') . '</p>';
      break;
    case 'admin/settings/clamav':
      break;
  }
  return $output;
}

/**
 * Implementation of hook_menu().
 */
function clamav_menu() {
  return array(
    'clamav/upload/js' => array(
      'page callback' => 'clamav_upload_js',
      'access arguments' => array(
        'upload files',
      ),
      'type' => MENU_CALLBACK,
    ),
    'admin/settings/clamav' => array(
      'title' => 'Anti-virus (ClamAV)',
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'clamav_admin_settings',
      ),
      'access arguments' => array(
        'administer site configuration',
      ),
      'file' => 'clamav.admin.inc',
    ),
  );
}

/**
 * Implementation of hook_elements().
 */
function clamav_elements() {
  $elements = array();

  // support the core 'file' FAPI element.
  if (variable_get('clamav_enable_element_file', TRUE)) {
    $elements['file']['#element_validate'] = array(
      'clamav_elements_file_validate',
    );
  }
  return $elements;
}

/**
 * Form element validator for the file FAPI type.
 *
 * @param Array $element
 */
function clamav_elements_file_validate($element) {
  $key = $element['#parents'][0];
  if (is_array($_FILES['files']['tmp_name']) && array_key_exists($key, $_FILES['files']['tmp_name']) && !empty($_FILES['files']['tmp_name'][$key])) {
    $filepath = $_FILES['files']['tmp_name'][$key];

    // filepath to the uploaded file
    $form_error_key = implode('][', $element['#parents']);

    // form-element to use with form_set_error
    require_once dirname(__FILE__) . '/clamav.inc';
    clamav_scan($filepath, $form_error_key);
  }
}

/**
 * Implementation of hook_form_alter().
 * Validation of CCK filefield and imagefield is applied here.
 */
function clamav_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] . '_node_form' == $form_id) {

    // Check for CCK integration and handle supported CCK fields.
    if (module_exists('content')) {
      $type = content_types($form['#node']->type);
      if (!empty($type['fields'])) {
        foreach ($type['fields'] as $field_name => $field) {
          $function = 'clamav_' . $field['widget']['type'] . '_alter';
          if (function_exists($function)) {
            $function($form[$field_name]);
          }
        }
      }
    }
    if (isset($form['attachments'])) {

      // Replace upload.module submission handler with a custom one that can
      // refuse to attach files containing viruses
      foreach ($form['#submit'] as $key => $handler) {
        if ($handler == 'upload_node_form_submit') {
          $form['#submit'][$key] = 'clamav_upload_node_form_submit';
        }
      }

      // Override default upload/js callback to add clamav validation
      $form['attachments']['wrapper']['new']['attach']['#ahah']['path'] = 'clamav/upload/js';
    }
  }
}

/**
 * Implementation of our custom hook_FIELDNAME_alter for filefield widget.
 */
function clamav_filefield_widget_alter(&$field) {
  if (module_exists('filefield') && variable_get('clamav_enable_element_filefield_widget', TRUE)) {
    $field[0]['#upload_validators']['clamav_elements_filefield_validate'] = array();
  }
}

/**
 * Implementation of our custom hook_FIELDNAME_alter for filefield widget.
 */
function clamav_imagefield_widget_alter(&$field) {
  if (module_exists('imagefield') && variable_get('clamav_enable_element_imagefield_widget', TRUE)) {
    $field[0]['#upload_validators']['clamav_elements_filefield_validate'] = array();
  }
}

/**
 *  Validator for filefield widget and imagefield widget.
 *  This is an implementation of a file upload_validator.
 */
function clamav_elements_filefield_validate($file) {
  $filepath = $file->filepath;
  $form_error_key = $file->source;
  require_once dirname(__FILE__) . '/clamav.inc';
  $result = clamav_scan_file($filepath);
  $errors = array();
  if ($result == CLAMAV_SCANRESULT_INFECTED) {
    $errors[] = t('A virus has been detected in the file.  The file will not be accepted.');
  }
  elseif ($result == CLAMAV_SCANRESULT_UNCHECKED && variable_get('clamav_unchecked_files', CLAMAV_DEFAULT_UNCHECKED) == CLAMAV_BLOCK_UNCHECKED) {
    $errors[] = t('The anti-virus scanner was not able to check the file.  The file cannot be uploaded.');
  }
  return $errors;
}

/**
 * Validator for upload.module attachments.
 *
 * Nearly an exact copy of upload_node_form_submit() in upload.module
 */
function clamav_upload_node_form_submit(&$form, &$form_state) {
  global $user;
  $limits = _upload_file_limits($user);
  $validators = array(
    'clamav_elements_filefield_validate' => array(),
    'file_validate_extensions' => array(
      $limits['extensions'],
    ),
    'file_validate_image_resolution' => array(
      $limits['resolution'],
    ),
    'file_validate_size' => array(
      $limits['file_size'],
      $limits['user_size'],
    ),
  );

  // Save new file uploads.
  if (user_access('upload files') && ($file = file_save_upload('upload', $validators, file_directory_path()))) {
    $file->list = variable_get('upload_list_default', 1);
    $file->description = $file->filename;
    $file->weight = 0;
    $file->new = TRUE;
    $form['#node']->files[$file->fid] = $file;
    $form_state['values']['files'][$file->fid] = (array) $file;
  }
  if (isset($form_state['values']['files'])) {
    foreach ($form_state['values']['files'] as $fid => $file) {

      // If the node was previewed prior to saving, $form['#node']->files[$fid]
      // is an array instead of an object. Convert file to object for compatibility.
      $form['#node']->files[$fid] = (object) $form['#node']->files[$fid];
      $form_state['values']['files'][$fid]['new'] = !empty($form['#node']->files[$fid]->new);
    }
  }

  // Order the form according to the set file weight values.
  if (!empty($form_state['values']['files'])) {
    $microweight = 0.001;
    foreach ($form_state['values']['files'] as $fid => $file) {
      if (is_numeric($fid)) {
        $form_state['values']['files'][$fid]['#weight'] = $file['weight'] + $microweight;
        $microweight += 0.001;
      }
    }
    uasort($form_state['values']['files'], 'element_sort');
  }
}

/**
 * Menu-callback for JavaScript-based uploads.
 *
 * Nearly an exact copy of upload_js() in upload.module
 */
function clamav_upload_js() {
  $cached_form_state = array();
  $files = array();

  // Load the form from the Form API cache.
  if (!($cached_form = form_get_cache($_POST['form_build_id'], $cached_form_state)) || !isset($cached_form['#node']) || !isset($cached_form['attachments'])) {
    form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
    $output = theme('status_messages');
    print drupal_to_js(array(
      'status' => TRUE,
      'data' => $output,
    ));
    exit;
  }
  $form_state = array(
    'values' => $_POST,
  );

  // Handle new uploads, and merge tmp files into node-files.
  clamav_upload_node_form_submit($cached_form, $form_state);
  if (!empty($form_state['values']['files'])) {
    foreach ($form_state['values']['files'] as $fid => $file) {
      if (isset($cached_form['#node']->files[$fid])) {
        $files[$fid] = $cached_form['#node']->files[$fid];
      }
    }
  }
  $node = $cached_form['#node'];
  $node->files = $files;
  $form = _upload_form($node);
  unset($cached_form['attachments']['wrapper']['new']);
  $cached_form['attachments']['wrapper'] = array_merge($cached_form['attachments']['wrapper'], $form);
  $cached_form['attachments']['#collapsed'] = FALSE;
  form_set_cache($_POST['form_build_id'], $cached_form, $cached_form_state);
  foreach ($files as $fid => $file) {
    if (is_numeric($fid)) {
      $form['files'][$fid]['description']['#default_value'] = $form_state['values']['files'][$fid]['description'];
      $form['files'][$fid]['list']['#default_value'] = !empty($form_state['values']['files'][$fid]['list']);
      $form['files'][$fid]['remove']['#default_value'] = !empty($form_state['values']['files'][$fid]['remove']);
      $form['files'][$fid]['weight']['#default_value'] = $form_state['values']['files'][$fid]['weight'];
    }
  }

  // Render the form for output.
  $form += array(
    '#post' => $_POST,
    '#programmed' => FALSE,
    '#tree' => FALSE,
    '#parents' => array(),
  );
  drupal_alter('form', $form, array(), 'upload_js');
  $form_state = array(
    'submitted' => FALSE,
  );
  $form = form_builder('upload_js', $form, $form_state);
  $output = theme('status_messages') . drupal_render($form);

  // We send the updated file attachments form.
  // Don't call drupal_json(). ahah.js uses an iframe and
  // the header output by drupal_json() causes problems in some browsers.
  print drupal_to_js(array(
    'status' => TRUE,
    'data' => $output,
  ));
  exit;
}

Functions

Namesort descending Description
clamav_elements Implementation of hook_elements().
clamav_elements_filefield_validate Validator for filefield widget and imagefield widget. This is an implementation of a file upload_validator.
clamav_elements_file_validate Form element validator for the file FAPI type.
clamav_filefield_widget_alter Implementation of our custom hook_FIELDNAME_alter for filefield widget.
clamav_form_alter Implementation of hook_form_alter(). Validation of CCK filefield and imagefield is applied here.
clamav_help Implementation of hook_help().
clamav_imagefield_widget_alter Implementation of our custom hook_FIELDNAME_alter for filefield widget.
clamav_menu Implementation of hook_menu().
clamav_upload_js Menu-callback for JavaScript-based uploads.
clamav_upload_node_form_submit Validator for upload.module attachments.

Constants