From 199a9ab83772a0d98940eb68837177414a3bcee2 Mon Sep 17 00:00:00 2001 From: pdontthink Date: Wed, 12 Aug 2009 08:28:38 +0000 Subject: [PATCH] Implemented security token system. (Secunia Advisory SA34627) git-svn-id: https://svn.code.sf.net/p/squirrelmail/code/trunk/squirrelmail@13817 7612ce4b-ef26-0410-bec9-ea0150e637f0 --- doc/ChangeLog | 1 + functions/addressbook.php | 2 +- functions/forms.php | 35 ++-- functions/mailbox_display.php | 6 + functions/strings.php | 182 ++++++++++++++++++ src/addrbook_search_html.php | 5 +- src/addressbook.php | 4 + src/compose.php | 35 ++++ src/folders.php | 21 ++ src/options.php | 13 +- src/options_highlight.php | 10 +- src/options_identities.php | 8 +- src/search.php | 11 +- templates/default/addressbook_list.tpl | 1 + templates/default/folder_manip.tpl | 4 + templates/default/folder_manip_dialog.tpl | 1 + templates/default/message_list.tpl | 1 + templates/default/read_menubar_buttons.tpl | 2 + templates/default/vcard.tpl | 3 +- .../default_advanced/read_menubar_buttons.tpl | 2 + 20 files changed, 325 insertions(+), 22 deletions(-) diff --git a/doc/ChangeLog b/doc/ChangeLog index ab2101fd..6b71e3a7 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -323,6 +323,7 @@ Version 1.5.2 - SVN - Stop using deprecated ereg functions. (#2820952) - Remove personal data from Message ID seed. (#880029/847107) - Implemented page referal verification mechanism. (Secunia Advisory SA34627) + - Implemented security token system. (Secunia Advisory SA34627) Version 1.5.1 (branched on 2006-02-12) -------------------------------------- diff --git a/functions/addressbook.php b/functions/addressbook.php index f8589a2f..8e6753dd 100644 --- a/functions/addressbook.php +++ b/functions/addressbook.php @@ -192,7 +192,7 @@ function abook_create_form($form_url, $name, $title, $button, global $oTemplate; - $output = addForm($form_url, 'post', 'f_add'); + $output = addForm($form_url, 'post', 'f_add', '', '', array(), TRUE); if ($button == _("Update address")) { $edit = true; diff --git a/functions/forms.php b/functions/forms.php index 1218d7e8..8e760b1e 100644 --- a/functions/forms.php +++ b/functions/forms.php @@ -313,20 +313,26 @@ function addTextArea($sName, $sText = '', $iCols = 40, $iRows = 10, $aAttribs = /** * Make a
start-tag. * - * @param string $sAction form handler URL - * @param string $sMethod http method used to submit form data. 'get' or 'post' - * @param string $sName form name used for identification (used for backward - * compatibility). Use of id is recommended instead. - * @param string $sEnctype content type that is used to submit data. html 4.01 - * defaults to 'application/x-www-form-urlencoded'. Form - * with file field needs 'multipart/form-data' encoding type. - * @param string $sCharset charset that is used for submitted data - * @param array $aAttribs (since 1.5.1) extra attributes + * @param string $sAction form handler URL + * @param string $sMethod http method used to submit form data. 'get' or 'post' + * @param string $sName form name used for identification (used for backward + * compatibility). Use of id is recommended instead. + * @param string $sEnctype content type that is used to submit data. html 4.01 + * defaults to 'application/x-www-form-urlencoded'. Form + * with file field needs 'multipart/form-data' encoding type. + * @param string $sCharset charset that is used for submitted data + * @param array $aAttribs (since 1.5.1) extra attributes + * @param boolean $bAddToken (since 1.5.2) When given as a string or as boolean TRUE, + * a hidden input is also added to the form containing a + * security token. When given as TRUE, the input name is + * "smtoken"; otherwise the name is the string that is + * given for this parameter. When FALSE, no hidden token + * input field is added. (OPTIONAL; default not used) * * @return string html formated form start string * */ -function addForm($sAction, $sMethod = 'post', $sName = '', $sEnctype = '', $sCharset = '', $aAttribs = array()) { +function addForm($sAction, $sMethod = 'post', $sName = '', $sEnctype = '', $sCharset = '', $aAttribs = array(), $bAddToken = FALSE) { global $oTemplate; @@ -338,7 +344,14 @@ function addForm($sAction, $sMethod = 'post', $sName = '', $sEnctype = '', $sCha $oTemplate->assign('enctype', $sEnctype); $oTemplate->assign('charset', $sCharset); - return $oTemplate->fetch('form.tpl'); + $sForm = $oTemplate->fetch('form.tpl'); + + if ($bAddToken) { + $sForm .= addHidden((is_string($bAddToken) ? $bAddToken : 'smtoken'), + sm_generate_security_token()); + } + + return $sForm; } /** diff --git a/functions/mailbox_display.php b/functions/mailbox_display.php index e6e16d26..3b917b74 100644 --- a/functions/mailbox_display.php +++ b/functions/mailbox_display.php @@ -1341,6 +1341,12 @@ function handleMessageListForm($imapConnection, &$aMailbox, $sButton='', $aUid = (isset($msg) && is_array($msg)) ? array_values($msg) : $aUid; if (count($aUid) && $sButton != 'expunge') { + // don't do anything to any messages until we have done security check + // FIXME: not sure this code really belongs here, but there's nowhere else to put it with this architecture + // FIXME: we might need to open this up to SQ_FORM instead, especially for plugins (?) + sqgetGlobalVar('smtoken', $submitted_token, SQ_POST, ''); + sm_validate_security_token($submitted_token, 3600, TRUE); + // make sure message UIDs are sanitized (BIGINT) foreach ($aUid as $i => $uid) $aUid[$i] = (preg_match('/^[0-9]+$/', $uid) ? $uid : '0'); diff --git a/functions/strings.php b/functions/strings.php index ca09684a..f2d882af 100644 --- a/functions/strings.php +++ b/functions/strings.php @@ -1250,3 +1250,185 @@ function sq_count8bit($string) { function sq_trim_value ( &$value ) { $value = trim($value); } + +/** + * Gathers the list of secuirty tokens currently + * stored in the user's preferences and optionally + * purges old ones from the list. + * + * @param boolean $purge_old Indicates if old tokens + * should be purged from the + * list ("old" is 30 days or + * older unless the administrator + * overrides that value using + * $max_security_token_age in + * config/config_local.php) + * (OPTIONAL; default is to always + * purge old tokens) + * + * @return array The list of tokens + * + * @since 1.4.19 and 1.5.2 + * + */ +function sm_get_user_security_tokens($purge_old=TRUE) +{ + + global $data_dir, $username, $max_token_age_days; + + $tokens = getPref($data_dir, $username, 'security_tokens', ''); + if (($tokens = unserialize($tokens)) === FALSE || !is_array($tokens)) + $tokens = array(); + + // purge old tokens if necessary + // + if ($purge_old) + { + if (empty($max_token_age_days)) $max_token_age_days = 30; + $now = time(); + $discard_token_date = $now - ($max_token_age_days * 86400); + $cleaned_tokens = array(); + foreach ($tokens as $token => $timestamp) + if ($timestamp >= $discard_token_date) + $cleaned_tokens[$token] = $timestamp; + $tokens = $cleaned_tokens; + } + + return $tokens; + +} + +/** + * Generates a security token that is then stored in + * the user's preferences with a timestamp for later + * verification/use. + * + * WARNING: If the administrator has turned the token system + * off by setting $disable_security_tokens to TRUE in + * config/config_local.php, this function will not + * store tokens in the user preferences (but it will + * still generate and return a random string). + * + * @return void + * + * @since 1.4.19 and 1.5.2 + * + */ +function sm_generate_security_token() +{ + + global $data_dir, $username, $disable_security_tokens; + $max_generation_tries = 1000; + + $tokens = sm_get_user_security_tokens(); + + $new_token = GenerateRandomString(12, '', 7); + $count = 0; + while (isset($tokens[$new_token])) + { + $new_token = GenerateRandomString(12, '', 7); + if (++$count > $max_generation_tries) + { + logout_error(_("Fatal token generation error; please contact your system administrator or the SquirrelMail Team")); + exit; + } + } + + // is the token system enabled? CAREFUL! + // + if (!$disable_security_tokens) + { + $tokens[$new_token] = time(); + setPref($data_dir, $username, 'security_tokens', serialize($tokens)); + } + + return $new_token; + +} + +/** + * Validates a given security token and optionally remove it + * from the user's preferences if it was valid. If the token + * is too old but otherwise valid, it will still be rejected. + * + * "Too old" is 30 days or older unless the administrator + * overrides that value using $max_security_token_age in + * config/config_local.php + * + * WARNING: If the administrator has turned the token system + * off by setting $disable_security_tokens to TRUE in + * config/config_local.php, this function will always + * return TRUE. + * + * @param string $token The token to validate + * @param int $validity_period The number of seconds tokens are valid + * for (set to zero to remove valid tokens + * after only one use; use 3600 to allow + * tokens to be reused for an hour) + * (OPTIONAL; default is to only allow tokens + * to be used once) + * @param boolean $show_error Indicates that if the token is not + * valid, this function should display + * a generic error, log the user out + * and exit - this function will never + * return in that case. + * (OPTIONAL; default FALSE) + * + * @return boolean TRUE if the token validated; FALSE otherwise + * + * @since 1.4.19 and 1.5.2 + * + */ +function sm_validate_security_token($token, $validity_period=0, $show_error=FALSE) +{ + + global $data_dir, $username, $max_token_age_days, + $disable_security_tokens; + + // bypass token validation? CAREFUL! + // + if ($disable_security_tokens) return TRUE; + + // don't purge old tokens here because we already + // do it when generating tokens + // + $tokens = sm_get_user_security_tokens(FALSE); + + // token not found? + // + if (empty($tokens[$token])) + { + if (!$show_error) return FALSE; + logout_error(_("This page request could not be verified and appears to have expired.")); + exit; + } + + $now = time(); + $timestamp = $tokens[$token]; + + // whether valid or not, we want to remove it from + // user prefs if it's old enough + // + if ($timestamp < $now - $validity_period) + { + unset($tokens[$token]); + setPref($data_dir, $username, 'security_tokens', serialize($tokens)); + } + + // reject tokens that are too old + // + if (empty($max_token_age_days)) $max_token_age_days = 30; + $old_token_date = $now - ($max_token_age_days * 86400); + if ($timestamp < $old_token_date) + { + if (!$show_error) return FALSE; + logout_error(_("The current page request appears to have originated from an untrusted source.")); + exit; + } + + // token OK! + // + return TRUE; + +} + diff --git a/src/addrbook_search_html.php b/src/addrbook_search_html.php index c9beedde..3f42e639 100644 --- a/src/addrbook_search_html.php +++ b/src/addrbook_search_html.php @@ -78,7 +78,8 @@ function addr_display_result($res, $includesource = true) { global $PHP_SELF, $oTemplate, $oErrorHandler; - echo addForm($PHP_SELF, 'post', 'addressbook'). +//FIXME: no HTML output from core + echo addForm($PHP_SELF, 'post', 'addressbook', '', '', array(), TRUE). addHidden('html_addr_search_done', 'true'); addr_insert_hidden(); @@ -172,7 +173,7 @@ if ($addrquery == '' || ! empty($listall)) { if ($addrquery == '' || sizeof($res) == 0) { //FIXME don't echo HTML from core -- especially convoluted given that there is template code immediately above AND below this block echo '
'. - addForm('compose.php','post','k'); + addForm('compose.php','post','k', '', '', array(), TRUE); addr_insert_hidden(); echo '' . "\n" . '
'; diff --git a/src/addressbook.php b/src/addressbook.php index 0abf138b..24e1fc7f 100644 --- a/src/addressbook.php +++ b/src/addressbook.php @@ -31,6 +31,7 @@ require_once(SM_PATH . 'functions/forms.php'); /** lets get the global vars we may need */ /* From the address form */ +sqgetGlobalVar('smtoken', $submitted_token, SQ_POST, ''); sqgetGlobalVar('addaddr', $addaddr, SQ_POST); sqgetGlobalVar('editaddr', $editaddr, SQ_POST); sqgetGlobalVar('deladdr', $deladdr, SQ_POST); @@ -97,6 +98,9 @@ $form_url = 'addressbook.php'; /* Handle user's actions */ if(sqgetGlobalVar('REQUEST_METHOD', $req_method, SQ_SERVER) && $req_method == 'POST') { + // first, validate security token + sm_validate_security_token($submitted_token, 3600, TRUE); + /************************************************** * Add new address * **************************************************/ diff --git a/src/compose.php b/src/compose.php index bd5599f2..96ec8064 100644 --- a/src/compose.php +++ b/src/compose.php @@ -138,6 +138,8 @@ if ( !sqgetGlobalVar('smaction',$action) ) if ( sqgetGlobalVar('smaction_edit_new',$tmp) ) $action = 'edit_as_new'; } +sqgetGlobalVar('smtoken', $submitted_token, $SQ_GLOBAL, ''); + /** * Here we decode the data passed in from mailto.php. */ @@ -412,6 +414,11 @@ if (empty($mailbox)) { } if ($draft) { + + // validate security token + // + sm_validate_security_token($submitted_token, 3600, TRUE); + /* * Set $default_charset to correspond with the user's selection * of language interface. @@ -466,6 +473,11 @@ if ($draft) { } if ($send) { + + // validate security token + // + sm_validate_security_token($submitted_token, 3600, TRUE); + if (isset($_FILES['attachfile']) && $_FILES['attachfile']['tmp_name'] && $_FILES['attachfile']['tmp_name'] != 'none') { @@ -587,6 +599,11 @@ if ($send) { /* sqimap_logout($imapConnection); */ } } elseif (isset($html_addr_search_done)) { + + // validate security token + // + sm_validate_security_token($submitted_token, 3600, TRUE); + if ($compose_new_win == '1') { compose_Header($color, $mailbox); } @@ -631,6 +648,11 @@ if ($send) { */ include_once('./addrbook_search_html.php'); } elseif (isset($attach)) { + + // validate security token + // + sm_validate_security_token($submitted_token, 3600, TRUE); + if ($compose_new_win == '1') { compose_Header($color, $mailbox); } else { @@ -642,6 +664,11 @@ if ($send) { showInputForm($session); } elseif (isset($sigappend)) { + + // validate security token + // + sm_validate_security_token($submitted_token, 3600, TRUE); + $signature = $idents[$identity]['signature']; $body .= "\n\n".($prefix_sig==true? "-- \n":'').$signature; @@ -652,6 +679,11 @@ elseif (isset($sigappend)) { } showInputForm($session); } elseif (isset($do_delete)) { + + // validate security token + // + sm_validate_security_token($submitted_token, 3600, TRUE); + if ($compose_new_win == '1') { compose_Header($color, $mailbox); } else { @@ -1162,6 +1194,9 @@ function showInputForm ($session, $values=false) { //FIXME: NO HTML IN CORE! echo ">\n"; +//FIXME: DON'T ECHO HTML FROM CORE! + echo addHidden('smtoken', sm_generate_security_token()); + //FIXME: DON'T ECHO HTML FROM CORE! echo addHidden('startMessage', $startMessage); diff --git a/src/folders.php b/src/folders.php index cb1797f6..04d2a04b 100644 --- a/src/folders.php +++ b/src/folders.php @@ -30,6 +30,7 @@ displayPageHeader($color); /* get globals we may need */ sqgetGlobalVar('delimiter', $delimiter, SQ_SESSION); sqgetGlobalVar('smaction', $action, SQ_POST); +sqgetGlobalVar('smtoken', $submitted_token, SQ_POST, ''); /* end of get globals */ @@ -40,6 +41,10 @@ if ( sqgetGlobalVar('smaction', $action, SQ_POST) ) { switch ($action) { case 'create': + + // first, validate security token + sm_validate_security_token($submitted_token, 3600, TRUE); + sqgetGlobalVar('folder_name', $folder_name, SQ_POST); sqgetGlobalVar('subfolder', $subfolder, SQ_POST); sqgetGlobalVar('contain_subs', $contain_subs, SQ_POST); @@ -54,6 +59,10 @@ if ( sqgetGlobalVar('smaction', $action, SQ_POST) ) { sqgetGlobalVar('old_name', $old_name, SQ_POST); folders_rename_getname($imapConnection, $delimiter, $old_name); } else { + + // first, validate security token + sm_validate_security_token($submitted_token, 3600, TRUE); + sqgetGlobalVar('orig', $orig, SQ_POST); sqgetGlobalVar('old_name', $old_name, SQ_POST); folders_rename_do($imapConnection, $delimiter, $orig, $old_name, $new_name); @@ -66,6 +75,10 @@ if ( sqgetGlobalVar('smaction', $action, SQ_POST) ) { } sqgetGlobalVar('folder_name', $folder_name, SQ_POST); if ( sqgetGlobalVar('confirmed', $dummy, SQ_POST) ) { + + // first, validate security token + sm_validate_security_token($submitted_token, 3600, TRUE); + folders_delete_do($imapConnection, $delimiter, $folder_name); $td_str = _("Deleted folder successfully."); } else { @@ -73,11 +86,19 @@ if ( sqgetGlobalVar('smaction', $action, SQ_POST) ) { } break; case 'subscribe': + + // first, validate security token + sm_validate_security_token($submitted_token, 3600, TRUE); + sqgetGlobalVar('folder_names', $folder_names, SQ_POST); folders_subscribe($imapConnection, $folder_names); $td_str = _("Subscribed successfully."); break; case 'unsubscribe': + + // first, validate security token + sm_validate_security_token($submitted_token, 3600, TRUE); + sqgetGlobalVar('folder_names', $folder_names, SQ_POST); folders_unsubscribe($imapConnection, $folder_names); $td_str = _("Unsubscribed successfully."); diff --git a/src/options.php b/src/options.php index 1270739c..66cefb4c 100644 --- a/src/options.php +++ b/src/options.php @@ -103,8 +103,9 @@ function process_optionmode_link($optpage) { /* get the globals that we may need */ sqgetGlobalVar('optpage', $optpage); -sqgetGlobalVar('optmode', $optmode, SQ_FORM); -sqgetGlobalVar('optpage_data',$optpage_data, SQ_POST); +sqgetGlobalVar('optmode', $optmode, SQ_FORM); +sqgetGlobalVar('optpage_data',$optpage_data, SQ_POST); +sqgetGlobalVar('smtoken', $submitted_token, SQ_POST, ''); /* end of getting globals */ /* Make sure we have an Option Page set. Default to main. */ @@ -199,6 +200,12 @@ if ( !@is_file( $optpage_file ) ) { /*** Next, process anything that needs to be processed. ***/ /***********************************************************/ +// security check before saving anything... +//FIXME: what about SMOPT_MODE_LINK?? +if ($optmode == SMOPT_MODE_SUBMIT) { + sm_validate_security_token($submitted_token, 3600, TRUE); +} + $optpage_save_error=array(); if ( isset( $optpage_data ) ) { @@ -464,7 +471,7 @@ if ($optpage == SMOPT_PAGE_MAIN) { } // Begin output form - echo addForm('options.php', 'post', 'option_form') + echo addForm('options.php', 'post', 'option_form', '', '', array(), TRUE) . create_optpage_element($optpage) . create_optmode_element(SMOPT_MODE_SUBMIT); diff --git a/src/options_highlight.php b/src/options_highlight.php index efbcc74c..3de59008 100644 --- a/src/options_highlight.php +++ b/src/options_highlight.php @@ -32,6 +32,7 @@ sqGetGlobalVar('newcolor_input', $newcolor_input); sqGetGlobalVar('color_type', $color_type); sqGetGlobalVar('match_type', $match_type); sqGetGlobalVar('value', $value); +sqgetGlobalVar('smtoken', $submitted_token, SQ_POST, ''); /* end of get globals */ @@ -52,6 +53,10 @@ if (! isset($message_highlight_list)) { if (isset($theid) && ($action == 'delete') || ($action == 'up') || ($action == 'down')) { + + // security check + sm_validate_security_token($submitted_token, 3600, TRUE); + $new_rules = array(); switch($action) { case('delete'): @@ -86,6 +91,9 @@ if (isset($theid) && ($action == 'delete') || exit; } else if ($action == 'save') { + // security check + sm_validate_security_token($submitted_token, 3600, TRUE); + if ($color_type == 1) $newcolor = $newcolor_choose; elseif ($color_type == 2) $newcolor = $newcolor_input; else $newcolor = $color_type; @@ -336,7 +344,7 @@ if ($action == 'edit' || $action == 'add') { $oTemplate->assign('color_radio', ($selected_choose ? 1 : ($selected_input ? 2 : 0))); $oTemplate->assign('color_input', ($selected_input ? $color : '')); - echo addForm('options_highlight.php', 'post', 'f'). + echo addForm('options_highlight.php', 'post', 'f', '', '', array(), TRUE). addHidden('action', 'save'); if($action == 'edit') { echo addHidden('theid', (isset($theid)?$theid:'')); diff --git a/src/options_identities.php b/src/options_identities.php index f4d8eb9a..bb7dde71 100644 --- a/src/options_identities.php +++ b/src/options_identities.php @@ -23,6 +23,7 @@ require('../include/init.php'); /* SquirrelMail required files. */ require_once(SM_PATH . 'functions/identity.php'); +require_once(SM_PATH . 'functions/forms.php'); /* make sure that page is not available when $edit_identity is false */ if (!$edit_identity) { @@ -37,10 +38,14 @@ if (!sqgetGlobalVar('identities', $identities, SQ_SESSION)) { sqgetGlobalVar('newidentities', $newidentities, SQ_POST); sqgetGlobalVar('smaction', $smaction, SQ_POST); sqgetGlobalVar('return', $return, SQ_POST); +sqgetGlobalVar('smtoken', $submitted_token, SQ_POST, ''); // First lets see if there are any actions to perform // if (!empty($smaction) && is_array($smaction)) { + // first do a security check + sm_validate_security_token($submitted_token, 3600, TRUE); + $doaction = ''; $identid = 0; @@ -93,7 +98,8 @@ $a['Signature'] = ''; $i[count($i)] = $a; //FIXME: NO HTML IN THE CORE -echo '
' . "\n"; +echo '' . "\n" + . addHidden('smtoken', sm_generate_security_token()) . "\n"; $oTemplate->assign('identities', $i); $oTemplate->display('options_advidentity_list.tpl'); diff --git a/src/search.php b/src/search.php index 0aeceddc..1cb868c5 100644 --- a/src/search.php +++ b/src/search.php @@ -806,7 +806,8 @@ function asearch_print_form($imapConnection, &$boxes, $mailbox_array, $biop_arra $oTemplate->assign('criteria', $c); - echo '' . "\n"; + echo '' . "\n" + . addHidden('smtoken', sm_generate_security_token()) . "\n"; $oTemplate->display('search_advanced.tpl'); echo "
\n"; } @@ -866,7 +867,8 @@ function asearch_print_form_basic($imapConnection, &$boxes, $mailbox_array, $bio $oTemplate->assign('where_sel', $where); $oTemplate->assign('what_val', $what); - echo '
' . "\n"; + echo '' . "\n" + . addHidden('smtoken', sm_generate_security_token()) . "\n"; $oTemplate->display('search.tpl'); echo "
\n"; } @@ -891,6 +893,7 @@ function sqimap_asearch_get_selectable_unformatted_mailboxes(&$boxes) /* ------------------------ main ------------------------ */ /* get globals we will need */ +sqgetGlobalVar('smtoken', $submitted_token, SQ_GET, ''); sqgetGlobalVar('delimiter', $delimiter, SQ_SESSION); if (!sqgetGlobalVar('checkall',$checkall,SQ_GET)) { @@ -1179,6 +1182,10 @@ if ((empty($submit)) && (!empty($where_array))) { if (!isset($submit)) { $submit = ''; } else { + + // first validate security token + sm_validate_security_token($submitted_token, 3600, TRUE); + switch ($submit) { case $search_button_text: if (asearch_check_query($where_array, $what_array, $exclude_array) == '') { diff --git a/templates/default/addressbook_list.tpl b/templates/default/addressbook_list.tpl index dd1468c3..8ef18f21 100644 --- a/templates/default/addressbook_list.tpl +++ b/templates/default/addressbook_list.tpl @@ -65,6 +65,7 @@ $source = $addresses[$current_backend]; $colspan = $abook_has_extra_field ? 6 : 5; ?>
+
diff --git a/templates/default/folder_manip.tpl b/templates/default/folder_manip.tpl index e113d123..64d0bb1a 100644 --- a/templates/default/folder_manip.tpl +++ b/templates/default/folder_manip.tpl @@ -52,6 +52,7 @@ extract($t);
+
@@ -157,6 +158,7 @@ extract($t); if (!empty($rendel_folder_list)) { ?> + " /> @@ -183,6 +186,7 @@ extract($t); } elseif (!empty($subbox_option_list)) { ?> +
+
+
diff --git a/templates/default/read_menubar_buttons.tpl b/templates/default/read_menubar_buttons.tpl index 83791cb0..092f0702 100644 --- a/templates/default/read_menubar_buttons.tpl +++ b/templates/default/read_menubar_buttons.tpl @@ -123,6 +123,7 @@ if ($nav_on_top) { if ($can_be_deleted) { ?> + value="" /> @@ -139,6 +140,7 @@ if ($nav_on_top) { if ($can_be_moved) { ?> + : diff --git a/templates/default/vcard.tpl b/templates/default/vcard.tpl index bfb7edee..2349da83 100644 --- a/templates/default/vcard.tpl +++ b/templates/default/vcard.tpl @@ -67,6 +67,7 @@ extract($t);
+ @@ -130,4 +131,4 @@ extract($t);
-
\ No newline at end of file +
diff --git a/templates/default_advanced/read_menubar_buttons.tpl b/templates/default_advanced/read_menubar_buttons.tpl index a6b2a329..21ba1a40 100644 --- a/templates/default_advanced/read_menubar_buttons.tpl +++ b/templates/default_advanced/read_menubar_buttons.tpl @@ -131,6 +131,7 @@ if ($nav_on_top) { if ($can_be_deleted) { ?>
+ value="'; ?> if ($can_be_moved) { ?> + : -- 2.25.1