#--------------------------------------------------------------------- # pwvalidator.tcl # # TCL script for IRC bot eggdrop # # A variety of new applications (CGI, PHP) require the verification # of a bots username/password combinations. # This script opens a password validator service on an eggdrop bot # on a listening port. # A client desiring the validation of a username/password combination, # must connect to the listening port set in this script. # # To get the validator service running on a bot, follow these steps: # 1. add a user to the bot allowed to use the service. # (5 characters minumum). # Example: .+user pwcheck # 2. set a password for this user. # Example: .chpass pwcheck hy81272a # 3. add (telnet) hosts for this user. # The password validator will match the host of the connecting # client against the hosts of the user. # The format must be: user@hostname where "hostname" # is the hostname of the connecting client. The "user" part # usually is the word "telnet". # Example: .+host pwcheck telnet@eggheads.org # 4. set the username, mentioned in step 1, also in this script. # 5. set the port where the service should listen on. # (portrange 1024 - 65000). # 6. set encryption keys (5 characters minimum). # # The validation process is a multi-tier process: # # 1. The client connects to the validator service listening port. # If the host of the connecting client resolves to the username # allowed to use the service, the connection is established. # Otherwise, the validator terminates the connection. # # 2. The clients sends a 3 item string containing the word "ENCRYPT", # a digit and a word. The digit indicates which key to use to # encrypt the word. # Example: ENCRYPT 1 helloworld # This would encrypt the word "helloworld" using encryption key 1. # If the string is correct the bot wil echo back the encrypted string # and leave the connection open. # The connection is closed in all other cases. # If the bot does not send back a correctly encrypted string, the # client should mark the validator service as UNTRUSTWORTHY and not # proceed! # The client can use pre-encrypted strings to compare with the # encrypted string returned by the bot i.e. it is not needed to # store encryption strings at the client. # # 3. The client sends a 3 item string containing the keyword # LOGIN and the username/password combination to access the service. # Example: LOGIN pwcheck hy81272a # If this string is correct the validator service will leave the # connection open. # If the string is incorrect, the bot will close the connection. # # 4. The client sends a 3 item string containing the keyword # VERIFY and the username/password combination to be verified. # Example: VERIFY bar g1t1t523623 # If this string contains more or less than 3 items or does not # contain the keyword VERIFY as first item the bot will close # the connection. # If the username/password combination is ok, the bot will return # "1" and "0" otherwise. The connection is kept open. # # A typical session: # client: ENCRYPT 1 helloworld # bot : QPYib0gV7r61cAIQr/wU9ii. # client: LOGIN pwcheck pwpass # bot : LOGIN OK. # client: VERIFY pwcheck pwpass # bot : 1 # client: VERIFY pwcheck badpass # bot : 0 # # v0: 24-Sep-2002 # v1: 26-Sep-2002 # - testing fase # todo: Currently the validator accepts plain username/password # combinations. Add possibility of encrypted username/password # combinations. #--------------------------------------------------------------------- #--------------------------------------------------------------------- # User specific settings: # - set the username of the user allowed to use the service. # - set the port the validator service will listen on. # - set encryption keys. #--------------------------------------------------------------------- set pwvalidatoruser pwcheck set pwvalidatorport 30000 set pwvalidatorkeys(1) hy4513dt set pwvalidatorkeys(2) 78D5agaA #--------------------------------------------------------------------- # package requirements #--------------------------------------------------------------------- package require Tcl 8.0 package require eggdrop 1.6.9 #--------------------------------------------------------------------- # Proc to check the username #--------------------------------------------------------------------- proc pwvalidator:userok { } { global pwvalidatoruser # check existence of username if {![info exists pwvalidatoruser]} { putlog "PWV ERR: no user defined." return 0 } # check length of the username if {[string length $pwvalidatoruser] < 5 } { putlog "PWV ERR: validator username too short." return 0 } # check if username is in the database if {![validuser $pwvalidatoruser]} { putlog "PWV ERR: user $pwvalidatoruser does not exist." return 0 } # check if a password is set. if {[passwdok $pwvalidatoruser ""] == 1 } { putlog "PWV ERR: No password set for $pwvalidatoruser!" return 0 } # user ok! return 1 } #--------------------------------------------------------------------- # Proc to check the listening port #--------------------------------------------------------------------- proc pwvalidator:portok { } { global pwvalidatorport # check existence of a listening port if {![info exists pwvalidatorport]} { putlog "PWV ERR: no listening port defined." return 0 } # check if it is a number between 1025 and 65000 if {[scan $pwvalidatorport %d number] != 1 } { putlog "PWV ERR: illformatted portnumber." return 0 } if {$pwvalidatorport != $number} { putlog "PWV ERR: illformatted portnumber." return 0 } if { $number < 1025 || $number > 65000 } { putlog "PWV ERR: the portnumber must be in the\ range of 1025-65000." return 0 } return 1 } #--------------------------------------------------------------------- # Proc to check the keys #--------------------------------------------------------------------- proc pwvalidator:keysok { } { global pwvalidatorkeys # does a keys array exist if {![array exists pwvalidatorkeys]} { putlog "PWV ERR: no keys defined." return 0 } # check each key. foreach keyidx [array names pwvalidatorkeys] { if {[string length $pwvalidatorkeys($keyidx)] < 5 } { putlog "PWV ERR: Erroneous key $keyidx." return 0 } } return 1 } #--------------------------------------------------------------------- # open listening port. #--------------------------------------------------------------------- set port [listen $pwvalidatorport script pwvalidator:step1] if { $port != $pwvalidatorport } { putlog "PWV PANIC!!! system changed listen port. Purged listen." listen $port off } #--------------------------------------------------------------------- # Step 1: match the hostname of the connection against the # database. #--------------------------------------------------------------------- proc pwvalidator:step1 { idx } { global pwvalidatoruser # lookup the uhost of the connection foreach connection [dcclist] { if {[lindex $connection 0] == $idx} { set uhost [lindex $connection 2] break } } # find the username in the database. if {[finduser $uhost] != $pwvalidatoruser} { putlog "PWV ERR: ($idx) $uhost is not in the known hosts of\ $pwvalidatoruser at step 1." killdcc $idx return 1 } # the host of the user is known, continue to step 2. putlog "PWV LOG: ($idx) connection from $uhost: OK!" control $idx pwvalidator:step2 } #--------------------------------------------------------------------- # Step 2: check the string "ENCRYPT number word" #--------------------------------------------------------------------- proc pwvalidator:step2 { idx text } { global pwvalidatorkeys # client terminated connection? if { $text == "" } { return 1 } # check that all keys are ok. if {![pwvalidator:keysok]} { putlog "PWV ERR: ($idx) keys not ok at step 2." killdcc $idx return 1 } # check string formatting. if {[scan $text "ENCRYPT %2d %s" keynum word] != 2} { putlog "PWV ERR: ($idx) ill formatted string at step 2." killdcc $idx return 1 } # word must contain 5 chars minimum and 50 chars maximum. set wordlength [string length $word] if { $wordlength < 5 || $wordlength > 50 } { putlog "PWV ERR: ($idx) length word ($word) not ok at step 2." killdcc $idx return 1 } # check existence of key . if {![info exists pwvalidatorkeys($keynum)]} { putlog "PWV ERR: ($idx) Key $keynum does not exist at step 2." killdcc $idx return 1 } putidx $idx [encrypt $pwvalidatorkeys($keynum) $word] putlog "PWV LOG: ($idx) encrypted $word with key $keynum." control $idx pwvalidator:step3 } #--------------------------------------------------------------------- # Step 3: check the string "LOGIN username password" #--------------------------------------------------------------------- proc pwvalidator:step3 { idx text } { global pwvalidatoruser # client terminated connection? if { $text == "" } { return 1 } # check the username allowed to use the validator service if {![pwvalidator:userok]} { putlog "PWV ERR: ($idx) user not ok at step 3." killdcc $idx return 1 } # check string formatting if {[scan $text "LOGIN %20s %20s" username password] != 2} { putlog "PWV ERR: ($idx) ill formatted string at step 3." killdcc $idx return 1 } # the username supplied must be the same as the username # allowed to use the validator. if { $username != $pwvalidatoruser } { putlog "PWV ERR: ($idx) incorrect username at step 3." killdcc $idx } # check username/password combination if {![passwdok $username $password]} { putlog "PWV ERR: ($idx) incorrect username/password at step 3." killdcc $idx return 1 } putlog "PWV LOG: ($idx) LOGIN OK!" putidx $idx "LOGIN OK." control $idx pwvalidator:step4 } #--------------------------------------------------------------------- # Step 4: validate the "VERIFY username password" #--------------------------------------------------------------------- proc pwvalidator:step4 { idx text } { # client terminated connection? if { $text == "" } { return 1 } # check string formatting if {[scan $text "VERIFY %20s %20s" username password] != 2} { putlog "PWV ERR: ($idx) ill formatted string at step 4." killdcc $idx return 1 } # check username/password combination if {[passwdok $username $password]} { putlog "PWV LOG: ($idx) correct username/password at step 4." putidx $idx "1" } else { putlog "PWV ERR: ($idx) incorrect username/password at step 4." putidx $idx "0" } # new strings should come to this proc: return 0 return 0 } #--------------------------------------------------------------------- # Check every minute for open connections. #--------------------------------------------------------------------- bind time - * pwvalidator:connections proc pwvalidator:connections { args } { # a bit awkward to string match set matchmask {scri pwvalidator:*} # string match the info of each connection against the # matchmask. If there is a match, compare the timestamp of that # connection with the currentime. foreach connection [dcclist] { set type [lindex $connection 4] if {[string match $matchmask $type]} { set timestamp [lindex $connection 5] if {[expr [unixtime] - $timestamp] > 30 } { set idx [lindex $connection 0] set uhost [lindex $connection 2] killdcc $idx putlog "PWV LOG: ($idx) purged connection of $uhost" } } } } #--------------------------------------------------------------------- # On startup check the listen port. #--------------------------------------------------------------------- if {![pwvalidator:portok]} { putlog "PWV WARNING: port setting is not OK! Check the setting!" } else { putlog "Password validator version 0 loaded:\ listening on port $pwvalidatorport." }