uid) { $track = db_fetch_object(db_query("SELECT * FROM {troll_ip_track} WHERE uid = %d AND ip_address = '%s'", $user->uid, $_SERVER['REMOTE_ADDR'])); if ($track->uid) { // record for this IP exists, update accessed timestamp db_query("UPDATE {troll_ip_track} SET accessed = %d WHERE uid = %d AND ip_address = '%s'", time(), $user->uid, $_SERVER['REMOTE_ADDR']); } else { // insert new IP record for user db_query("INSERT INTO {troll_ip_track} (uid, ip_address, created, accessed) VALUES (%d, '%s', %d, %d)", $user->uid, $_SERVER['REMOTE_ADDR'], time(), time()); } } if (variable_get('troll_enable_ip_ban', FALSE)) { $ban = db_fetch_object(db_query('SELECT * FROM {troll_ip_ban} WHERE (expires > %d OR expires = 0) AND ip_address = \'%s\'', time(), $_SERVER['REMOTE_ADDR'])); if ($ban->ip_address) { global $base_url; watchdog('troll', 'IP Ban: '. $_SERVER['REMOTE_ADDR'], l(t('administer'), 'admin/settings/troll/ip_ban/'), WATCHDOG_NOTICE); $troll_ip_ban_redirect = variable_get('troll_ip_ban_redirect', ''); $page = (empty($troll_ip_ban_redirect) ? drupal_get_path('module', 'troll') .'/blocked.html' : $troll_ip_ban_redirect); header('location: '. $base_url .'/'. $page); die(); } } } /** * Implementation of hook_help. * * @param $section string */ function troll_help($section = 'admin/help#legal') { switch ($section) { case 'admin/settings/troll/ip_ban': if (!variable_get('troll_enable_ip_ban', FALSE)) { return theme('error', t('IP banning is currently disabled, you can enable it in the !settings page', array('!settings' => l(t('settings'), 'admin/settings/troll/settings')))); } break; } } /** * Implementation of hook_settings(). * * @return array */ function troll_admin_settings() { $form['ip_settings'] = array( '#type' => 'fieldset', '#title' => 'IP Address Banning' ); $form['ip_settings']['troll_enable_ip_ban'] = array( '#type' => 'radios', '#title' => t('IP Address Banning'), '#default_value' => variable_get('troll_enable_ip_ban', 1), '#options' => array('1' => t('Enable banning by IP address'), '0' => t('Disable banning by IP address')) ); $form['ip_settings']['troll_ip_ban_redirect'] = array( '#type' => 'textfield', '#title' => t('IP Ban Relocation Page'), '#default_value' => variable_get('troll_ip_ban_redirect', ''), '#description' => t("Page for relocating users banned based on their IP address or domain name. If left blank, users will be redirected to blocked.html in the troll module's directory. Do not use a drupal path here! You will cause a loop since IP banning completely blocks all access to the site! Edit the blocked.html file, or redirect to http://localhost."), ); $roles = user_roles(); array_unshift($roles, t(' -Select Role- ')); $form['role_settings'] = array( '#type' => 'fieldset', '#title' => t('User Blocking') ); $form['role_settings']['troll_block_role'] = array( '#type' => 'select', '#title' => t('Troll Block Role'), '#default_value' => variable_get('troll_block_role', 0), '#options' => $roles, '#description' => t('Select the role to set users to when blocking from the troll adminstration screens'), ); return system_settings_form($form); } /** * admin settings page validate handler */ function troll_admin_settings_validate($form_id, $form_values) { if ($form_values['troll_block_role'] == '0') { form_set_error('troll_block_role', t('You must choose a role to set users to when blocking from the troll settings page.')); } } /** * Implementation of hook_perm(). */ function troll_perm() { return array('administer troll'); } /** * Implementation of hook_menu(). * * @return array */ function troll_menu($may_cache) { $items = array(); $access = user_access('administer troll'); if ($may_cache) { $items[] = array( 'path' => 'admin/settings/troll', 'title' => t('Troll'), 'description' => t('Manage visitor IP banning.'), 'callback' => 'troll_search_users', 'access' => $access, 'weight' => 0, ); $items[] = array( 'path' => 'admin/settings/troll/search', 'title' => t('Search Users'), 'callback' => 'troll_search_users', 'type' => MENU_DEFAULT_LOCAL_TASK, 'access' => $access, 'weight' => 0 ); $items[] = array( 'path' => 'admin/settings/troll/ip_ban', 'title' => t('IP Banning'), 'callback' => 'troll_ip_ban', 'type' => MENU_LOCAL_TASK, 'access' => $access, 'weight' => 1 ); $items[] = array( 'path' => 'admin/settings/troll/ip_blacklist', 'title' => t('Blacklists'), 'callback' => 'troll_blacklist', 'type' => MENU_LOCAL_TASK, 'access' => $access, 'weight' => 2 ); $items[] = array( 'path' => 'admin/settings/troll/settings', 'title' => t('Settings'), 'callback' => 'drupal_get_form', 'callback arguments' => array('troll_admin_settings'), 'type' => MENU_LOCAL_TASK, 'access' => user_access('administer site configuration'), 'weight' => 3 ); $items[] = array( 'path' => 'admin/settings/troll/ip_ban/edit', 'title' => t('IP Ban Form'), 'callback' => 'drupal_get_form', 'callback arguments' => 'troll_ip_ban_form', 'type' => MENU_CALLBACK, 'access' => $access ); } return $items; } /** * * MENU CALLBACKS * **/ /** * User IP banning page callback function. */ function troll_ip_ban($op = NULL, $iid = NULL) { $op = $_POST['op'] ? $_POST['op'] : $op; $edit = $_POST ? $_POST : $edit; switch ($op) { case 'user': $ip = db_fetch_object(db_query('SELECT ip_address FROM {troll_ip_track} WHERE uid = %d ORDER BY accessed DESC', $iid)); $edit['ip_address'] = $ip->ip_address; troll_insert_ip($edit); drupal_goto('admin/settings/troll/ip_ban'); break; case t('Ban IP'): troll_insert_ip($edit); drupal_goto('admin/settings/troll/ip_ban'); break; case t('Update Banned IP'): troll_update_ip($edit); drupal_goto('admin/settings/troll/ip_ban'); break; case 'delete': $output = troll_confirm_delete_ip($iid); break; case t('Confirm'): troll_remove_ip($iid); drupal_goto('admin/settings/troll/ip_ban'); break; case t('Cancel'): drupal_goto('admin/settings/troll/ip_ban'); break; default: $form['banform'] = array( '#type' => 'fieldset', '#title' => t('Add IP Ban'), '#value' => drupal_get_form('troll_ip_ban_form'), '#weight' => -1, '#collapsible' => true ); $form['ipdisplay'] = array( '#type' => 'fieldset', '#title' => t('Banned IPs'), '#value' => troll_display_ip(), '#weight' => 0, '#collapsible' => true ); $output = drupal_render($form); break; } return $output; } /** * Central function to display Blacklists tab and fire * off functions for Blacklist-related form submissions * * @param string $op * @param array $edit * @return string */ function troll_blacklist($op = NULL, $edit = NULL) { $op = $_POST['op'] ? $_POST['op'] : $op; switch ($op) { case t('Update Blacklist Punishments'): variable_set('troll_blacklist_mod_requests', $_POST['mod_requests']); variable_set('troll_blacklist_stutter', $_POST['stutter']); variable_set('troll_blacklist_alt_page', $_POST['alt_page']); variable_set('troll_blacklist_alt_url', $_POST['alt_url']); drupal_goto('admin/settings/troll/ip_blacklist'); break; case t('Import List'): if (!empty($_POST['truncate_list'])) { if (db_query('TRUNCATE TABLE {troll_blacklist}')) { drupal_set_message(t('Blacklist table truncated.')); } } if (!empty($_POST['select_list'])) { troll_blacklist_import_list($_POST['select_list']); } if (!empty($_POST['custom_list'])) { troll_blacklist_import_url($_POST['custom_list']); } drupal_goto('admin/settings/troll/ip_blacklist'); break; case t('Search Blacklisted IPs'): return troll_blacklist_search($_POST['ip_address']); break; case t('Insert Whitelist IPs'): troll_whitelist_insert_ips($_POST['whitelist_addr1'], $_POST['whitelist_addr2']); drupal_goto('admin/settings/troll/ip_blacklist'); break; case t('Update Whitelist IPs'): troll_whitelist_insert_ips($_POST['whitelist_addr1'], $_POST['whitelist_addr2']); drupal_goto('admin/settings/troll/ip_blacklist'); break; case 'deletewhite': $output = troll_confirm_delete_white_block(arg(5), arg(6)); break; case t('Confirm Whitelist Removal'): troll_remove_whitelist($_POST['net'], $_POST['bcast']); drupal_goto('admin/settings/troll/ip_blacklist'); break; case 'deleteblack': $output = troll_confirm_delete_black_block(arg(5), arg(6)); break; case t('Confirm Blacklist Removal'): troll_remove_blacklist($_POST['net'], $_POST['bcast']); drupal_goto('admin/settings/troll/ip_blacklist'); break; default: $form['blacklist_info'] = array( '#type' => 'fieldset', '#title' => t('Blacklist Summary'), '#value' => troll_blacklist_summary(), '#weight' => -4, '#collapsible' => true, '#collapsed' => false ); $form['blacklist_punishment'] = array( '#type' => 'fieldset', '#title' => t('Blacklist Visitor Punishment'), '#value' => troll_blacklist_punishment(), '#weight' => -3, '#collapsible' => true, '#collapsed' => true ); $form['blacklist_import'] = array( '#type' => 'fieldset', '#title' => t('Import Blacklist'), '#value' => troll_blacklist_import(), '#weight' => -2, '#collapsible' => true, '#collapsed' => true ); $form['search_blacklist'] = array( '#type' => 'fieldset', '#title' => t('Search Blacklisted IPs'), '#value' => troll_blacklist_search_blacklist(), '#weight' => 0, '#collapsible' => true, '#collapsed' => true ); $form['whitelist_form'] = array( '#type' => 'fieldset', '#title' => t('Whitelist IPs'), '#value' => troll_whitelist(), '#description' => t('Whitelisted IPs override the blacklist. Does not apply to the IP Ban feature.'), '#weight' => 1, '#collapsible' => true, '#collapsed' => true ); $output = drupal_render($form); break; } return $output; } /** * Summary of how many IP blocks are filtered * * @return string */ function troll_blacklist_summary() { $count = db_result(db_query('SELECT COUNT(net) FROM {troll_blacklist}')); return t('%d address blocks filtered.', array('%d' => $count)); } /** * Form builder function * * @uses troll_blacklist_punishment_form() * @return string */ function troll_blacklist_punishment() { return drupal_get_form('troll_blacklist_punishment_form'); } /** * Gives admins a choice of how to punish blacklisted visitors * * @return array */ function troll_blacklist_punishment_form() { $form['stutter'] = array( '#type' => 'checkbox', '#title' => t('Randomly stutter output'), '#default_value' => variable_get('troll_blacklist_stutter', 0), '#description' => t('While outputting content, the troll module will cause Drupal to "sleep" for random intervals of 1-5 seconds to delay output.') ); $form['mod_requests'] = array( '#type' => 'radios', '#title' => t('Page request modification'), '#options' => array(t('none'), 'silent_post_drop' => 'Silently drop form post submission data', 'notice_post_drop' => 'Drop form post submission data and give a notice'), '#default_value' => variable_get('troll_blacklist_mod_requests', '0'), '#description' => t('Modifies data sent by visitors from blacklisted IPs before Drupal even has a chance to process it.') ); $form['alt_page_output'] = array( '#type' => 'fieldset', '#title' => 'Alternate page output' ); $form['alt_page_output']['alt_page'] = array( '#type' => 'radios', '#title' => 'Alternate pages', '#options' => array(t('none'), 'blank' => t('Blank pages'), '404' => t('404 every page request'), 'redirect' => t('Redirect to alternate URL')), '#default_value' => variable_get('troll_blacklist_alt_page', '0'), '#description' => t('Sends alternate page output, whether the visitor hits a real URL or not.') ); $form['alt_page_output']['alt_url'] = array( '#type' => 'textfield', '#title' => t('Redirection URL'), '#default_value' => variable_get('troll_blacklist_alt_url', ''), '#description' => t('URL to redirect blacklisted visitors to if "Redirect to alternate URL" is selected. Start with the appropriate prefix (e.g. http://)') ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Update Blacklist Punishments'), '#weight' => 1 ); return $form; } /** * Form builder function * * @uses troll_blacklist_import_form() * @return string */ function troll_blacklist_import() { return drupal_get_form('troll_blacklist_import_form'); } /** * Builds form array for admin to select how to import a new blacklist. * * @return array */ function troll_blacklist_import_form() { $form['truncate_list'] = array( '#type' => 'checkbox', '#title' => t('Truncate/delete existing blacklist before import') ); $options = array(); $options[0] = ''; if (function_exists('gzopen')) { $options['S1gz'] = 'OpenBSD mirror: SPEWS.org Level 1 bzip archive'; $options['S2gz'] = 'OpenBSD mirror: SPEWS.org Level 2 bzip archive'; $options['OCgz'] = 'OpenBSD mirror: okean.com China bzip archive'; $options['OKgz'] = 'OpenBSD mirror: okean.com Korea bzip archive'; } $options['S1tx'] = 'SPEWS.org Level 1 text file'; $options['S2tx'] = 'SPEWS.org Level 2 text file'; $options['OTtx'] = 'okean.com China and Korea text file'; $options['OCtx'] = 'okean.com China text file'; $options['OKtx'] = 'okean.com Korea text file'; $form['select_list'] = array( '#type' => 'select', '#title' => t('Download and import'), '#options' => $options, '#description' => t('Downloads supported blacklist from the internet. Please select a mirror when possible.'), ); $form['custom_list'] = array( '#type' => 'textfield', '#title' => t('List URL'), '#description' => t('URL of a list to import. List files should have one address per line in Classless Internet Domain Routing CIDR format (x.x.x.x/x). Individual IPs should still have /32.'), ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Import List'), '#weight' => 1, ); return $form; } /** * Decyphers what dropdown selection was chosen to send for parsing out IP blocks * * @see troll_blacklist_import_form() * @see troll_blacklist_parse_save() * @param array $edit */ function troll_blacklist_import_list($list) { $files = array('S1gz' => array('gz' => 'http://www.openbsd.org/spamd/spews_list_level1.txt.gz'), 'S2gz' => array('gz' => 'http://www.openbsd.org/spamd/spews_list_level2.txt.gz'), 'OCgz' => array('gz' => 'http://www.openbsd.org/spamd/chinacidr.txt.gz'), 'OKgz' => array('gz' => 'http://www.openbsd.org/spamd/koreacidr.txt.gz'), 'S1tx' => array('txt' => 'http://www.spews.org/spews_list_level1.txt'), 'S2tx' => array('txt' => 'http://www.spews.org/spews_list_level2.txt'), 'OTtx' => array('txt' => 'http://www.okean.com/sinokoreacidr.txt'), 'OCtx' => array('txt' => 'http://www.okean.com/chinacidr.txt'), 'OKtx' => array('txt' => 'http://www.okean.com/koreacidr.txt')); if (!isset($files[$list])) { drupal_set_message(t('Form input not valid')); return false; } $file_type = array_keys($files[$list]); troll_blacklist_parse_save($files[$list][$file_type[0]], $file_type[0]); } /** * Parses a URL to send for parsing out IP blocks * * @see troll_blacklist_parse_save() * @param array $edit */ function troll_blacklist_import_url($edit) { $url_parts = parse_url($edit['custom_list']); $file_parts = pathinfo($url_parts['path']); troll_blacklist_parse_save($edit['custom_list'], $file_parts['extension']); } /** * Parses input file, line by line, searching for IP blocks in CIDR * format (x.x.x.x/x). Understands files in text, bzip, and bzip2 format, * where the PHP installation supports it. Writes what it finds to the * database as long integers. * * @todo should probably function_exists() the compression file functions * @param $file_name string * @param $file_type string */ function troll_blacklist_parse_save($file_name, $file_type) { $netmasks = array( 0, -2147483648, -1073741824, -536870912, -268435456, -134217728, -67108864, -33554432, -16777216, -8388608, -4194304, -2097152, -1048576, -524288, -262144, -131072, -65536, -32768, -16384, -8192, -4096, -2048, -1024, -512, -256, -128, -64, -32, -16, -8, -4, -2, -1); switch ($file_type) { case 'gz': $fp = gzopen($file_name, 'r'); break; default: $fp = fopen($file_name, 'r'); break; } if (is_resource($fp)) { $i = 0; while (!feof($fp)) { $buffer = fgets($fp); if ($buffer{0} != '#' && ($buffer{0} != '/' && $buffer{1} != '/') && !empty($buffer)) { preg_match("/([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})/", $buffer, $matches); // $matches array // 0 = whole string // 1 = i.i.i.i // 2 = s $longip = ip2long($matches[1]); unset($buffer, $matches[0], $matches[1]); if ($matches[2] == 32) { $net = $bcast = $longip; } else { $net = $longip & $netmasks[$matches[2]]; $bcast = $longip | ($netmasks[$matches[2]] ^ -1); } db_query('DELETE FROM {troll_blacklist} WHERE net = %d AND bcast = %d', $net, $bcast); db_query('INSERT INTO {troll_blacklist} (net, bcast) VALUES (%d, %d)', $net, $bcast); $i++; } } switch ($file_type) { case 'gz': gzclose($fp); break; default: fclose($fp); break; } drupal_set_message(t('%i IP blocks imported', array('%i' => $i))); } else { drupal_set_message(t('Import failed! File could not be read.')); } } /** * Form builder function * * @uses troll_blacklist_search_blacklist_form() * @return string */ function troll_blacklist_search_blacklist() { return drupal_get_form('troll_blacklist_search_blacklist_form'); } /** * Form to search blacklist to see if an IP matches * * @return array */ function troll_blacklist_search_blacklist_form() { $form['ip_address'] = array( '#type' => 'textfield', '#title' => t('IP Address'), '#size' => 15, '#maxlength' => 15, '#description' => t('Address to search for in the database of imported IP blocks.'), '#required' => TRUE ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Search Blacklisted IPs'), '#weight' => 1 ); return $form; } /** * Perform search to see if an IP address matches a blacklisted IP block. * If no results are found, get redirected to the blacklist management * page instead of seeing results. * * @param $edit array * @return string */ function troll_blacklist_search($ip_address) { $longip = _troll_longip($ip_address); $sql = "SELECT net, bcast FROM {troll_blacklist} WHERE net <= %d AND bcast >= %d"; $headers = array( array('data' => t('Network Address'), 'field' => 'net', 'sort' => 'asc'), array('data' => t('Broadcast Address'), 'field' => 'bcast'), array('data' => t('Actions'), 'field' => 'delete') ); $sql .= tablesort_sql($headers); $result = pager_query($sql, 25, 0, NULL, $longip, $longip); if (db_num_rows($result) == 0) { drupal_set_message(t('No matches found in blacklist search.')); drupal_goto('admin/settings/troll/ip_blacklist'); } while ($row = db_fetch_object($result)) { $printnet = long2ip($row->net); $printbcast = long2ip($row->bcast); $action = l(t('remove'), "admin/settings/troll/ip_blacklist/deleteblack/{$row->net}/{$row->bcast}"); $rows[] = array($printnet, $printbcast, $action); } $pager = theme('pager', NULL, 25, 0); if (!empty($pager)) { $rows[] = array(array('data' => $pager, 'colspan' => 3)); } return theme('table', $headers, $rows); } /** * Form builder function * * @param $net string Not used, here for later implementation of whitelist editing * @param $bcast string Not used, here for later implementation of whitelist editing * @uses troll_whitelist_form() * @return string */ function troll_whitelist($net = NULL, $bcast = NULL) { $output = drupal_get_form('troll_whitelist_form', $net, $bcast); $sql = 'SELECT net, bcast FROM {troll_whitelist}'; $headers = array( array('data' => t('Start/Network Address'), 'field' => 'net', 'sort' => 'asc'), array('data' => t('End/Broadcast Address'), 'field' => 'bcast'), array('data' => t('Actions'), 'field' => 'delete') ); $sql .= tablesort_sql($headers); $result = pager_query($sql, 25); while($row = db_fetch_object($result)) { $printnet = long2ip($row->net); $printbcast = long2ip($row->bcast); $action = l(t('remove'), "admin/settings/troll/ip_blacklist/deletewhite/{$row->net}/{$row->bcast}"); $rows[] = array($printnet, $printbcast, $action); } $pager = theme('pager', NULL, 25, 0); if (!empty($pager)) { $rows[] = array(array('data' => $pager, 'colspan' => 3)); } return $output . theme('table', $headers, $rows); } /** * Display form for creating new whitelist block and table of current whitelisted IPs * * @return array */ function troll_whitelist_form($net, $bcast) { $form['whitelist_addr1'] = array( '#type' => 'textfield', '#title' => t('Starting IP Address'), '#size' => 15, '#maxlength' => 15, '#default_value' => $net ? long2ip($net) : '', '#description' => t('IP or start to range of IPs to whitelist.'), '#required' => TRUE ); $form['whitelist_addr2'] = array( '#type' => 'textfield', '#title' => t('Ending IP Address'), '#size' => 15, '#maxlength' => 15, '#default_value' => $bcast ? long2ip($bcast) : '', '#description' => t('End of IP range to whitelist. If whitelisting a single IP, leave this blank.'), '#required' => false ); $form['submit'] = array( '#type' => 'submit', '#value' => ($net && $bcast) ? t('Update Whitelist IPs') : t('Insert Whitelist IPs'), '#weight' => 1 ); $form['#action'] = url('admin/settings/troll/ip_blacklist'); return $form; } /** * Insert a new IP block into the whitelist * * @param array $edit */ function troll_whitelist_insert_ips($whitelist_addr1, $whitelist_addr2) { $longip1 = _troll_longip($whitelist_addr1); $longip2 = _troll_longip(empty($whitelist_addr2) ? $whitelist_addr1 : $whitelist_addr2); if ($longip1 > $longip2) { $temp = $longip1; $longip1 = $longip2; $longip2 = $temp; unset($temp); } db_query("REPLACE INTO {troll_whitelist} (net, bcast) VALUES (%d, %d)", $longip1, $longip2); drupal_set_message(t('IP range %ip1 to %ip2 whitelisted.', array('%ip1' => long2ip($longip1), '%ip2' => long2ip($longip2)))); } /** * Convert dotted decimal IP to long integer and check for validity * * @param $ip string * @return integer */ function _troll_longip($ip) { $longip = ip2long($ip); if ($longip === false || $longip == -1) { drupal_set_message(t('IP %ip not valid!', array('%ip' => $ip))); drupal_goto('admin/settings/troll/ip_blacklist'); } return $longip; } /** * User search page callback function. * * @param $op array * @param $uid * @return string */ function troll_search_users($op = NULL, $uid = NULL) { $form_values = $_POST; $output = ''; switch ($op) { case 'view': $output = troll_search_user_detail($uid); break; case 'block': troll_block_user($uid); drupal_goto('admin/settings/troll/search'); break; default: $output = troll_user_search(); $output .= troll_list_users($form_values); break; } return $output; } /** * * FORM FUNCTIONS * **/ /** * IP banning form. * * @param $iid int * @return array */ function troll_ip_ban_form($iid = NULL) { $form['submit'] = array( '#type' => 'submit', '#value' => ($iid) ? t('Update Banned IP') : t('Ban IP'), '#weight' => 1, ); $ip = db_fetch_object(db_query('SELECT * FROM {troll_ip_ban} WHERE iid = %d', $iid)); $form['iid'] = array( '#type' => 'hidden', '#value' => $ip->iid, ); $form['ip_address'] = array( '#type' => 'textfield', '#title' => t('IP Address'), '#default_value' => $ip->ip_address, '#description' => t('The IP address to ban.'), '#required' => TRUE ); $form['domain_name'] = array( '#type' => 'textfield', '#title' => t('Domain Name'), '#default_value' => ($ip->domain_name ? $ip->domain_name : ($ip->ip_address ? gethostbyaddr($ip->ip_address) : '')), '#description' => t('The Domain Name of the IP address to ban - for reference only.') ); $timestamp = ($ip->expires ? $ip->expires : time()); $date = getdate( gmmktime() ); $curyear = $date['year']; while ($i < 10) { $years[$curyear+$i] = $curyear+$i; $i++; } $months = array(1 => t('January'), t('February'), t('March'), t('April'), t('May'), t('June'), t('July'), t('August'), t('September'), t('October'), t('November'), t('December')); for ($i = 1; $i <= 31; $i++) { $days[$i] = $i; } $form['timestamp'] = array( '#type' => 'fieldset', '#title' => t('Expires'), '#prefix' => '