-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathclient.go
427 lines (392 loc) · 15.2 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
package adsi
import (
"strings"
"sync"
"unsafe"
"github.com/go-ole/go-ole"
"github.com/google/uuid"
"github.com/scjalliance/comshim"
"github.com/scjalliance/comutil"
"github.com/go-adsi/adsi/adspath"
"github.com/go-adsi/adsi/api"
"github.com/go-adsi/adsi/comclsid"
"github.com/go-adsi/adsi/comiid"
)
type namespace struct {
Name string
ClassID uuid.UUID
Iface *api.IADsOpenDSObject
Err error
}
// Client provides access to Active Directory Service Interfaces for
// any namespace supported by a local or remote COM server.
type Client struct {
m sync.RWMutex
n []namespace
flags uint32
}
// NewClient creates a new ADSI client. When done with a client it should be
// closed with a call to Close(). If NewClient is successful it will return a
// client and error will be nil, otherwise the returned client will be nil and
// error will be non-nil.
func NewClient() (*Client, error) {
return NewRemoteClient("")
}
// NewRemoteClient creates a new ADSI client on a remote server. When done with
// a client it should be closed with a call to Close(). If NewClient is
// successful it will return a client and error will be nil, otherwise the
// returned client will be nil and error will be non-nil.
//
// If no server is provided a local client is created instead and the
// resulting behavior is identical to NewClient.
func NewRemoteClient(server string) (*Client, error) {
comshim.Add(1)
c := &Client{flags: defaultFlags}
if err := c.init(server); err != nil {
comshim.Done()
return nil, err
}
// TODO: Add finalizer for ds?
return c, nil
}
func (c *Client) init(server string) (err error) {
// Acquiring a container for the CLSID_ADsNamespaces class gives us access to
// an enumeration of all of the available namespaces.
iface, err := api.NewIADsContainer(server, comclsid.ADsNamespaces)
if err != nil {
return err
}
root := NewContainer(iface)
defer root.Close()
iter, err := root.Children()
if err != nil {
return err
}
defer iter.Close()
c.n = make([]namespace, 0, 12)
for child, iterErr := iter.Next(); iterErr == nil; child, iterErr = iter.Next() {
defer child.Close()
// Add the entry and whip up a pointer to it
c.n = append(c.n, namespace{})
item := &c.n[len(c.n)-1]
// Name
item.Name, item.Err = child.Name()
if item.Err != nil {
continue
}
item.Name = strings.TrimRight(item.Name, ":")
// GUID
item.ClassID, item.Err = child.GUID()
if item.Err != nil {
continue
}
// Interface
var idisp *ole.IDispatch
idisp, item.Err = child.iface.QueryInterface(comutil.GUID(comiid.IADsOpenDSObject))
if item.Err != nil {
continue
}
item.Iface = (*api.IADsOpenDSObject)(unsafe.Pointer(idisp))
}
// TODO: Check the value of iterErr to see if it returned something other than
// io.EOF.
return
}
func (c *Client) closed() bool {
return (c.n == nil)
}
// Close will release resources consumed by the client. It should be called
// when the client is no longer needed.
func (c *Client) Close() {
c.m.Lock()
defer c.m.Unlock()
if c.closed() {
return
}
defer comshim.Done()
for i := 0; i < len(c.n); i++ {
if c.n[i].Iface != nil {
c.n[i].Iface.Release()
}
}
c.n = nil
}
// Flags returns the default flags that are used when opening a connection.
func (c *Client) Flags() (flags uint32) {
c.m.RLock()
defer c.m.RUnlock()
return c.flags
}
// SetFlags sets the default flags that are used when opening a connection.
func (c *Client) SetFlags(flags uint32) {
c.m.Lock()
defer c.m.Unlock()
c.flags = flags
}
// Open opens an ADSI object with the given path. The existing security
// context of the application and any flags specified via SetFlags will be
// used when making the connection. The default flags specify an encrypted
// read-only connection.
//
// Open returns the ADSI object as an Object type, which provides
// an idiomatic go wrapper around the underlying component object model
// IADs interface.
//
// Open calls QueryInterface internally to acquire an implementation of
// the IADs interface that is needed by the Object type. If the returned
// directory object does not implement the IADs interface an error is
// returned.
//
// The returned object consumes resources until it is closed. It is the
// caller's responsibilty to call Close on the returned object when it is no
// longer needed.
func (c *Client) Open(path string) (obj *Object, err error) {
return c.OpenSC(path, "", "", c.Flags())
}
// OpenSC opens an ADSI object with the given path. When provided, the
// username and password are used to establish a security context for the
// connection. When credentials are not provided the existing security
// context of the application is used instead. The provided flags will be used
// when making the connection.
//
// OpenSC returns the ADSI object as an Object type, which provides
// an idiomatic go wrapper around the underlying component object model
// IADs interface.
//
// OpenSC calls QueryInterface internally to acquire an implementation of
// the IADs interface that is needed by the Object type. If the returned
// directory object does not implement the IADs interface an error is
// returned.
//
// The returned object consumes resources until it is closed. It is the
// caller's responsibilty to call Close on the returned object when it is no
// longer needed.
func (c *Client) OpenSC(path, user, password string, flags uint32) (obj *Object, err error) {
idispatch, err := c.OpenInterfaceSC(path, user, password, flags, comiid.IADs)
if err != nil {
return nil, err
}
iface := (*api.IADs)(unsafe.Pointer(idispatch))
obj = NewObject(iface)
return
}
// OpenContainer opens an ADSI container with the given path. The existing
// security context of the application and any flags specified via SetFlags will
// be used when making the connection. The default flags specify an encrypted
// read-only connection.
//
// OpenContainer returns the ADSI container as a Container type, which provides
// an idiomatic go wrapper around the underlying component object model
// IADsContainer interface.
//
// OpenContainer calls QueryInterface internally to acquire an implementation of
// the IADsContainer interface that is needed by the Object type. If the
// returned directory object does not implement the IADsContainer interface an
// error is returned.
//
// The returned container consumes resources until it is closed. It is the
// caller's responsibilty to call Close on the returned container when it is no
// longer needed.
func (c *Client) OpenContainer(path string) (container *Container, err error) {
return c.OpenContainerSC(path, "", "", c.Flags())
}
// OpenContainerSC opens an ADSI container with the given path. When provided,
// the username and password are used to establish a security context for the
// connection. When credentials are not provided the existing security
// context of the application is used instead. The provided flags will be used
// when making the connection.
//
// OpenContainerSC returns the ADSI container as a Container type, which
// provides an idiomatic go wrapper around the underlying component object model
// IADsContainer interface.
//
// OpenContainerSC calls QueryInterface internally to acquire an implementation
// of the IADsContainer interface that is needed by the Object type. If the
// returned directory object does not implement the IADsContainer interface an
// error is returned.
//
// The returned container consumes resources until it is closed. It is the
// caller's responsibilty to call Close on the returned container when it is no
// longer needed.
func (c *Client) OpenContainerSC(path, user, password string, flags uint32) (container *Container, err error) {
idispatch, err := c.OpenInterfaceSC(path, user, password, flags, comiid.IADsContainer)
if err != nil {
return nil, err
}
iface := (*api.IADsContainer)(unsafe.Pointer(idispatch))
container = NewContainer(iface)
return
}
// OpenComputer opens an ADSI computer with the given path. The existing
// security context of the application and any flags specified via SetFlags will
// be used when making the connection. The default flags specify an encrypted
// read-only connection.
//
// OpenComputer returns the ADSI computer as a Computer type, which provides
// an idiomatic go wrapper around the underlying component object model
// IADsComputer interface.
//
// OpenComputer calls QueryInterface internally to acquire an implementation of
// the IADsComputer interface that is needed by the Object type. If the
// returned directory object does not implement the IADsComputer interface an
// error is returned.
//
// The returned computer consumes resources until it is closed. It is the
// caller's responsibilty to call Close on the returned computer when it is no
// longer needed.
func (c *Client) OpenComputer(path string) (computer *Computer, err error) {
return c.OpenComputerSC(path, "", "", c.Flags())
}
// OpenComputerSC opens an ADSI computer with the given path. When provided,
// the username and password are used to establish a security context for the
// connection. When credentials are not provided the existing security
// context of the application is used instead. The provided flags will be used
// when making the connection.
//
// OpenComputerSC returns the ADSI computer as a Computer type, which
// provides an idiomatic go wrapper around the underlying component object model
// IADsComputer interface.
//
// OpenComputerSC calls QueryInterface internally to acquire an implementation
// of the IADsComputer interface that is needed by the Object type. If the
// returned directory object does not implement the IADsComputer interface an
// error is returned.
//
// The returned computer consumes resources until it is closed. It is the
// caller's responsibilty to call Close on the returned computer when it is no
// longer needed.
func (c *Client) OpenComputerSC(path, user, password string, flags uint32) (computer *Computer, err error) {
idispatch, err := c.OpenInterfaceSC(path, user, password, flags, comiid.IADsComputer)
if err != nil {
return nil, err
}
iface := (*api.IADsComputer)(unsafe.Pointer(idispatch))
computer = NewComputer(iface)
return
}
// OpenDispatch opens an ADSI object with the given path. The existing security
// context of the application and any flags specified via SetFlags will be
// used when making the connection. The default flags specify an encrypted
// read-only connection.
//
// OpenDispatch returns a generic IDispatch interface for the object, which can be
// further interrogated to find out which component object model interfaces it
// implements.
//
// To return an object that has already been wrapped in the more convenient
// and safer Object type, use Open instead.
//
// To open an object with a specific interface ID, use OpenInterface instead.
//
// The returned interface consumes resources until it is released. It is the
// caller's responsibilty to call Release on the returned object when it is no
// longer needed.
func (c *Client) OpenDispatch(path string) (obj *ole.IDispatch, err error) {
return c.OpenDispatchSC(path, "", "", c.Flags())
}
// OpenDispatchSC opens an ADSI object with the given path. When provided, the
// username and password are used to establish a security context for the
// connection. When credentials are not provided the existing security
// context of the application is used instead. The provided flags will be used
// when making the connection.
//
// OpenDispatchSC returns a generic IDispatch interface for the object, which
// can be further interrogated to find out which component object model
// interfaces it implements.
//
// To return an object that has already been wrapped in the more convenient
// and safer Object type, use OpenSC instead.
//
// To open an object with a specific interface ID, use OpenInterface instead.
//
// The returned interface consumes resources until it is released. It is the
// caller's responsibilty to call Release on the returned object when it is no
// longer needed.
func (c *Client) OpenDispatchSC(path, user, password string, flags uint32) (obj *ole.IDispatch, err error) {
c.m.Lock()
defer c.m.Unlock()
if c.closed() {
return nil, ErrClosed
}
obj, err = c.open(path, user, password, flags)
return
}
// OpenInterface opens a directory object with the given path. The existing
// security context of the application and any flags specified via SetFlags will
// be used when making the connection. The default flags specify an encrypted
// read-only connection.
//
// OpenInterface calls QueryInterface internally to return a pointer to an
// object implementing the requested interface ID. If the returned object
// does not implement the requested interface an error is returned. The object
// is returned as a pointer to an IDispatch interface; it is expected that the
// caller will recast it as a pointer to the requested implementation.
//
// To return an object that has already been wrapped in the more convenient
// and safer Object type, use OpenObject instead.
//
// The returned interface consumes resources until it is released. It is the
// caller's responsibilty to call Release on the returned object when it is no
// longer needed.
func (c *Client) OpenInterface(path string, iid uuid.UUID) (obj *ole.IDispatch, err error) {
return c.OpenInterfaceSC(path, "", "", c.Flags(), iid)
}
// OpenInterfaceSC opens a directory object with the given path. When provided,
// the username and password are used to establish a security context for the
// connection. When credentials are not provided the existing security
// context of the application is used instead. The provided flags will be used
// when making the connection.
//
// OpenInterfaceSC calls QueryInterface internally to return a pointer to an
// object implementing the requested interface ID. If the returned object
// does not implement the requested interface an error is returned. The object
// is returned as a pointer to an IDispatch interface; it is expected that the
// caller will recast it as a pointer to the requested implementation.
//
// To return an object that has already been wrapped in the more convenient
// and safer Object type, use OpenObject instead.
//
// The returned interface consumes resources until it is released. It is the
// caller's responsibilty to call Release on the returned object when it is no
// longer needed.
func (c *Client) OpenInterfaceSC(path, user, password string, flags uint32, iid uuid.UUID) (obj *ole.IDispatch, err error) {
c.m.Lock()
defer c.m.Unlock()
if c.closed() {
return nil, ErrClosed
}
idispatch, err := c.open(path, user, password, flags)
if err != nil {
return
}
defer idispatch.Release()
obj, err = idispatch.QueryInterface(comutil.GUID(iid))
return
}
func (c *Client) open(path, user, password string, flags uint32) (obj *ole.IDispatch, err error) {
p, err := adspath.Parse(path)
if err != nil {
return
}
ns := c.namespace(p.Scheme)
if ns == nil {
return nil, api.ErrInvalidNamespace
}
if ns.Err != nil {
return nil, ns.Err
}
obj, err = ns.Iface.OpenDSObject(path, user, password, flags)
return
}
// namespace returns information about the namespace with the given name. If
// no namespace has been registered with that name then nil is returend.
//
// The name matching is case-sensitive.
func (c *Client) namespace(name string) *namespace {
for i := 0; i < len(c.n); i++ {
if c.n[i].Name == name {
return &c.n[i]
}
}
return nil
}