/* ** 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 #include "config.h" #include "setup.h" /* ** The table of web pages supported by this application is generated ** automatically by the "mkindex" program and written into a file ** named "page_index.h". We include that file here to get access ** to the table. */ #include "page_index.h" /* ** Output a single entry for a menu generated using an HTML table. ** If zLink is not NULL or 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 ){ @ if( zLink && zLink[0] ){ @ %h(zTitle) }else{ @ %h(zTitle) } @ %h(zDesc) } /* ** WEBPAGE: /setup */ void setup_page(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } 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="$baseurl/$current_page"> after @ <head> in the HTML header!

} @ setup_menu_entry("Users", "setup_ulist", "Grant privileges to individual users."); 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("Settings", "setup_settings", "Web interface to the \"fossil settings\" command"); setup_menu_entry("Timeline", "setup_timeline", "Timeline display preferences"); 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("Transfers", "xfersetup", "Configure the transfer system for this repository"); setup_menu_entry("Skins", "setup_skin", "Select from a menu of prepackaged \"skins\" for the web interface"); setup_menu_entry("CSS", "setup_editcss", "Edit the Cascading Style Sheet used by all pages of this repository"); setup_menu_entry("Header", "setup_header", "Edit HTML text inserted at the top of every page"); setup_menu_entry("Footer", "setup_footer", "Edit HTML text inserted at the bottom of every page"); 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("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", "rcvfromlist", "A record of received artifacts and their sources"); setup_menu_entry("User-Log", "access_log", "A record of login attempts"); setup_menu_entry("Stats", "stat", "Display repository statistics"); setup_menu_entry("SQL", "admin_sql", "Enter raw SQL commands"); setup_menu_entry("TH1", "admin_th1", "Enter raw TH1 commands"); @
style_footer(); } /* ** WEBPAGE: setup_ulist ** ** Show a list of users. Clicking on any user jumps to the edit ** screen for that user. */ void setup_ulist(void){ Stmt s; int prevLevel = 0; login_check_credentials(); if( !g.perm.Admin ){ login_needed(); return; } style_submenu_element("Add", "Add User", "setup_uedit"); style_header("User List"); @ @
@ Users: @ prevLevel = 0; db_prepare(&s, "SELECT uid, login, cap, info, 1 FROM user" " WHERE login IN ('anonymous','nobody','developer','reader') " " UNION ALL " "SELECT uid, login, cap, info, 2 FROM user" " WHERE login NOT IN ('anonymous','nobody','developer','reader') " "ORDER BY 5, 2" ); while( db_step(&s)==SQLITE_ROW ){ int iLevel = db_column_int(&s, 4); const char *zCap = db_column_text(&s, 2); const char *zLogin = db_column_text(&s, 1); if( iLevel>prevLevel ){ if( prevLevel>0 ){ @ } if( iLevel==1 ){ @ @ @ @ @ }else{ @ @ @ @ @ } prevLevel = iLevel; } @ @ @ @ @ } @

CategoryCapabilitiesNotes
User IDCapabilitiesContact Info
if( g.perm.Admin && (zCap[0]!='s' || g.perm.Setup) ){ @ } @ %h(zLogin) if( g.perm.Admin ){ @ } @ %s(zCap)%h(db_column_text(&s,3))
@
@ Notes: @
    @
  1. The permission flags are as follows:

    @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @
    aAdmin: Create and delete users
    bAttach: Add attachments to wiki or tickets
    cAppend-Tkt: Append to tickets
    dDelete: Delete wiki and tickets
    eEmail: View sensitive data such as EMail addresses
    fNew-Wiki: Create new wiki pages
    gClone: Clone the repository
    hHyperlinks: Show hyperlinks to detailed @ repository history
    iCheck-In: Commit new versions in the repository
    jRead-Wiki: View wiki pages
    kWrite-Wiki: Edit wiki pages
    lMod-Wiki: Moderator for wiki pages
    mAppend-Wiki: Append to wiki pages
    nNew-Tkt: Create new tickets
    oCheck-Out: Check out versions
    pPassword: Change your own password
    qMod-Tkt: Moderator for tickets
    rRead-Tkt: View tickets
    sSetup/Super-user: Setup and configure this website
    tTkt-Report: Create new bug summary reports
    uReader: Inherit privileges of @ user reader
    vDeveloper: Inherit privileges of @ user developer
    wWrite-Tkt: Edit tickets
    xPrivate: Push and/or pull private branches
    zZip download: Download a baseline via the @ /zip URL even without @ checkout @ and history permissions
    @
  2. @ @
  3. @ Every user, logged in or not, inherits the privileges of @ nobody. @

  4. @ @
  5. @ Any human can login as anonymous since the @ password is clearly displayed on the login page for them to type. The @ purpose of requiring anonymous to log in is to prevent access by spiders. @ Every logged-in user inherits the combined privileges of @ anonymous and @ nobody. @

  6. @ @
  7. @ Users with privilege v inherit the combined @ privileges of developer, @ anonymous, and @ nobody. @

  8. @ @
@
style_footer(); } /* ** Return true if zPw is a valid password string. A valid ** password string is: ** ** (1) A zero-length string, or ** (2) a string that contains a character other than '*'. */ static int isValidPwString(const char *zPw){ if( zPw==0 ) return 0; if( zPw[0]==0 ) return 1; while( zPw[0]=='*' ){ zPw++; } return zPw[0]!=0; } /* ** WEBPAGE: /setup_uedit */ void user_edit(void){ const char *zId, *zLogin, *zInfo, *zCap, *zPw; const char *zGroup; const char *zOldLogin; int doWrite; int uid, i; int higherUser = 0; /* True if user being edited is SETUP and the */ /* user doing the editing is ADMIN. Disallow editing */ char *inherit[128]; int a[128]; char *oa[128]; /* Must have ADMIN privileges to access this page */ login_check_credentials(); if( !g.perm.Admin ){ login_needed(); return; } /* Check to see if an ADMIN user is trying to edit a SETUP account. ** Don't allow that. */ zId = PD("id", "0"); uid = atoi(zId); if( zId && !g.perm.Setup && uid>0 ){ char *zOldCaps; zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid); higherUser = zOldCaps && strchr(zOldCaps,'s'); } if( P("can") ){ cgi_redirect("setup_ulist"); return; } /* If we have all the necessary information, write the new or ** modified user record. After writing the user record, redirect ** to the page that displays a list of users. */ doWrite = cgi_all("login","info","pw") && !higherUser; if( doWrite ){ char c; char zCap[50], zNm[4]; zNm[0] = 'a'; zNm[2] = 0; for(i=0, c='a'; c<='z'; c++){ zNm[1] = c; a[c&0x7f] = (c!='s' || g.perm.Setup) && P(zNm)!=0; if( a[c&0x7f] ) zCap[i++] = c; } zCap[i] = 0; zPw = P("pw"); zLogin = P("login"); if( strlen(zLogin)==0 ){ style_header("User Creation Error"); @ Empty login not allowed. @ @

[Bummer]

style_footer(); return; } if( isValidPwString(zPw) ){ zPw = sha1_shared_secret(zPw, zLogin, 0); }else{ zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid); } zOldLogin = db_text(0, "SELECT login FROM user WHERE uid=%d", uid); if( uid>0 && db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid) ){ style_header("User Creation Error"); @ Login "%h(zLogin)" is already used by @ a different user. @ @

[Bummer]

style_footer(); return; } login_verify_csrf_secret(); db_multi_exec( "REPLACE INTO user(uid,login,info,pw,cap,mtime) " "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s',now())", uid, P("login"), P("info"), zPw, zCap ); if( atoi(PD("all","0"))>0 ){ Blob sql; char *zErr = 0; blob_zero(&sql); if( zOldLogin==0 ){ blob_appendf(&sql, "INSERT INTO user(login)" " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);", zLogin, zLogin ); zOldLogin = zLogin; } blob_appendf(&sql, "UPDATE user SET login=%Q," " pw=coalesce(shared_secret(%Q,%Q," "(SELECT value FROM config WHERE name='project-code')),pw)," " info=%Q," " cap=%Q," " mtime=now()" " WHERE login=%Q;", zLogin, P("pw"), zLogin, P("info"), zCap, zOldLogin ); login_group_sql(blob_str(&sql), "
  • ", "
  • \n", &zErr); blob_reset(&sql); if( zErr ){ style_header("User Change Error"); @ %s(zErr) @ @

    [Bummer]

    style_footer(); return; } } cgi_redirect("setup_ulist"); return; } /* Load the existing information about the user, if any */ zLogin = ""; zInfo = ""; zCap = ""; zPw = ""; for(i='a'; i<='z'; i++) oa[i] = ""; if( uid ){ zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); for(i=0; zCap[i]; i++){ char c = zCap[i]; if( c>='a' && c<='z' ) oa[c&0x7f] = " checked=\"checked\""; } } /* figure out inherited permissions */ memset(inherit, 0, sizeof(inherit)); if( fossil_strcmp(zLogin, "developer") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='developer'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = ""; } free(z2); } if( fossil_strcmp(zLogin, "reader") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='reader'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = ""; } free(z2); } if( fossil_strcmp(zLogin, "anonymous") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='anonymous'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = ""; } free(z2); } if( fossil_strcmp(zLogin, "nobody") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='nobody'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = ""; } free(z2); } /* Begin generating the page */ style_submenu_element("Cancel", "Cancel", "setup_ulist"); if( uid ){ style_header(mprintf("Edit User %h", zLogin)); }else{ style_header("Add A New User"); } @
    @
    login_insert_csrf_secret(); @ @ @ if( uid ){ @ }else{ @ } @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ if( zPw[0] ){ /* Obscure the password for all users */ @ }else{ /* Show an empty password as an empty input field */ @ } @ zGroup = login_group_name(); if( zGroup ){ @ @ @ } if( !higherUser ){ @ @ @ @ } @
    User ID:%d(uid) (new user)
    Login:
    Contact Info:
    Capabilities: #define B(x) inherit[x] @
    if( g.perm.Setup ){ @
    } @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @
    @ @
    @
    Password:
    Scope: @ @ Apply changes to this repository only.
    @ @ Apply changes to all repositories in the "%h(zGroup)" @ login group.
     
    @
    @
    @

    Privileges And Capabilities:

    @ @ @

    Special Logins

    @ @ style_footer(); } /* ** Generate a checkbox for an attribute. */ static void onoff_attribute( const char *zLabel, /* The text label on the checkbox */ const char *zVar, /* The corresponding row in the VAR table */ const char *zQParm, /* The query parameter */ int dfltVal /* Default value if VAR table entry does not exist */ ){ const char *zQ = P(zQParm); int iVal = db_get_boolean(zVar, dfltVal); if( zQ==0 && P("submit") ){ zQ = "off"; } if( zQ ){ int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ); if( iQ!=iVal ){ login_verify_csrf_secret(); db_set(zVar, iQ ? "1" : "0", 0); iVal = iQ; } } if( iVal ){ @ @ %s(zLabel) }else{ @ %s(zLabel) } } /* ** 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 VAR table */ const char *zQParm, /* The query parameter */ char *zDflt /* Default value if VAR table entry does not exist */ ){ const char *zVal = db_get(zVar, zDflt); const char *zQ = P(zQParm); if( zQ && fossil_strcmp(zQ,zVal)!=0 ){ login_verify_csrf_secret(); db_set(zVar, zQ, 0); zVal = zQ; } @ @ %s(zLabel) } /* ** Generate a text box for an attribute. */ static void 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 VAR table */ const char *zQP, /* The query parameter */ const char *zDflt /* Default value if VAR table entry does not exist */ ){ const char *z = db_get(zVar, (char*)zDflt); const char *zQ = P(zQP); if( zQ && fossil_strcmp(zQ,z)!=0 ){ login_verify_csrf_secret(); db_set(zVar, zQ, 0); z = zQ; } if( rows>0 && cols>0 ){ @ if (zLabel && *zLabel){ @ %s(zLabel) } } } /* ** WEBPAGE: setup_access */ void setup_access(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } style_header("Access Control Settings"); db_begin_transaction(); @
    login_insert_csrf_secret(); @
    onoff_attribute("Require password for local access", "localauth", "localauth", 0); @

    When enabled, the password sign-in is always required for @ web access. When disabled, unrestricted web access from 127.0.0.1 @ is allowed for the fossil ui command or @ from the fossil server, @ fossil http commands when the @ "--localauth" command line options is used, or from the @ fossil cgi if a line containing @ the word "localauth" appears in the CGI script. @ @

    A password is always required if any one or more @ of the following are true: @

      @
    1. This button is checked @
    2. The inbound TCP/IP connection is not from 127.0.0.1 @
    3. The server is started using either of the @ fossil server or @ fossil http commands @ without the "--localauth" option. @
    4. The server is started from CGI without the "localauth" keyword @ in the CGI script. @
    @ @
    onoff_attribute("Enable /test_env", "test_env_enable", "test_env_enable", 0); @

    When enabled, the %h(g.zBaseURL)/test_env URL is available to all @ users. When disabled (the default) only users Admin and Setup can visit @ the /test_env page. @

    @ @
    onoff_attribute("Allow REMOTE_USER authentication", "remote_user_ok", "remote_user_ok", 0); @

    When enabled, if the REMOTE_USER environment variable is set to the @ login name of a valid user and no other login credentials are available, @ then the REMOTE_USER is accepted as an authenticated user. @

    @ @
    entry_attribute("IP address terms used in login cookie", 3, "ip-prefix-terms", "ipt", "2"); @

    The number of octets of of the IP address used in the login cookie. @ Set to zero to omit the IP address from the login cookie. A value of @ 2 is recommended. @

    @ @
    entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766"); @

    The number of hours for which a login is valid. This must be a @ positive number. The default is 8766 hours which is approximately equal @ to a year.

    @
    entry_attribute("Download packet limit", 10, "max-download", "mxdwn", "5000000"); @

    Fossil tries to limit out-bound sync, clone, and pull packets @ to this many bytes, uncompressed. If the client requires more data @ than this, then the client will issue multiple HTTP requests. @ Values below 1 million are not recommended. 5 million is a @ reasonable number.

    @
    entry_attribute("Download time limit", 11, "max-download-time", "mxdwnt", "30"); @

    Fossil tries to spend less than this many seconds gathering @ the out-bound data of sync, clone, and pull packets. @ If the client request takes longer, a partial reply is given similar @ to the download packet limit. 30s is a reasonable default.

    @
    onoff_attribute( "Enable hyperlinks for \"nobody\" based on User-Agent and Javascript", "auto-hyperlink", "autohyperlink", 1); @

    Enable hyperlinks (the equivalent of the "h" permission) for all users @ including user "nobody", as long as (1) the User-Agent string in the @ HTTP header indicates that the request is coming from an actual human @ being and not a a robot or spider and (2) the user agent is able to @ run Javascript in order to set the href= attribute of hyperlinks. Bots @ and spiders can specify whatever User-Agent string they that want and @ they can run javascript just like browsers. But most bots don't go to @ that much trouble so this is normally an effective defense.

    @ @

    You do not normally want a bot to walk your entire repository because @ if it does, your server will end up computing diffs and annotations for @ every historical version of every file and creating ZIPs and tarballs of @ every historical check-in, which can use a lot of CPU and bandwidth @ even for relatively small projects.

    @
    onoff_attribute("Require a CAPTCHA if not logged in", "require-captcha", "reqcapt", 1); @

    Require a CAPTCHA for edit operations (appending, creating, or @ editing wiki or tickets or adding attachments to wiki or tickets) @ for users who are not logged in.

    @
    entry_attribute("Public pages", 30, "public-pages", "pubpage", ""); @

    A comma-separated list of glob patterns for pages that are accessible @ without needing a login and using the privileges given by the @ "Default privileges" setting below. Example use case: Set this field @ to "/doc/trunk/www/*" to give anonymous users read-only permission to the @ latest version of the embedded documentation in the www/ folder without @ allowing them to see the rest of the source code. @

    @
    onoff_attribute("Allow users to register themselves", "self-register", "selfregister", 0); @

    Allow users to register themselves through the HTTP UI. @ The registration form always requires filling in a CAPTCHA @ (auto-captcha setting is ignored). Still, bear in mind that anyone @ can register under any user name. This option is useful for public projects @ where you do not want everyone in any ticket discussion to be named @ "Anonymous".

    @
    entry_attribute("Default privileges", 10, "default-perms", "defaultperms", "u"); @

    Permissions given to users that...

    • register themselves using @ the self-registration procedure (if enabled), or
    • access "public" @ pages identified by the public-pages glob pattern above, or
    • @ are users newly created by the administrator.
    @

    @
    onoff_attribute("Show javascript button to fill in CAPTCHA", "auto-captcha", "autocaptcha", 0); @

    When enabled, a button appears on the login screen for user @ "anonymous" that will automatically fill in the CAPTCHA password. @ This is less secure than forcing the user to do it manually, but is @ probably secure enough and it is certainly more convenient for @ anonymous users.

    @
    @

    @
    db_end_transaction(0); style_footer(); } /* ** WEBPAGE: setup_login_group */ void setup_login_group(void){ const char *zGroup; char *zErrMsg = 0; Blob fullName; char *zSelfRepo; const char *zRepo = PD("repo", ""); const char *zLogin = PD("login", ""); const char *zPw = PD("pw", ""); const char *zNewName = PD("newname", "New Login Group"); login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } file_canonical_name(g.zRepositoryName, &fullName, 0); zSelfRepo = mprintf(blob_str(&fullName)); blob_reset(&fullName); if( P("join")!=0 ){ login_group_join(zRepo, zLogin, zPw, zNewName, &zErrMsg); }else if( P("leave") ){ login_group_leave(&zErrMsg); } style_header("Login Group Configuration"); if( zErrMsg ){ @

    %s(zErrMsg)

    } zGroup = login_group_name(); if( zGroup==0 ){ @

    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.

    @ @
    login_insert_csrf_secret(); @
    @ @ @ @ @ @ @ @ @ @ @ @ @ @ @
    Repository filename in group to join: @
    Login on the above repo: @
    Password: @
    Name of login-group: @ @ (only used if creating a new login-group).
    @
    }else{ Stmt q; 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:

    @ @ 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++; @ } db_finalize(&q); @
    Project Name @ Repository File
    %d(n). @ %h(zTitle)%h(zRepo)
    @ @

    login_insert_csrf_secret(); @ To leave this login group press @ @

    } style_footer(); } /* ** WEBPAGE: setup_timeline */ void setup_timeline(void){ double tmDiff; char zTmDiff[20]; login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } style_header("Timeline Display Preferences"); db_begin_transaction(); @
    login_insert_csrf_secret(); @
    onoff_attribute("Allow block-markup in timeline", "timeline-block-markup", "tbm", 0); @

    In timeline displays, check-in comments can be displayed with or @ without block markup (paragraphs, tables, etc.)

    @
    onoff_attribute("Plaintext comments on timelines", "timeline-plaintext", "tpt", 0); @

    In timeline displays, check-in comments are displayed literally, @ without any wiki or HTML interpretation.

    @
    onoff_attribute("Use Universal Coordinated Time (UTC)", "timeline-utc", "utc", 1); @

    Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or @ Zulu) instead of in local time. On this server, local time is currently g.fTimeFormat = 2; tmDiff = db_double(0.0, "SELECT julianday('now')"); tmDiff = db_double(0.0, "SELECT (julianday(%.17g,'localtime')-julianday(%.17g))*24.0", tmDiff, tmDiff); sqlite3_snprintf(sizeof(zTmDiff), zTmDiff, "%.1f", tmDiff); if( strcmp(zTmDiff, "0.0")==0 ){ @ the same as UTC and so this setting will make no difference in @ the display.

    }else if( tmDiff<0.0 ){ sqlite3_snprintf(sizeof(zTmDiff), zTmDiff, "%.1f", -tmDiff); @ %s(zTmDiff) hours behind UTC.

    }else{ @ %s(zTmDiff) hours ahead of UTC.

    } @
    onoff_attribute("Show version differences by default", "show-version-diffs", "vdiff", 0); @

    On the version-information pages linked from the timeline can either @ show complete diffs of all file changes, or can just list the names of @ the files that have changed. Users can get to either page by @ clicking. This setting selects the default.

    @
    entry_attribute("Max timeline comment length", 6, "timeline-max-comment", "tmc", "0"); @

    The maximum length of a comment to be displayed in a timeline. @ "0" there is no length limit.

    @
    @

    @
    db_end_transaction(0); style_footer(); } /* ** WEBPAGE: setup_settings */ void setup_settings(void){ struct stControlSettings const *pSet; login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } style_header("Settings"); db_open_local(); db_begin_transaction(); @

    This page provides a simple interface to the "fossil setting" command. @ See the "fossil help setting" output below for further information on @ the meaning of each setting.


    @
    @
    login_insert_csrf_secret(); for(pSet=ctrlSettings; pSet->name!=0; pSet++){ if( pSet->width==0 ){ onoff_attribute(pSet->name, pSet->name, pSet->var!=0 ? pSet->var : pSet->name, is_truth(pSet->def)); if( pSet->versionable ){ @ (v)
    } else { @
    } } } @
    for(pSet=ctrlSettings; pSet->name!=0; pSet++){ if( pSet->width!=0 && !pSet->versionable){ entry_attribute(pSet->name, /*pSet->width*/ 25, pSet->name, pSet->var!=0 ? pSet->var : pSet->name, (char*)pSet->def); @
    } } @
    for(pSet=ctrlSettings; pSet->name!=0; pSet++){ if( pSet->width!=0 && pSet->versionable){ textarea_attribute(pSet->name, /*rows*/ 3, /*cols*/ 20, pSet->name, pSet->var!=0 ? pSet->var : pSet->name, (char*)pSet->def); @ (v)
    } } @
    @

    @
    @

    Settings marked with (v) are 'versionable' and will be overridden @ by the contents of files named .fossil-settings/PROPERTY.

    @

    @ These settings work in the same way, as the set @ commandline:
    @

    %s(zHelp_setting_cmd)
    db_end_transaction(0); style_footer(); } /* ** WEBPAGE: setup_config */ void setup_config(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } style_header("WWW Configuration"); db_begin_transaction(); @
    login_insert_csrf_secret(); @
    entry_attribute("Project Name", 60, "project-name", "pn", ""); @

    Give your project a name so visitors know what this site is about. @ The project name will also be used as the RSS feed title.

    @
    textarea_attribute("Project Description", 3, 80, "project-description", "pd", ""); @

    Describe your project. This will be used in page headers for search @ engines as well as a short RSS description.

    @
    onoff_attribute("Enable WYSIWYG Wiki Editing", "wysiwyg-wiki", "wysiwyg-wiki", 0); @

    Enable what-you-see-is-what-you-get (WYSIWYG) editing of wiki pages. @ The WYSIWYG editor generates HTML instead of markup, which makes @ subsequent manual editing more difficult.

    @
    entry_attribute("Index Page", 60, "index-page", "idxpg", "/home"); @

    Enter the pathname of the page to display when the "Home" menu @ option is selected and when no pathname is @ specified in the URL. For example, if you visit the url:

    @ @

    %h(g.zBaseURL)

    @ @

    And you have specified an index page of "/home" the above will @ automatically redirect to:

    @ @

    %h(g.zBaseURL)/home

    @ @

    The default "/home" page displays a Wiki page with the same name @ as the Project Name specified above. Some sites prefer to redirect @ to a documentation page (ex: "/doc/tip/index.wiki") or to "/timeline".

    @ @

    Note: To avoid a redirect loop or other problems, this entry must @ begin with "/" and it must specify a valid page. For example, @ "/home" will work but "home" will not, since it omits the @ leading "/".

    @
    onoff_attribute("Use HTML as wiki markup language", "wiki-use-html", "wiki-use-html", 0); @

    Use HTML as the wiki markup language. Wiki links will still be parsed @ but all other wiki formatting will be ignored. This option is helpful @ if you have chosen to use a rich HTML editor for wiki markup such as @ TinyMCE.

    @

    CAUTION: when @ enabling, all HTML tags and attributes are accepted in the wiki. @ No sanitization is done. This means that it is very possible for malicious @ users to inject dangerous HTML, CSS and JavaScript code into your wiki.

    @

    This should only be enabled when wiki editing is limited @ to trusted users. It should not be used on a publically @ editable wiki.

    @
    @

    @
    db_end_transaction(0); style_footer(); } /* ** WEBPAGE: setup_editcss */ void setup_editcss(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } db_begin_transaction(); if( P("clear")!=0 ){ db_multi_exec("DELETE FROM config WHERE name='css'"); cgi_replace_parameter("css", zDefaultCSS); db_end_transaction(0); cgi_redirect("setup_editcss"); } if( P("submit")!=0 ){ textarea_attribute(0, 0, 0, "css", "css", zDefaultCSS); db_end_transaction(0); cgi_redirect("setup_editcss"); } style_header("Edit CSS"); @
    login_insert_csrf_secret(); @ Edit the CSS below:
    textarea_attribute("", 35, 80, "css", "css", zDefaultCSS); @
    @ @ @
    @

    Note: Press your browser Reload button after @ modifying the CSS in order to pull in the modified CSS file.

    @
    @ The default CSS is shown below for reference. Other examples @ of CSS files can be seen on the skins page. @ See also the header and @ footer editing screens. @
      cgi_append_default_css();
      @ 
    style_footer(); db_end_transaction(0); } /* ** WEBPAGE: setup_header */ void setup_header(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } db_begin_transaction(); if( P("clear")!=0 ){ db_multi_exec("DELETE FROM config WHERE name='header'"); cgi_replace_parameter("header", zDefaultHeader); }else if( P("submit")!=0 ){ textarea_attribute(0, 0, 0, "header", "header", zDefaultHeader); }else if( P("fixbase")!=0 ){ const char *z = db_get("header", (char*)zDefaultHeader); char *zHead = strstr(z, ""); if( strstr(z, "\n%s", zHead+6-z, z, zTail); cgi_replace_parameter("header", zNew); db_set("header", zNew, 0); } } style_header("Edit Page Header"); @
    /* Make sure the header contains . Issue a warning ** if it does not. */ if( !cgi_header_contains("Please add @ <base href="$baseurl/$current_page"> after @ <head> in the header! @

    } login_insert_csrf_secret(); @

    Edit HTML text with embedded TH1 (a TCL dialect) that will be used to @ generate the beginning of every page through start of the main @ menu.

    textarea_attribute("", 35, 80, "header", "header", zDefaultHeader); @
    @ @ @
    @
    @ The default header is shown below for reference. Other examples @ of headers can be seen on the skins page. @ See also the CSS and @ footer editing screeens. @
      @ %h(zDefaultHeader)
      @ 
    style_footer(); db_end_transaction(0); } /* ** WEBPAGE: setup_footer */ void setup_footer(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } db_begin_transaction(); if( P("clear")!=0 ){ db_multi_exec("DELETE FROM config WHERE name='footer'"); cgi_replace_parameter("footer", zDefaultFooter); } style_header("Edit Page Footer"); @
    login_insert_csrf_secret(); @

    Edit HTML text with embedded TH1 (a TCL dialect) that will be used to @ generate the end of every page.

    textarea_attribute("", 20, 80, "footer", "footer", zDefaultFooter); @
    @ @ @
    @
    @ The default footer is shown below for reference. Other examples @ of footers can be seen on the skins page. @ See also the CSS and @ header editing screens. @
      @ %h(zDefaultFooter)
      @ 
    style_footer(); db_end_transaction(0); } /* ** WEBPAGE: setup_modreq */ void setup_modreq(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } style_header("Moderator For Wiki And Tickets"); db_begin_transaction(); @
    login_insert_csrf_secret(); @
    onoff_attribute("Moderate ticket changes", "modreq-tkt", "modreq-tkt", 0); @

    When enabled, any change to tickets is subject to the approval @ a ticket moderator - a user with the "q" or Mod-Tkt privilege. @ Ticket changes enter the system and are shown locally, but are not @ synced until they are approved. The moderator has the option to @ delete the change rather than approve it. Ticket changes made by @ a user who hwas the Mod-Tkt privilege are never subject to @ moderation. @ @


    onoff_attribute("Moderate wiki changes", "modreq-wiki", "modreq-wiki", 0); @

    When enabled, any change to wiki is subject to the approval @ a ticket moderator - a user with the "l" or Mod-Wiki privilege. @ Wiki changes enter the system and are shown locally, but are not @ synced until they are approved. The moderator has the option to @ delete the change rather than approve it. Wiki changes made by @ a user who has the Mod-Wiki privilege are never subject to @ moderation. @

    @
    @

    @
    db_end_transaction(0); style_footer(); } /* ** WEBPAGE: setup_adunit */ void setup_adunit(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } db_begin_transaction(); if( P("clear")!=0 ){ db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'"); cgi_replace_parameter("adunit",""); } style_header("Edit Ad Unit"); @
    login_insert_csrf_secret(); @

    Edit HTML text for an ad unit that will be inserted after the @ menu bar and above the content of every page.

    textarea_attribute("", 20, 80, "adunit", "adunit", ""); @
    onoff_attribute("Omit ads to administrator", "adunit-omit-if-admin", "oia", 0); @
    onoff_attribute("Omit ads to logged-in users", "adunit-omit-if-user", "oiu", 0); @
    @ @ @
    style_footer(); db_end_transaction(0); } /* ** WEBPAGE: setup_logo */ void setup_logo(void){ const char *zLogoMime = db_get("logo-mimetype","image/gif"); const char *aLogoImg = P("logoim"); int szLogoImg = atoi(PD("logoim:bytes","0")); const char *zBgMime = db_get("background-mimetype","image/gif"); const char *aBgImg = P("bgim"); int szBgImg = atoi(PD("bgim:bytes","0")); if( szLogoImg>0 ){ zLogoMime = PD("logoim:mimetype","image/gif"); } if( szBgImg>0 ){ zBgMime = PD("bgim:mimetype","image/gif"); } login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } db_begin_transaction(); if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){ Blob img; Stmt ins; blob_init(&img, aLogoImg, szLogoImg); db_prepare(&ins, "REPLACE INTO config(name,value,mtime)" " VALUES('logo-image',:bytes,now())" ); db_bind_blob(&ins, ":bytes", &img); db_step(&ins); db_finalize(&ins); db_multi_exec( "REPLACE INTO config(name,value,mtime) VALUES('logo-mimetype',%Q,now())", zLogoMime ); db_end_transaction(0); cgi_redirect("setup_logo"); }else if( P("clrlogo")!=0 ){ db_multi_exec( "DELETE FROM config WHERE name IN " "('logo-image','logo-mimetype')" ); db_end_transaction(0); cgi_redirect("setup_logo"); }else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){ Blob img; Stmt ins; blob_init(&img, aBgImg, szBgImg); db_prepare(&ins, "REPLACE INTO config(name,value,mtime)" " VALUES('background-image',:bytes,now())" ); db_bind_blob(&ins, ":bytes", &img); db_step(&ins); db_finalize(&ins); db_multi_exec( "REPLACE INTO config(name,value,mtime)" " VALUES('background-mimetype',%Q,now())", zBgMime ); db_end_transaction(0); cgi_redirect("setup_logo"); }else if( P("clrbg")!=0 ){ db_multi_exec( "DELETE FROM config WHERE name IN " "('background-image','background-mimetype')" ); db_end_transaction(0); cgi_redirect("setup_logo"); } style_header("Edit Project Logo And Background"); @

    The current project logo has a MIME-Type of %h(zLogoMime) @ and looks like this:

    @

    logo @

    @ @
    @

    The logo is accessible to all users at this URL: @ %s(g.zBaseURL)/logo. @ The logo may or may not appear on each @ page depending on the CSS and @ header setup. @ To change the logo image, use the following form:

    login_insert_csrf_secret(); @ Logo Image file: @ @

    @ @

    @
    @
    @ @

    The current background image has a MIME-Type of %h(zBgMime) @ and looks like this:

    @

    background @

    @ @
    @

    The background image is accessible to all users at this URL: @ %s(g.zBaseURL)/background. @ The background image may or may not appear on each @ page depending on the CSS and @ header setup. @ To change the background image, use the following form:

    login_insert_csrf_secret(); @ Background image file: @ @

    @ @

    @
    @
    @ @

    Note: Your browser has probably cached these @ images, so you may need to press the Reload button before changes will @ take effect.

    style_footer(); db_end_transaction(0); } /* ** Prevent the RAW SQL feature from being used to ATTACH a different ** database and query it. ** ** Actually, the RAW SQL feature only does a single statement per request. ** So it is not possible to ATTACH and then do a separate query. This ** routine is not strictly necessary, therefore. But it does not hurt ** to be paranoid. */ int raw_sql_query_authorizer( void *pError, int code, const char *zArg1, const char *zArg2, const char *zArg3, const char *zArg4 ){ if( code==SQLITE_ATTACH ){ return SQLITE_DENY; } return SQLITE_OK; } /* ** WEBPAGE: admin_sql ** ** Run raw SQL commands against the database file using the web interface. */ void sql_page(void){ const char *zQ = P("q"); int go = P("go")!=0; login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } db_begin_transaction(); style_header("Raw SQL Commands"); @

    Caution: There are no restrictions on the SQL that can be @ run by this page. You can do serious and irrepairable damage to the @ repository. Proceed with extreme caution.

    @ @

    Only a the first statement in the entry box will be run. @ Any subsequent statements will be silently ignored.

    @ @

    Database names:

    • repository → %s(db_name("repository")) if( g.zConfigDbName ){ @
    • config → %s(db_name("configdb")) } if( g.localOpen ){ @
    • local-checkout → %s(db_name("localdb")) } @

    @ @
    login_insert_csrf_secret(); @ SQL:
    @
    @ @ @ @
    if( P("schema") ){ zQ = sqlite3_mprintf( "SELECT sql FROM %s.sqlite_master WHERE sql IS NOT NULL", db_name("repository")); go = 1; }else if( P("tablelist") ){ zQ = sqlite3_mprintf( "SELECT name FROM %s.sqlite_master WHERE type='table'" " ORDER BY name", db_name("repository")); go = 1; } if( go ){ sqlite3_stmt *pStmt; int rc; const char *zTail; int nCol; int nRow = 0; int i; @
    login_verify_csrf_secret(); sqlite3_set_authorizer(g.db, raw_sql_query_authorizer, 0); rc = sqlite3_prepare_v2(g.db, zQ, -1, &pStmt, &zTail); if( rc!=SQLITE_OK ){ @
    %h(sqlite3_errmsg(g.db))
    sqlite3_finalize(pStmt); }else if( pStmt==0 ){ /* No-op */ }else if( (nCol = sqlite3_column_count(pStmt))==0 ){ sqlite3_step(pStmt); rc = sqlite3_finalize(pStmt); if( rc ){ @
    %h(sqlite3_errmsg(g.db))
    } }else{ @ while( sqlite3_step(pStmt)==SQLITE_ROW ){ if( nRow==0 ){ @ for(i=0; i%h(sqlite3_column_name(pStmt, i)) } @ } nRow++; @ for(i=0; i @ %s(sqlite3_column_text(pStmt, i)) break; } case SQLITE_NULL: { @ break; } case SQLITE_TEXT: { const char *zText = (const char*)sqlite3_column_text(pStmt, i); @ break; } case SQLITE_BLOB: { @ break; } } } @ } sqlite3_finalize(pStmt); @
    NULL%h(zText) @ %d(sqlite3_column_bytes(pStmt, i))-byte BLOB
    } } style_footer(); } /* ** WEBPAGE: admin_th1 ** ** Run raw TH1 commands using the web interface. If Tcl integration was ** enabled at compile-time and the "tcl" setting is enabled, Tcl commands ** may be run as well. */ void th1_page(void){ const char *zQ = P("q"); int go = P("go")!=0; login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } db_begin_transaction(); style_header("Raw TH1 Commands"); @

    Caution: There are no restrictions on the TH1 that can be @ run by this page. If Tcl integration was enabled at compile-time and @ the "tcl" setting is enabled, Tcl commands may be run as well.

    @ @
    login_insert_csrf_secret(); @ TH1:
    @
    @ @
    if( go ){ const char *zR; int rc; int n; @
    login_verify_csrf_secret(); rc = Th_Eval(g.interp, 0, zQ, -1); zR = Th_GetResult(g.interp, &n); if( rc==TH_OK ){ @
    %h(zR)
    }else{ @
    %h(zR)
    } } style_footer(); }