/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** Implementation of the Setup page
*/
#include "config.h"
#include
#include "setup.h"
/*
** Increment the "cfgcnt" variable, so that ETags will know that
** the configuration has changed.
*/
void setup_incr_cfgcnt(void){
static int once = 1;
if( once ){
once = 0;
db_unprotect(PROTECT_CONFIG);
db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'");
if( db_changes()==0 ){
db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)");
}
db_protect_pop();
}
}
/*
** Output a single entry for a menu generated using an HTML table.
** If zLink is neither NULL nor an empty string, then it is the page that
** the menu entry will hyperlink to. If zLink is NULL or "", then
** the menu entry has no hyperlink - it is disabled.
*/
void setup_menu_entry(
const char *zTitle,
const char *zLink,
const char *zDesc /* Caution! Rendered using %s. May contain raw HTML. */
){
@
}
/*
** WEBPAGE: setup
**
** Main menu for the administrative pages. Requires Admin or Setup
** privileges. Links to sub-pages only usable by Setup users are
** shown only to Setup users.
*/
void setup_page(void){
int setup_user = 0;
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
}
setup_user = g.perm.Setup;
style_set_current_feature("setup");
style_header("Server Administration");
/* Make sure the header contains . Issue a warning
** if it does not. */
if( !cgi_header_contains("Configuration Error: Please add
@ <base href="$secureurl/$current_page"> after
@ <head> in the
@ HTML header!
}
#if !defined(_WIN32)
/* Check for /dev/null and /dev/urandom. We want both devices to be present,
** but they are sometimes omitted (by mistake) from chroot jails. */
if( access("/dev/null", R_OK|W_OK) ){
@
WARNING: Device "/dev/null" is not available
@ for reading and writing.
}
if( access("/dev/urandom", R_OK) ){
@
WARNING: Device "/dev/urandom" is not available
@ for reading. This means that the pseudo-random number generator used
@ by SQLite will be poorly seeded.
}
#endif
@
setup_menu_entry("Users", "setup_ulist",
"Grant privileges to individual users.");
if( setup_user ){
setup_menu_entry("Access", "setup_access",
"Control access settings.");
setup_menu_entry("Configuration", "setup_config",
"Configure the WWW components of the repository");
}
setup_menu_entry("Security-Audit", "secaudit0",
"Analyze the current configuration for security problems");
if( setup_user ){
setup_menu_entry("Robot-Defense", "setup_robot",
"Settings for configure defense against robots");
setup_menu_entry("Settings", "setup_settings",
"Web interface to the \"fossil settings\" command");
}
setup_menu_entry("Timeline", "setup_timeline",
"Timeline display preferences");
if( setup_user ){
setup_menu_entry("Login-Group", "setup_login_group",
"Manage single sign-on between this repository and others"
" on the same server");
setup_menu_entry("Tickets", "tktsetup",
"Configure the trouble-ticketing system for this repository");
setup_menu_entry("Wiki", "setup_wiki",
"Configure the wiki for this repository");
setup_menu_entry("Interwiki Map", "intermap",
"Mapping keywords for interwiki links");
setup_menu_entry("Chat", "setup_chat",
"Configure the chatroom");
setup_menu_entry("Forum", "setup_forum",
"Forum config and metrics");
}
setup_menu_entry("Search","srchsetup",
"Configure the built-in search engine");
setup_menu_entry("URL Aliases", "waliassetup",
"Configure URL aliases");
if( setup_user ){
setup_menu_entry("Notification", "setup_notification",
"Automatic notifications of changes via outbound email");
#if 0 /* Disabled for now. Does this even work? */
setup_menu_entry("Transfers", "xfersetup",
"Configure the transfer system for this repository");
#endif
}
setup_menu_entry("Skins", "setup_skin_admin",
"Select and/or modify the web interface \"skins\"");
setup_menu_entry("Moderation", "setup_modreq",
"Enable/Disable requiring moderator approval of Wiki and/or Ticket"
" changes and attachments.");
setup_menu_entry("Ad-Unit", "setup_adunit",
"Edit HTML text for an ad unit inserted after the menu bar");
setup_menu_entry("URLs & Checkouts", "urllist",
"Show URLs used to access this repo and known check-outs");
if( setup_user ){
setup_menu_entry("Web-Cache", "cachestat",
"View the status of the expensive-page cache");
}
setup_menu_entry("Logo", "setup_logo",
"Change the logo and background images for the server");
setup_menu_entry("Shunned", "shun",
"Show artifacts that are shunned by this repository");
setup_menu_entry("Log Files", "setup-logmenu",
"A menu of available log files");
setup_menu_entry("Unversioned Files", "uvlist?byage=1",
"Show all unversioned files held");
setup_menu_entry("Stats", "stat",
"Repository Status Reports");
setup_menu_entry("Sitemap", "sitemap",
"Links to miscellaneous pages");
if( setup_user ){
setup_menu_entry("SQL", "admin_sql",
"Enter raw SQL commands");
setup_menu_entry("TH1", "admin_th1",
"Enter raw TH1 commands");
}
@
style_finish_page();
}
/*
** WEBPAGE: setup-logmenu
**
** Show a menu of available log renderings accessible to an administrator,
** together with a succinct explanation of each.
**
** This page is only accessible by administrators.
*/
void setup_logmenu_page(void){
Blob desc;
int bErrLog; /* True if Error Log enabled */
blob_init(&desc, 0, 0);
/* Administrator access only */
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_header("Log Menu");
@
if( db_get_boolean("admin-log",1)==0 ){
blob_appendf(&desc,
"The admin log records configuration changes to the repository.\n"
"Disabled: Turn on the "
" admin-log setting to enable."
);
setup_menu_entry("Admin Log", 0, blob_str(&desc));
blob_reset(&desc);
}else{
setup_menu_entry("Admin Log", "admin_log",
"The admin log records configuration changes to the repository\n"
"in the \"admin_log\" table.\n"
);
}
setup_menu_entry("Xfer Log", "rcvfromlist",
"The artifact log records when new content is added in the\n"
"\"rcvfrom\" table.\n"
);
if( db_get_boolean("access-log",1) ){
setup_menu_entry("User Log", "user_log",
"Login attempts recorded in the \"accesslog\" table."
);
}else{
blob_appendf(&desc,
"Login attempts recorded in the \"accesslog\" table.\n"
"Disabled: Turn on the "
"access-log setting to enable."
);
setup_menu_entry("User Log", 0, blob_str(&desc));
blob_reset(&desc);
}
blob_appendf(&desc,
"A separate text file to which warning and error\n"
"messages are appended. A single error log can and often is shared\n"
"across multiple repositories.\n"
);
if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){
blob_appendf(&desc,"Disabled: "
"To enable the error log ");
if( fossil_strcmp(g.zCmdName, "cgi")==0 ){
blob_appendf(&desc,
"make an entry like \"errorlog: FILENAME\""
" in the CGI script at %h",
P("SCRIPT_FILENAME")
);
}else{
blob_appendf(&desc,
" add the \"--errorlog FILENAME\" option to the\n"
"\"%h %h\" command that launched the server.",
g.argv[0], g.zCmdName
);
}
bErrLog = 0;
}else{
blob_appendf(&desc,"In this repository, the error log is the file "
"named \"%s\".", g.zErrlog);
bErrLog = 1;
}
setup_menu_entry("Error Log", bErrLog ? "errorlog" : 0, blob_str(&desc));
blob_reset(&desc);
@
style_finish_page();
}
/*
** Generate a checkbox for an attribute.
*/
void onoff_attribute(
const char *zLabel, /* The text label on the checkbox */
const char *zVar, /* The corresponding row in the CONFIG table */
const char *zQParm, /* The query parameter */
int dfltVal, /* Default value if CONFIG table entry does not exist */
int disabled /* 1 if disabled */
){
const char *zQ = P(zQParm);
int iVal = db_get_boolean(zVar, dfltVal);
if( zQ==0 && !disabled && P("submit") ){
zQ = "off";
}
if( zQ ){
int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
if( iQ!=iVal && cgi_csrf_safe(2) ){
db_protect_only(PROTECT_NONE);
db_set(zVar/*works-like:"x"*/, iQ ? "1" : "0", 0);
db_protect_pop();
setup_incr_cfgcnt();
admin_log("Set option [%q] to [%q].",
zVar, iQ ? "on" : "off");
iVal = iQ;
}
}
@
}
/*
** Generate an entry box for an attribute.
*/
void entry_attribute(
const char *zLabel, /* The text label on the entry box */
int width, /* Width of the entry box */
const char *zVar, /* The corresponding row in the CONFIG table */
const char *zQParm, /* The query parameter */
const char *zDflt, /* Default value if CONFIG table entry does not exist */
int disabled /* 1 if disabled */
){
const char *zVal = db_get(zVar, zDflt);
const char *zQ = P(zQParm);
if( zQ && fossil_strcmp(zQ,zVal)!=0 && cgi_csrf_safe(2) ){
const int nZQ = (int)strlen(zQ);
setup_incr_cfgcnt();
db_protect_only(PROTECT_NONE);
db_set(zVar/*works-like:"x"*/, zQ, 0);
db_protect_pop();
admin_log("Set entry_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
zVal = zQ;
}
@ %s(zLabel)
}
/*
** Generate a text box for an attribute.
*/
const char *textarea_attribute(
const char *zLabel, /* The text label on the textarea */
int rows, /* Rows in the textarea */
int cols, /* Columns in the textarea */
const char *zVar, /* The corresponding row in the CONFIG table */
const char *zQP, /* The query parameter */
const char *zDflt, /* Default value if CONFIG table entry does not exist */
int disabled /* 1 if the textarea should not be editable */
){
const char *z = db_get(zVar, zDflt);
const char *zQ = P(zQP);
if( zQ && !disabled && fossil_strcmp(zQ,z)!=0 && cgi_csrf_safe(2) ){
const int nZQ = (int)strlen(zQ);
db_protect_only(PROTECT_NONE);
db_set(zVar/*works-like:"x"*/, zQ, 0);
db_protect_pop();
setup_incr_cfgcnt();
admin_log("Set textarea_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
z = zQ;
}
if( rows>0 && cols>0 ){
@
if( *zLabel ){
@ %s(zLabel)
}
}
return z;
}
/*
** Generate a text box for an attribute.
*/
void multiple_choice_attribute(
const char *zLabel, /* The text label on the menu */
const char *zVar, /* The corresponding row in the CONFIG table */
const char *zQP, /* The query parameter */
const char *zDflt, /* Default value if CONFIG table entry does not exist */
int nChoice, /* Number of choices */
const char *const *azChoice /* Choices in pairs (VAR value, Display) */
){
const char *z = db_get(zVar, zDflt);
const char *zQ = P(zQP);
int i;
if( zQ && fossil_strcmp(zQ,z)!=0 && cgi_csrf_safe(2) ){
const int nZQ = (int)strlen(zQ);
db_unprotect(PROTECT_ALL);
db_set(zVar/*works-like:"x"*/, zQ, 0);
setup_incr_cfgcnt();
db_protect_pop();
admin_log("Set multiple_choice_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
z = zQ;
}
@ %h(zLabel)
}
/*
** Insert code into the current page that allows the user to configure
** auto-hyperlink related robot defense settings.
*/
static void addAutoHyperlinkSettings(void){
static const char *const azDefenseOpts[] = {
"0", "Off",
"2", "UserAgent only",
"1", "UserAgent and Javascript",
};
multiple_choice_attribute(
"Enable hyperlinks base on User-Agent and/or Javascript",
"auto-hyperlink", "autohyperlink", "1",
count(azDefenseOpts)/2, azDefenseOpts);
@
entry_attribute("Delay in milliseconds before enabling hyperlinks", 5,
"auto-hyperlink-delay", "ah-delay", "50", 0);
@
onoff_attribute("Also require a mouse event before enabling hyperlinks",
"auto-hyperlink-mouseover", "ahmo", 0, 0);
@
Enable hyperlinks (the equivalent of the "h" permission) for all users,
@ including user "nobody" if the request appears to be from a human.
@ Disabling hyperlinks helps prevent robots from walking your site and
@ soaking up all your CPU and bandwidth.
@ If this setting is "UserAgent only" (2) then the
@ UserAgent string is the only factor considered. If the value of this
@ setting is "UserAgent And Javascript" (1) then Javascript is added that
@ runs after the page loads and fills in the href= values of <a>
@ elements. In either case, <a> tags are not generated if the
@ UserAgent string indicates that the client is a robot.
@ (Property: "auto-hyperlink")
@
@
For maximum robot defense, "Delay" should be at least 50 milliseconds
@ and "require a mouse event" should be turned on. These values only come
@ into play when the main auto-hyperlink settings is 2 ("UserAgent and
@ Javascript").
@ (Properties: "auto-hyperlink-delay" and "auto-hyperlink-mouseover")
@
@
To see if Javascript-base hyperlink enabling mechanism is working,
@ visit the /test-env page from a separate
@ web browser that is not logged in, even as "anonymous" and verify
@ that the "g.jsHref" value is "1".
A Fossil website can have billions of pages in its tree, even for a
@ modest project. Many of those pages (examples: diffs and tarballs)
@ might be expensive to compute. A robot that tries to walk the entire
@ website can present a crippling CPU and bandwidth load.
@
@
The settings on this page are intended to help administrators
@ defend against abusive robots.
@
@
This repository (in the file named "%h(zSelfRepo)")
@ is not currently part of any login-group.
@ To join a login group, fill out the form below.
@
@
}else{
int n = 0;
@
This repository (in the file "%h(zSelfRepo)")
@ is currently part of the "%h(zGroup)" login group.
@ Other repositories in that group are:
@
@
Project Name
@
Repository File
db_prepare(&q,
"SELECT value,"
" (SELECT value FROM config"
" WHERE name=('peer-name-' || substr(x.name,11)))"
" FROM config AS x"
" WHERE name GLOB 'peer-repo-*'"
" ORDER BY value"
);
while( db_step(&q)==SQLITE_ROW ){
const char *zRepo = db_column_text(&q, 0);
const char *zTitle = db_column_text(&q, 1);
n++;
@