The following example shows a username/password database for Tclhttpd. It uses the following * Metakit for the database * logic and code from the [formkit] package * [crypt in pure tcl] from the wiki for the passwords * The Tclhttpd session module for row(record) locking and session control. Create a "dbdata" directory under the Doc_Root directory (usually htdocs). '''The following files are all saved to the custom directory in Tclhttpd.''' Get the [crypt in pure tcl] package and save it to tclcrypt.tcl Change the proc name from crypt to tclcrypt. This is to avoid name conflicts as Tclhttpd looks for the unix crypt. Edit the first line and change it from proc crypt {password salt} { to proc tclcrypt {password salt} { Save the following ************ Begin fmkt.tcl ************************************************ # This an example of a username/password database for the Tclhttpd webserver. # It uses Metakit for the database and uses ideas from formkit for # row or record locking. It uses the session module from Tclhttpd for session # control and crypt in pure tclfrom the wiki. # Set up the Database mk::file open users [Doc_Root]/dbdata/users.dat mk::view layout users.details {user longname location phone email pass } # Register Document type handler. Mtype_Add .fmkt application/x-tcl-fmkt # Set the start page that is used to launch a new session. set Fmkt(startpage) /start.tml # Set the time in seconds that a session will last for. set Fmkt(age) 300 proc Doc_application/x-tcl-fmkt {path suffix sock} { upvar #0 Httpd$sock data global Fmkt append data(query) "" set queryList [Url_DecodeQuery $data(query)] # Destroy any old session that are laying around. In this instance # 5 minutes is the setting. Session_Reap $Fmkt(age) Fmkt # Find the current session (or start a new one if session=new). set session [Session_Match $queryList Fmkt error] if {$session == {}} { Fmkt_ErrorPage $sock "The session no longer exists! $error" return } # Process the query data from the previous page. if [catch {FmktProcess $session $queryList} result] { Httpd_ReturnData $sock text/html $result return } # Expand the page in the correct session interpreter, or treat # the page as ordinary html if the session has ended. switch -exact -- $result { 0 { Httpd_ReturnFile $sock text/html $path } 1 { Doc_Subst $sock $path interp$session } 2 { Fmkt_ErrorPage $sock "This record locked by another user!" } 3 { Fmkt_ErrorPage $sock "Must enter a character!" } 4 { Fmkt_ErrorPage $sock "Record has been saved!" } } } # The purpose of this procedure is to process the form query data. # Based on the query data certain procedures are triggered. # Parameters # session: the session id # query: a list of names and values produced by Url_DecodeQuery proc FmktProcess {session query} { global Fmkt upvar #0 Session:$session state set interp $state(interp) # Process each query item. # Some items, such as "session" and "user" are treated # specially. # Upon completion, zero or more of the following may occur: # Variables and values are set in the appropriate slave # interpreter. # The user is defined in the state array. # The session is destroyed. foreach {name value} $query { if {[string match "user" $name]} { set user [string trim $value] if {[string match $user ""]} { Session_Destroy $session return 3 } elseif {![info exist state(user)]} { if {[Fmkt_UserLock $user]} { Session_Destroy $session return 2 } else { set state(user) $user interp eval $interp [list set user $user] } } } elseif {[string match "cancel" $name] && $value} { Session_Destroy $session return 0 } elseif {[string match "save" $name] && $value} { if {[Fmkt_DbSave $session $query]} { Session_Destroy $session return 4 } else { return 1 } } else { # Define variables in the slave interpreter so they are there before # we do a Doc_Subst on the page! interp eval $interp [list set $name $value] } } return 1 } proc Fmkt_ErrorPage {sock error} { global Fmkt upvar #0 Httpd$sock data append result "" append result $error

append result " This page will redirect to the start page" Httpd_ReturnData $sock text/html $result } # Set the Formkit tag and view for the open datafile. proc Fmkt_DbView {session db view} { upvar #0 Session:$session state set state(db) $db set state(view) $view return "" } # Check that the row or record is not being edited # by another session. proc Fmkt_UserLock {user} { foreach id [info globals Session:*] { upvar #0 $id session if {[info exist session(user)]} { if {[string match $session(user) $user]} { return 1 } } } return 0 } # Retrieve row based on unique "user" and drop values # in the session's slave interpreter proc Fmkt_DbLookup {session} { upvar #0 Session:$session state set interp $state(interp) if {[info exists state(errorpass)]} { return } else { set position [mk::select $state(db).$state(view) -exact user $state(user)] if {[string match "" $position]} { foreach name [mk::view info $state(db).$state(view)] { if {[string match "user" $name]} { continue } else { interp eval $interp [list set $name ""] } } } else { set state(pass) [mk::get $state(db).$state(view)!$position pass] foreach {name value} [mk::get $state(db).$state(view)!$position] { interp eval $interp [list set $name $value] } } unset position interp eval $interp [list set newpass ""] interp eval $interp [list set vfypass ""] } } # Collect all the return values and check if password and verify password # match. Crypt the password then write the values back to the database. proc Fmkt_DbSave {session query} { upvar #0 Session:$session state set interp $state(interp) lappend field_values user $state(user) foreach {name value} $query { if {[string match "session" $name]} { continue } elseif {[string match "save" $name]} { continue } elseif {[string match "newpass" $name]} { set $name $value interp eval $interp [list set $name $value] } elseif {[string match "vfypass" $name]} { set $name $value interp eval $interp [list set $name $value] } else { set $name $value lappend field_values $name $value interp eval $interp [list set $name $value] } } set newpass [string trim $newpass] set vfypass [string trim $vfypass] if {![info exists state(pass)]} { if {[string match $newpass ""]} { set state(errorpass) "Must enter a password!" return 0 } } if {[string compare $newpass $vfypass] != 0} { set state(errorpass) "New and Verify Passwords do not match!" return 0 } set position [mk::select $state(db).$state(view) -exact user $state(user)] lappend field_values pass [Fmkt_passCrypt $newpass] if {[string match "" $position]} { eval mk::row append $state(db).$state(view) $field_values } else { eval mk::set $state(db).$state(view)!$position $field_values } mk::file commit $state(db) unset field_values return 1 } proc Fmkt_passCrypt {newpass} { set passcrypt [tclcrypt $newpass 91] return $passcrypt } proc Fmkt_formSession {session args} { upvar #0 Session:$session state append result "" return $result } proc Fmkt_errorPass {session} { upvar #0 Session:$session state if {[info exists state(errorpass)]} { return $state(errorpass) } else { return } } # Use this procedure for authentication. It is to be called from # a .tclaccess file in the Directory you want authenticated access too. # In the .tclaccess file put the following # set realm "TclHttpd" # set callback Fmkt_AuthChecker proc Fmkt_AuthChecker {sock realm user pass} { set row [mk::select users.details -exact user $user] array set userdb [mk::get users.details!$row] set salt [string range $userdb(pass) 0 1] set passcrypt [tclcrypt $pass $salt] if {[string compare $user $userdb(user)] == 0 && [string compare $passcrypt $userdb(pass)] == 0} { return 1 } else { return 0 } } ******************************** End fmkt.tcl ****************************** '''The following are placed in the /hdocs directory''' ************ Begin start.tml **************** [ Doc_Dynamic ] Start.tml

This an example of a username/password database..

Start Session ****************** End start.tml ****************** ******************Begin page1.fmkt **************** [ DbView users details ] Page1.fmkt

Add or Edit a User

[formSession]

**************** End page1.fmkt ****************** **************** Begin page2.fmkt **************** Page2.fmkt
[formSession] [DbLookup]
Username:$user
Name:
Location:
Phone:
Email:
Enter Password:
Verify Password:

[errorPass]

************************ End page2.tcl **********************************