#! /bin/sh # the next line restarts using tclsh \ exec tclsh "$0" ${1+"$@"} # /bin/sh executes tclsh with arguments, if any. # If there are no arguments, first argument $1 is unset in sh. # If first argument $1 is set, 'sh' replaces it with all arguments $@. # tclsh interprets backslash as continuation of the comment string, sh does not. # man tclsh # Intellectual property information START # # Copyright (c) 2024 Ivan Bityutskiy # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # Intellectual property information END # Description START # # The script produces 27 unique passwords with user defined length (1..=62). # Modify 'passLines' variable to produce different amount of passwords. # Modify 'charList' variable to use different characters. # # Usage: # ./make_pass_alnum.tcl # OR # Start tclsh and source ./make_pass_alnum.tcl, then call printResult # tclsh # source ./make_pass_alnum.tcl # printResult # Description END # vim:ft=tcl # Amount of passwords to produce set passLines 27 # List with all symbols allowed in passwords set charList { 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z } # Maximum password's length set maxValue [llength $charList] # Get user input, trim leading zeroes proc getUserInput {} { puts -nonewline "\nEnter password's length (1..=$::maxValue): " flush stdout set ::userNum [string trimleft [gets stdin] 0] return 1 } # Check user input # 'digit' character class always works with Unicode characters: # string is digit \u096a # returns '1' because '\u096a' is a valid unicode digit, however: # string is integer \u096a # returns '0' proc checkUserInput {} { expr { [string is integer -strict $::userNum] && [string is digit -strict $::userNum] && $::userNum > 0 && $::userNum <= $::maxValue } } # Get 1st argument, or ask user for an input; trim leading zeroes expr { $argc > 0 && [set userNum [string trimleft [lindex $argv 0] 0]; string cat 1] && [checkUserInput] || [getUserInput] && [checkUserInput] || [set userNum $maxValue] } # Get true random numbers from /dev/urandom on *nix systems: # Maximum limit for the range set max [::tcl::mathop::- [llength $charList] 1] # Minimum limit for the range set min 0 # How many bytes to read at once (8 bytes = 64 bits = 64 bit integer) set readSingle 8 # How many integers are needed # Multiply by 10 to make sure there are enough integers for duplicate characters set readAll [::tcl::mathop::* $readSingle $userNum 10] # Procedure will read all the data from /dev/urandom in a single step and store # it in a list. This is a better solution than reading from /dev/urandom every # single time, opening and closing the file. For example instead of opening and # closing /dev/urandom 100 times, it will be done only once. proc genRandList {maxValue {minValue 0} {amtBytes 620}} { set devUrandom [open /dev/urandom rb] set randList [read $devUrandom $amtBytes] close $devUrandom # Convert binary data into unsigned integers for future use: # Order can be 'little endian', 'big endian', or 'native' for the CPU; # m = 64 bit integer in native order; n = 32 bit integer in native order; # u = unsigned flag; * = count, all bytes will be stored in one variable binary scan $randList mu* randList # Store random numbers in a list foreach {num} $randList { lappend randResult [expr {$num % ($maxValue - $minValue + 1) + $minValue}] } return $randResult } # Procedure will generate the password proc genPassword {} { set pwStr {} foreach {randNum} [::genRandList $::max $::min $::readAll] { set theChar [lindex $::charList $randNum] if {![string match *\\${theChar}* $pwStr]} { append pwStr $theChar } } set pwStr [string range $pwStr 0 [::tcl::mathop::- $::userNum 1]] # Return the password return $pwStr } # Procedure to print the password proc printResult {} { set decrNum $::passLines puts {} while {$decrNum ^ 0} { incr decrNum -1 puts [::genPassword] } puts {} } # Print the password printResult # END OF SCRIPT