Using TCL ldap package to change users AD password - tcl

The challange is to change user's AD passwort.
I have a TCL Script wrapping ldapmodify to set the passcode, which works:
set unicodePwd [encodePw4ad $pw]
lappend text {dn: $dn}
lappend text {changetype: modify}
lappend text {replace: unicodePwd}
lappend text {unicodePwd:: $unicodePwd}
lappend text {-}
set fn /tmp/ldiff.[clock microseconds].ldif
write_file $fn [subst [join $text \n]]
.....
exec ldapmodify -H $host -D $binddn -x -w $bindpw -f $fn
Using TCL 8.6 with LDAP 1.9.2 Package the code looks:
set unicodePwd [encodePw4ad $pw]
set handle [::ldap::secure_connect $host 636 0]
ldap::bind $handle $binddn $bindpw
#ldap::modify $handle $dn [list postalCode 123456]
ldap::modify $handle $dn [list unicodePwd $unicodePwd]
ldap::unbind $handle
ldap::disconnect $handle
This works for the "postalCode" but not for the "unicodePwd".
LDAP error unwillingToPerform '': 0000001F: SvcErr: DSID-031A12D2, problem 5003 (WILL_NOT_PERFORM), data 0
Any hint to investigate?

I cannot test this (as I don't have a the environment available), but following this quote ...
The syntax of the unicodePwd attribute is octet-string; however, the
directory service expects that the octet-string will contain a UNICODE
string (as the name of the attribute indicates). This means that any
values for this attribute passed in LDAP must be UNICODE strings that
are BER-encoded (Basic Encoding Rules) as an octet-string. In
addition, the UNICODE string must begin and end in quotes that are not
part of the desired password.
from https://support.microsoft.com/en-gb/help/269190/how-to-change-a-windows-active-directory-and-lds-user-password-through
... the value for the unicodePwd record entry must be formatted as follows in Tcl (>= 8.6):
set pwd "abc123"
set pwd [string cat \" $pwd \"]; # must begin/ end in quotes
set pwd [encoding convertto unicode $pwd]; # UNICODE (UTF-16LE) string
set unicodePwd [binary encode base64 $pwd]; # base64 encoded variant
NB: You can watch that the resulting string from [encoding convertto unicode $pwd] using 2 bytes per character (16 bytes for "abc123" incl. quotes), or just eight if you were using the utf-8 or whatever, when running [string length] at the different steps.

Problem found:
ldapmodify uses 'unicodePwd::'. The '::' tells the AD, that the value is base64 encoded.
in TCL ldap::modify 'unicodePwd' has to be send in unicode only (no base64).
We did find a way to send base64 via the TCL ldap::modify

Related

TCL regsub uses RegEx match as index in associate array

I'd like to automatically convert URLs, i.e
"https://sc-uat.ct.example.com/sc/" into "https://invbeta.example.com/sc/"
"https://sc-dev.ct.example.com/sc/" into "https://invtest.example.com/sc/"
"https://sc-qa.ct.example.com/sc/" into "https://invdemo.example.com/sc/"
I've tried following code snippet in TCL
set loc "https://sc-uat.ct.example.com/sc/"
set envs(dev) "test"
set envs(uat) "beta"
set envs(qa) "demo"
puts $envs(uat)
regsub -nocase {://.+-(.+).ct.example.com} $loc {://inv[$envs(\1)].example.com} hostname
puts "new location = $hostname"
But the result is: new location = https://inv[$envs(uat)].example.com/sc/
It seems that [$envs(uat)] is NOT evaluated and substituted further with the real value. Any hints will be appreciated. Thanks in advance
But the result is: new location =
https://inv[$envs(uat)].example.com/sc/ It seems that [$envs(uat)] is eval-ed further.
You meant to say: [$envs(uat)] is not evaluated further?
This is because due to the curly braces in {://inv[$envs(\1)].example.com}, the drop-in string is taken literally, and not subjected to variable or command substitution. Besides, you don't want command and variable substitution ([$envs(\1)]), just one of them: $envs(\1) or [set envs(\1)].
To overcome this, you must treat the regsub-processed string further via subst:
set hostname [subst -nocommands -nobackslashes [regsub -nocase {://.+-(.+).ct.example.com} $loc {://inv$envs(\1).example.com}]]
Suggestions for improvement
I advise to avoid the use of subst in this context, because even when restricted, you might run into conflicts with characters special to Tcl in your hostnames (e.g., brackets in the IPv6 authority parts). Either you have to sanitize the loc string before, or, better work on string ranges like so:
if {[regexp -indices {://(.+-(.+)).ct.example.com} $loc _ replaceRange keyRange]} {
set key [string range $loc {*}$keyRange]
set sub [string cat "inv" $envs($key)]
set hostname [string replace $loc {*}$replaceRange $sub]
}

Extracting query string value

How to extract the username value from this query string (HTTP url-encoded): username=james&password=pwd in Tcl?
I can get it through Java's request.getParameter("username"); but how to get using Tcl?
The first stage is to split the query string up, and form a dictionary of it (which isn't strictly correct, but I'm guessing you don't care about the case where someone puts multiple username fields in the query string!). However, you also need to decode the encoding of the contents, and that's pretty awful:
proc QueryStringToDict {qs} {
set mapping {}
foreach item [split $qs "&"] {
if {[regexp {^([^=]+)=(.*)$} $item -> key value]} {
dict set mapping [DecodeURL $key] [DecodeURL $value]
}
}
return $mapping
}
proc DecodeURL {string} {
# This *is* tricky! The URL encoding of fields is way nastier than you thought!
set mapped [string map {+ { } \[ "\\\[" \] "\\\]" $ "\\$" \\ "\\\\"} $string]
encoding convertfrom utf-8 \
[subst [regsub -all {%([[:xdigit:]]{2})} $string {[format %c 0x\1]}]]
}
set qs "username=james&password=pwd"
set info [QueryStringToDict $qs]
puts "user name is [dict get $info username]"
In 8.7 (currently in alpha) it'll be much simpler to do that inner encoding; there won't need to be that subst call in there for example. But you haven't got that version of Tcl; nobody has (except for people who insist on being right on the bleeding edge and get themselves into trouble over it).
Assuming this is a CGI environment, where the environment will contain
REQUEST_METHOD=GET
QUERY_STRING='username=james&password=pwd'
or
REQUEST_METHOD=POST
CONTENT_LENGTH=27
# and stdin contains "username=james&password=pwd"
then use tcllib's ncgi module
$ cat > cgi.tcl
#!/usr/bin/env tclsh
package require ncgi
::ncgi::parse
array set params [::ncgi::nvlist]
parray params
$ printf "username=james&password=pwd" | env REQUEST_METHOD=POST CONTENT_LENGTH=27 ./cgi.tcl
params(password) = pwd
params(username) = james
$ env REQUEST_METHOD=GET QUERY_STRING='username=james&password=pwd' ./cgi.tcl
params(password) = pwd
params(username) = james
An alternative to Donal's suggestion, sharing the spirit, but building on battery pieces: tcllib rest package:
(1) To process the query (as part of a valid URL)
% package req rest
1.3.1
% set query [rest::parameters ?username=jo%3Dhn]; # http:// is default scheme, ? is minimum URL boilerplate
username jo%3Dhn
(2) Run a URL decoder (e.g., the one by Donal or the one from Rosetta code):
% proc urlDecode {str} {
set specialMap {"[" "%5B" "]" "%5D"}
set seqRE {%([0-9a-fA-F]{2})}
set replacement {[format "%c" [scan "\1" "%2x"]]}
set modStr [regsub -all $seqRE [string map $specialMap $str] $replacement]
return [encoding convertfrom utf-8 [subst -nobackslash -novariable $modStr]]
}
then:
% set info [lmap v $query {urlDecode $v}]
username jo=hn
% dict get $info username
jo=hn

how to replace \ by \\ in tcl script string variable?

I am trying to replace the \ with \\ in a string variable which contains a network folder path. script is
regsub -all {'\'} $folderpath {\\} $folderpath
if it is other character i am able to replace, since its \, I am getting problem.
set folderpath [string map {\\ \\\\} $s]
is about five times faster than using regsub.
Note that the file command has several subcommands that can manage a path string regardless of what the separators look like.
Documentation:
file,
string
% set path {C:\Dinesh\Downloads\Movies\Friends}
C:\Dinesh\Downloads\Movies\Friends
% regsub -all {\\} $path {\\\\}
C:\\Dinesh\\Downloads\\Movies\\Friends
%

Get line number using grep

I would like to get the line number using grep command, but I am getting the error message when search pattern is not a single word:
couldn't read file "Pattern": no such file or directory
How should be the proper usage of the grep? The code is here:
set status [catch {eval exec grep -n '$textToGrep' $fileName} lineNumber]
if { $status != 0 } {
#error
} else {
puts "lineNumber = $lineNumber"
}
Also if the search pattern is not matched at all, the returned value is : "child process exited abnormally"
Here is the simple test case:
set textToGrep "<BBB name=\"BBBRM\""
file contents:
<?xml version="1.0"?>
<!DOCTYPE AAA>
<AAA>
<BBB name="BBBRM" />
</AAA>
Well, I also get problems with your code and a single word pattern!
First of all, I don't think you need the eval command, because catch itself does an evaluation of its first argument.
Then, the problem is that you put the $textToGrep variable in exec inside single quotes ', which have no meaning to Tcl.
Therefore, if the content of textToGrep is foo, you are asking grep to search for the string 'foo'. If that string, including the single quotes, is not found in the file, you get the error.
Try to rewrite your first line with
set status [catch {exec grep -n $textToGrep $fileName} lineNumber]
and see if it works. Also, read the exec man page, which explains well these problems.
If your system has tcllib install, you can use the fileutil::grep command from the fileutil package:
package require fileutil
set fileName data.xml
set textToGrep {<BBB +name="BBBRM"}; # Update: Add + for multi-space match
set grepResult [::fileutil::grep $textToGrep $fileName]
foreach result $grepResult {
# Example result:
# data.xml:4: <BBB name="BBBRM" />
set lineNumber [lindex [split $result ":"] 1]
puts $lineNumber
# Update: Get the line, squeeze the spaces before name=
set line [lindex [split $result ":"] 2]
regsub { +name=} $line " name=" line
puts $line
}
Discussion
When assigning value to textToGrep, I used the curly braces, thus allowing double quote inside without having to escape them.
the result of the ::fileutil::grep command is a lits of strings. Each string contains the file name, line number, and the line itself; separated by colon.
One way to extract the line number is to first split the string (result) into pieces, using the colon as a separator. Next, I use lindex to grab the second item (index=1, since list is zero-base).
I have updated the code to account for case where there are multiple spaces before name=
There are two problems here:
Pattern matching does not work.
grep exits with error child process
exited abnormally when pattern is not found
The first problem is because you are not enclosing the textToGrep within double quotes(instead of single quotes). So your code should be:
[catch {exec grep -n "$textToGrep" $fileName} lineNumber]
Second problem is because of the exit status of grep command. grep exits with error when the pattern is not found. Here is the try on a shell:
# cat file
pattern
pattern with multiple spaces
# grep pattern file
pattern
pattern with multiple spaces
# echo $?
0
# grep nopattern file
# echo $?
1
EDIT:
In your case you have special characters such as < and > (which have special meaning on a shell).
set textToGrep "<BBB name=\"BBBRM\""
regsub -all -- {<} "$textToGrep" "\\\<" textToGrep
regsub -all -- {>} "$textToGrep" "\\\>" textToGrep
set textToGrep {\<BBB name="BBBRM"}
catch {exec grep -n $textToGrep $fileName} status
if {![regexp "child process" $status]} {
puts $status
} else {
puts "no word found"
}
I think you should do regular expression with child process. Just check above code if it works. In if statement you can process the status command as you like.
With the given example (in your post) the above code works only you need to use backslash for the "<" in the textToGrep variable

TCL command - string trim

I was using the command 'string trimright' to trim my string but I found that this command trims more than required.
My expression is "dssss.dcsss" If I use string trim command to trim the last few characters ".dcsss", it trims the entire string. How can I deal with this?
Command:
set a [string trimright "dcssss.dcsss" ".dcsss"]
puts $a
Intended output:
dcsss
Actual output
""
The string trimright command treats its (optional) last argument as a set of characters to remove (and so .dcsss is the same as sdc. to it), just like string trim and string trimleft do; indeed, string trim is just like using both string trimright and string trimleft in succession. This makes it unsuitable for what you are trying to do; to remove a suffix if it is present, you can use several techniques:
# It looks like we're stripping a filename extension...
puts [file rootname "dcssss.dcsss"]
# Can use a regular expression if we're careful...
puts [regsub {\.dcsss$} "dcssss.dcsss" {}]
# Do everything by hand...
set str "dcssss.dcsss"
if {[string match "*.dcsss" $str]} {
set str [string range $str 0 end-6]
}
puts $str
If what you're doing really is filename manipulation, like it looks like, do use the first of these options. The file command has some really useful commands for working with filenames in a cross-platform manner in it.