/* ** 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"); @ 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("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("Logo", "setup_logo", "Change the logo image 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"); @
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; login_check_credentials(); if( !g.perm.Admin ){ login_needed(); return; } style_submenu_element("Add", "Add User", "setup_uedit"); style_header("User List"); @ @
@ Users: @ @ @ @ @ @ db_prepare(&s, "SELECT uid, login, cap, info FROM user ORDER BY login"); while( db_step(&s)==SQLITE_ROW ){ const char *zCap = db_column_text(&s, 2); @ @ @ @ @ } @
User IDCapabilitiesContact Info
if( g.perm.Admin && (zCap[0]!='s' || g.perm.Setup) ){ @ } @ %h(db_column_text(&s,1)) if( g.perm.Admin ){ @ } @ %s(zCap)%s(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
    mAppend-Wiki: Append to wiki pages
    nNew-Tkt: Create new tickets
    oCheck-Out: Check out versions
    pPassword: Change your own password
    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; char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap; char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae; char *oat, *oau, *oav, *oab, *oax, *oaz; const char *zGroup; const char *zOldLogin; char *inherit[128]; int doWrite; int uid; int higherUser = 0; /* True if user being edited is SETUP and the */ /* user doing the editing is ADMIN. Disallow editing */ /* Must have ADMIN privleges 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 zCap[50]; int i = 0; int aa = P("aa")!=0; int ab = P("ab")!=0; int ad = P("ad")!=0; int ae = P("ae")!=0; int ai = P("ai")!=0; int aj = P("aj")!=0; int ak = P("ak")!=0; int an = P("an")!=0; int ao = P("ao")!=0; int ap = P("ap")!=0; int ar = P("ar")!=0; int as = g.perm.Setup && P("as")!=0; int aw = P("aw")!=0; int ac = P("ac")!=0; int af = P("af")!=0; int am = P("am")!=0; int ah = P("ah")!=0; int ag = P("ag")!=0; int at = P("at")!=0; int au = P("au")!=0; int av = P("av")!=0; int ax = P("ax")!=0; int az = P("az")!=0; if( aa ){ zCap[i++] = 'a'; } if( ab ){ zCap[i++] = 'b'; } if( ac ){ zCap[i++] = 'c'; } if( ad ){ zCap[i++] = 'd'; } if( ae ){ zCap[i++] = 'e'; } if( af ){ zCap[i++] = 'f'; } if( ah ){ zCap[i++] = 'h'; } if( ag ){ zCap[i++] = 'g'; } if( ai ){ zCap[i++] = 'i'; } if( aj ){ zCap[i++] = 'j'; } if( ak ){ zCap[i++] = 'k'; } if( am ){ zCap[i++] = 'm'; } if( an ){ zCap[i++] = 'n'; } if( ao ){ zCap[i++] = 'o'; } if( ap ){ zCap[i++] = 'p'; } if( ar ){ zCap[i++] = 'r'; } if( as ){ zCap[i++] = 's'; } if( at ){ zCap[i++] = 't'; } if( au ){ zCap[i++] = 'u'; } if( av ){ zCap[i++] = 'v'; } if( aw ){ zCap[i++] = 'w'; } if( ax ){ zCap[i++] = 'x'; } if( az ){ zCap[i++] = 'z'; } 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 = ""; oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam = oan = oao = oap = oar = oas = oat = oau = oav = oaw = oax = oaz = ""; 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); if( strchr(zCap, 'a') ) oaa = " checked=\"checked\""; if( strchr(zCap, 'b') ) oab = " checked=\"checked\""; if( strchr(zCap, 'c') ) oac = " checked=\"checked\""; if( strchr(zCap, 'd') ) oad = " checked=\"checked\""; if( strchr(zCap, 'e') ) oae = " checked=\"checked\""; if( strchr(zCap, 'f') ) oaf = " checked=\"checked\""; if( strchr(zCap, 'g') ) oag = " checked=\"checked\""; if( strchr(zCap, 'h') ) oah = " checked=\"checked\""; if( strchr(zCap, 'i') ) oai = " checked=\"checked\""; if( strchr(zCap, 'j') ) oaj = " checked=\"checked\""; if( strchr(zCap, 'k') ) oak = " checked=\"checked\""; if( strchr(zCap, 'm') ) oam = " checked=\"checked\""; if( strchr(zCap, 'n') ) oan = " checked=\"checked\""; if( strchr(zCap, 'o') ) oao = " checked=\"checked\""; if( strchr(zCap, 'p') ) oap = " checked=\"checked\""; if( strchr(zCap, 'r') ) oar = " checked=\"checked\""; if( strchr(zCap, 's') ) oas = " checked=\"checked\""; if( strchr(zCap, 't') ) oat = " checked=\"checked\""; if( strchr(zCap, 'u') ) oau = " checked=\"checked\""; if( strchr(zCap, 'v') ) oav = " checked=\"checked\""; if( strchr(zCap, 'w') ) oaw = " checked=\"checked\""; if( strchr(zCap, 'x') ) oax = " checked=\"checked\""; if( strchr(zCap, 'z') ) oaz = " 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 ){ @ %s(B('s'))Setup
    } @ %s(B('a'))Admin
    @ %s(B('d'))Delete
    @ %s(B('e'))Email
    @ %s(B('p'))Password
    @ %s(B('i'))Check-In
    @ %s(B('o'))Check-Out
    @ %s(B('h'))History
    @ %s(B('u'))Reader
    @ %s(B('v'))Developer
    @ %s(B('g'))Clone
    @ %s(B('j'))Read Wiki
    @ %s(B('f'))New Wiki
    @ %s(B('m'))Append Wiki
    @ %s(B('k'))Write Wiki
    @ %s(B('b'))Attachments
    @ %s(B('r'))Read Ticket
    @ %s(B('n'))New Ticket
    @ %s(B('c'))Append Ticket
    @ %s(B('w'))Write Ticket
    @ %s(B('t'))Ticket Report
    @ %s(B('x'))Private
    @ %s(B('z'))Download Zip @
    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 turns 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 8760 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.

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

    Enable hyperlinks (the equivalent of the "h" permission) for all users @ including user "nobody", as long as 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 script. Note: Bots can specify whatever User-Agent string they @ that want. So a bot that wants to impersonate a human can easily do so. @ Hence, this technique does not necessarily exclude malicious bots. @

    @
    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 HTTP UI @ or are registered by the administrator using the command line interface. @

    @
    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); 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){ 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("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.

    @
    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 ){ entry_attribute(pSet->name, /*pSet->width*/ 40, pSet->name, pSet->var!=0 ? pSet->var : pSet->name, (char*)pSet->def); if( pSet->versionable ){ @ (v)
    } else { @
    } } } @
    @

    @
    @

    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", 5, 60, "project-description", "pd", ""); @

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

    @
    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"); }else{ textarea_attribute(0, 0, 0, "css", "css", zDefaultCSS); } if( P("submit")!=0 ){ db_end_transaction(0); cgi_redirect("setup_editcss"); } style_header("Edit CSS"); @
    login_insert_csrf_secret(); @ Edit the CSS below:
    textarea_attribute("", 40, 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{ textarea_attribute(0, 0, 0, "header", "header", zDefaultHeader); } style_header("Edit Page 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("", 40, 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); }else{ textarea_attribute(0, 0, 0, "footer", "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_logo */ void setup_logo(void){ const char *zMime = db_get("logo-mimetype","image/gif"); const char *aImg = P("im"); int szImg = atoi(PD("im:bytes","0")); if( szImg>0 ){ zMime = PD("im:mimetype","image/gif"); } login_check_credentials(); if( !g.perm.Setup ){ login_needed(); } db_begin_transaction(); if( P("set")!=0 && zMime && zMime[0] && szImg>0 ){ Blob img; Stmt ins; blob_init(&img, aImg, szImg); 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())", zMime ); db_end_transaction(0); cgi_redirect("setup_logo"); }else if( P("clr")!=0 ){ db_multi_exec( "DELETE FROM config WHERE name GLOB 'logo-*'" ); db_end_transaction(0); cgi_redirect("setup_logo"); } style_header("Edit Project Logo"); @

    The current project logo has a MIME-Type of %h(zMime) 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 set a new logo image, select a file to use as the logo using @ the entry box below and then press the "Change Logo" button.

    login_insert_csrf_secret(); @ Logo Image file: @
    @ @ @
    @ @

    Note: Your browser has probably cached the @ logo image, so you will probably need to press the Reload button on your @ browser after changing the logo to provoke your browser to reload the new @ logo image.

    style_footer(); db_end_transaction(0); }