1package vault
2
3import (
4	"context"
5	"fmt"
6	"reflect"
7	"sort"
8	"strings"
9
10	"github.com/armon/go-radix"
11	"github.com/hashicorp/errwrap"
12	"github.com/hashicorp/go-multierror"
13	"github.com/hashicorp/vault/helper/identity"
14	"github.com/hashicorp/vault/helper/namespace"
15	"github.com/hashicorp/vault/sdk/helper/strutil"
16	"github.com/hashicorp/vault/sdk/logical"
17	"github.com/mitchellh/copystructure"
18)
19
20// ACL is used to wrap a set of policies to provide
21// an efficient interface for access control.
22type ACL struct {
23	// exactRules contains the path policies that are exact
24	exactRules *radix.Tree
25
26	// prefixRules contains the path policies that are a prefix
27	prefixRules *radix.Tree
28
29	segmentWildcardPaths map[string]interface{}
30
31	// root is enabled if the "root" named policy is present.
32	root bool
33
34	// Stores policies that are actually RGPs for later fetching
35	rgpPolicies []*Policy
36}
37
38type PolicyCheckOpts struct {
39	RootPrivsRequired bool
40	Unauth            bool
41}
42
43type AuthResults struct {
44	ACLResults  *ACLResults
45	Allowed     bool
46	RootPrivs   bool
47	DeniedError bool
48	Error       *multierror.Error
49}
50
51type ACLResults struct {
52	Allowed            bool
53	RootPrivs          bool
54	IsRoot             bool
55	MFAMethods         []string
56	ControlGroup       *ControlGroup
57	CapabilitiesBitmap uint32
58}
59
60// NewACL is used to construct a policy based ACL from a set of policies.
61func NewACL(ctx context.Context, policies []*Policy) (*ACL, error) {
62	// Initialize
63	a := &ACL{
64		exactRules:           radix.New(),
65		prefixRules:          radix.New(),
66		segmentWildcardPaths: make(map[string]interface{}, len(policies)),
67		root:                 false,
68	}
69
70	ns, err := namespace.FromContext(ctx)
71	if err != nil {
72		return nil, err
73	}
74	if ns == nil {
75		return nil, namespace.ErrNoNamespace
76	}
77
78	// Inject each policy
79	for _, policy := range policies {
80		// Ignore a nil policy object
81		if policy == nil {
82			continue
83		}
84
85		switch policy.Type {
86		case PolicyTypeACL:
87		case PolicyTypeRGP:
88			a.rgpPolicies = append(a.rgpPolicies, policy)
89			continue
90		default:
91			return nil, fmt.Errorf("unable to parse policy (wrong type)")
92		}
93
94		// Check if this is root
95		if policy.Name == "root" {
96			if ns.ID != namespace.RootNamespaceID {
97				return nil, fmt.Errorf("root policy is only allowed in root namespace")
98			}
99
100			if len(policies) != 1 {
101				return nil, fmt.Errorf("other policies present along with root")
102			}
103			a.root = true
104		}
105
106		for _, pc := range policy.Paths {
107			var raw interface{}
108			var ok bool
109			var tree *radix.Tree
110
111			switch {
112			case pc.HasSegmentWildcards:
113				raw, ok = a.segmentWildcardPaths[pc.Path]
114			default:
115				// Check which tree to use
116				tree = a.exactRules
117				if pc.IsPrefix {
118					tree = a.prefixRules
119				}
120
121				// Check for an existing policy
122				raw, ok = tree.Get(pc.Path)
123			}
124
125			if !ok {
126				clonedPerms, err := pc.Permissions.Clone()
127				if err != nil {
128					return nil, errwrap.Wrapf("error cloning ACL permissions: {{err}}", err)
129				}
130				switch {
131				case pc.HasSegmentWildcards:
132					a.segmentWildcardPaths[pc.Path] = clonedPerms
133				default:
134					tree.Insert(pc.Path, clonedPerms)
135				}
136				continue
137			}
138
139			// these are the ones already in the tree
140			existingPerms := raw.(*ACLPermissions)
141
142			switch {
143			case existingPerms.CapabilitiesBitmap&DenyCapabilityInt > 0:
144				// If we are explicitly denied in the existing capability set,
145				// don't save anything else
146				continue
147
148			case pc.Permissions.CapabilitiesBitmap&DenyCapabilityInt > 0:
149				// If this new policy explicitly denies, only save the deny value
150				existingPerms.CapabilitiesBitmap = DenyCapabilityInt
151				existingPerms.AllowedParameters = nil
152				existingPerms.DeniedParameters = nil
153				goto INSERT
154
155			default:
156				// Insert the capabilities in this new policy into the existing
157				// value
158				existingPerms.CapabilitiesBitmap = existingPerms.CapabilitiesBitmap | pc.Permissions.CapabilitiesBitmap
159			}
160
161			// Note: In these stanzas, we're preferring minimum lifetimes. So
162			// we take the lesser of two specified max values, or we take the
163			// lesser of two specified min values, the idea being, allowing
164			// token lifetime to be minimum possible.
165			//
166			// If we have an existing max, and we either don't have a current
167			// max, or the current is greater than the previous, use the
168			// existing.
169			if pc.Permissions.MaxWrappingTTL > 0 &&
170				(existingPerms.MaxWrappingTTL == 0 ||
171					pc.Permissions.MaxWrappingTTL < existingPerms.MaxWrappingTTL) {
172				existingPerms.MaxWrappingTTL = pc.Permissions.MaxWrappingTTL
173			}
174			// If we have an existing min, and we either don't have a current
175			// min, or the current is greater than the previous, use the
176			// existing
177			if pc.Permissions.MinWrappingTTL > 0 &&
178				(existingPerms.MinWrappingTTL == 0 ||
179					pc.Permissions.MinWrappingTTL < existingPerms.MinWrappingTTL) {
180				existingPerms.MinWrappingTTL = pc.Permissions.MinWrappingTTL
181			}
182
183			if len(pc.Permissions.AllowedParameters) > 0 {
184				if existingPerms.AllowedParameters == nil {
185					clonedAllowed, err := copystructure.Copy(pc.Permissions.AllowedParameters)
186					if err != nil {
187						return nil, err
188					}
189					existingPerms.AllowedParameters = clonedAllowed.(map[string][]interface{})
190				} else {
191					for key, value := range pc.Permissions.AllowedParameters {
192						pcValue, ok := existingPerms.AllowedParameters[key]
193						// If an empty array exist it should overwrite any other
194						// value.
195						if len(value) == 0 || (ok && len(pcValue) == 0) {
196							existingPerms.AllowedParameters[key] = []interface{}{}
197						} else {
198							// Merge the two maps, appending values on key conflict.
199							existingPerms.AllowedParameters[key] = append(value, existingPerms.AllowedParameters[key]...)
200						}
201					}
202				}
203			}
204
205			if len(pc.Permissions.DeniedParameters) > 0 {
206				if existingPerms.DeniedParameters == nil {
207					clonedDenied, err := copystructure.Copy(pc.Permissions.DeniedParameters)
208					if err != nil {
209						return nil, err
210					}
211					existingPerms.DeniedParameters = clonedDenied.(map[string][]interface{})
212				} else {
213					for key, value := range pc.Permissions.DeniedParameters {
214						pcValue, ok := existingPerms.DeniedParameters[key]
215						// If an empty array exist it should overwrite any other
216						// value.
217						if len(value) == 0 || (ok && len(pcValue) == 0) {
218							existingPerms.DeniedParameters[key] = []interface{}{}
219						} else {
220							// Merge the two maps, appending values on key conflict.
221							existingPerms.DeniedParameters[key] = append(value, existingPerms.DeniedParameters[key]...)
222						}
223					}
224				}
225			}
226
227			if len(pc.Permissions.RequiredParameters) > 0 {
228				if len(existingPerms.RequiredParameters) == 0 {
229					existingPerms.RequiredParameters = pc.Permissions.RequiredParameters
230				} else {
231					for _, v := range pc.Permissions.RequiredParameters {
232						if !strutil.StrListContains(existingPerms.RequiredParameters, v) {
233							existingPerms.RequiredParameters = append(existingPerms.RequiredParameters, v)
234						}
235					}
236				}
237			}
238
239			if len(pc.Permissions.MFAMethods) > 0 {
240				if existingPerms.MFAMethods == nil {
241					existingPerms.MFAMethods = pc.Permissions.MFAMethods
242				} else {
243					for _, method := range pc.Permissions.MFAMethods {
244						existingPerms.MFAMethods = append(existingPerms.MFAMethods, method)
245					}
246				}
247				existingPerms.MFAMethods = strutil.RemoveDuplicates(existingPerms.MFAMethods, false)
248			}
249
250			// No need to dedupe this list since any authorization can satisfy any factor
251			if pc.Permissions.ControlGroup != nil {
252				if len(pc.Permissions.ControlGroup.Factors) > 0 {
253					if existingPerms.ControlGroup == nil {
254						existingPerms.ControlGroup = pc.Permissions.ControlGroup
255					} else {
256						for _, authz := range pc.Permissions.ControlGroup.Factors {
257							existingPerms.ControlGroup.Factors = append(existingPerms.ControlGroup.Factors, authz)
258						}
259					}
260				}
261			}
262
263		INSERT:
264			switch {
265			case pc.HasSegmentWildcards:
266				a.segmentWildcardPaths[pc.Path] = existingPerms
267			default:
268				tree.Insert(pc.Path, existingPerms)
269			}
270		}
271	}
272	return a, nil
273}
274
275func (a *ACL) Capabilities(ctx context.Context, path string) (pathCapabilities []string) {
276	req := &logical.Request{
277		Path: path,
278		// doesn't matter, but use List to trigger fallback behavior so we can
279		// model real behavior
280		Operation: logical.ListOperation,
281	}
282
283	res := a.AllowOperation(ctx, req, true)
284	if res.IsRoot {
285		return []string{RootCapability}
286	}
287
288	capabilities := res.CapabilitiesBitmap
289
290	if capabilities&SudoCapabilityInt > 0 {
291		pathCapabilities = append(pathCapabilities, SudoCapability)
292	}
293	if capabilities&ReadCapabilityInt > 0 {
294		pathCapabilities = append(pathCapabilities, ReadCapability)
295	}
296	if capabilities&ListCapabilityInt > 0 {
297		pathCapabilities = append(pathCapabilities, ListCapability)
298	}
299	if capabilities&UpdateCapabilityInt > 0 {
300		pathCapabilities = append(pathCapabilities, UpdateCapability)
301	}
302	if capabilities&DeleteCapabilityInt > 0 {
303		pathCapabilities = append(pathCapabilities, DeleteCapability)
304	}
305	if capabilities&CreateCapabilityInt > 0 {
306		pathCapabilities = append(pathCapabilities, CreateCapability)
307	}
308
309	// If "deny" is explicitly set or if the path has no capabilities at all,
310	// set the path capabilities to "deny"
311	if capabilities&DenyCapabilityInt > 0 || len(pathCapabilities) == 0 {
312		pathCapabilities = []string{DenyCapability}
313	}
314	return
315}
316
317// AllowOperation is used to check if the given operation is permitted.
318func (a *ACL) AllowOperation(ctx context.Context, req *logical.Request, capCheckOnly bool) (ret *ACLResults) {
319	ret = new(ACLResults)
320
321	// Fast-path root
322	if a.root {
323		ret.Allowed = true
324		ret.RootPrivs = true
325		ret.IsRoot = true
326		return
327	}
328	op := req.Operation
329
330	// Help is always allowed
331	if op == logical.HelpOperation {
332		ret.Allowed = true
333		return
334	}
335
336	var permissions *ACLPermissions
337
338	ns, err := namespace.FromContext(ctx)
339	if err != nil {
340		return
341	}
342	path := ns.Path + req.Path
343
344	// The request path should take care of this already but this is useful for
345	// tests and as defense in depth
346	for {
347		if len(path) > 0 && path[0] == '/' {
348			path = path[1:]
349		} else {
350			break
351		}
352	}
353
354	// Find an exact matching rule, look for prefix if no match
355	var capabilities uint32
356	raw, ok := a.exactRules.Get(path)
357	if ok {
358		permissions = raw.(*ACLPermissions)
359		capabilities = permissions.CapabilitiesBitmap
360		goto CHECK
361	}
362	if op == logical.ListOperation {
363		raw, ok = a.exactRules.Get(strings.TrimSuffix(path, "/"))
364		if ok {
365			permissions = raw.(*ACLPermissions)
366			capabilities = permissions.CapabilitiesBitmap
367			goto CHECK
368		}
369	}
370
371	permissions = a.CheckAllowedFromNonExactPaths(path, false)
372	if permissions != nil {
373		capabilities = permissions.CapabilitiesBitmap
374		goto CHECK
375	}
376
377	// No exact, prefix, or segment wildcard paths found, return without
378	// setting allowed
379	return
380
381CHECK:
382	// Check if the minimum permissions are met
383	// If "deny" has been explicitly set, only deny will be in the map, so we
384	// only need to check for the existence of other values
385	ret.RootPrivs = capabilities&SudoCapabilityInt > 0
386
387	// This is after the RootPrivs check so we can gate on it being from sudo
388	// rather than policy root
389	if capCheckOnly {
390		ret.CapabilitiesBitmap = capabilities
391		return ret
392	}
393
394	ret.MFAMethods = permissions.MFAMethods
395	ret.ControlGroup = permissions.ControlGroup
396
397	operationAllowed := false
398	switch op {
399	case logical.ReadOperation:
400		operationAllowed = capabilities&ReadCapabilityInt > 0
401	case logical.ListOperation:
402		operationAllowed = capabilities&ListCapabilityInt > 0
403	case logical.UpdateOperation:
404		operationAllowed = capabilities&UpdateCapabilityInt > 0
405	case logical.DeleteOperation:
406		operationAllowed = capabilities&DeleteCapabilityInt > 0
407	case logical.CreateOperation:
408		operationAllowed = capabilities&CreateCapabilityInt > 0
409
410	// These three re-use UpdateCapabilityInt since that's the most appropriate
411	// capability/operation mapping
412	case logical.RevokeOperation, logical.RenewOperation, logical.RollbackOperation:
413		operationAllowed = capabilities&UpdateCapabilityInt > 0
414
415	default:
416		return
417	}
418
419	if !operationAllowed {
420		return
421	}
422
423	if permissions.MaxWrappingTTL > 0 {
424		if req.WrapInfo == nil || req.WrapInfo.TTL > permissions.MaxWrappingTTL {
425			return
426		}
427	}
428	if permissions.MinWrappingTTL > 0 {
429		if req.WrapInfo == nil || req.WrapInfo.TTL < permissions.MinWrappingTTL {
430			return
431		}
432	}
433	// This situation can happen because of merging, even though in a single
434	// path statement we check on ingress
435	if permissions.MinWrappingTTL != 0 &&
436		permissions.MaxWrappingTTL != 0 &&
437		permissions.MaxWrappingTTL < permissions.MinWrappingTTL {
438		return
439	}
440
441	// Only check parameter permissions for operations that can modify
442	// parameters.
443	if op == logical.ReadOperation || op == logical.UpdateOperation || op == logical.CreateOperation {
444		for _, parameter := range permissions.RequiredParameters {
445			if _, ok := req.Data[strings.ToLower(parameter)]; !ok {
446				return
447			}
448		}
449
450		// If there are no data fields, allow
451		if len(req.Data) == 0 {
452			ret.Allowed = true
453			return
454		}
455
456		if len(permissions.DeniedParameters) == 0 {
457			goto ALLOWED_PARAMETERS
458		}
459
460		// Check if all parameters have been denied
461		if _, ok := permissions.DeniedParameters["*"]; ok {
462			return
463		}
464
465		for parameter, value := range req.Data {
466			// Check if parameter has been explicitly denied
467			if valueSlice, ok := permissions.DeniedParameters[strings.ToLower(parameter)]; ok {
468				// If the value exists in denied values slice, deny
469				if valueInParameterList(value, valueSlice) {
470					return
471				}
472			}
473		}
474
475	ALLOWED_PARAMETERS:
476		// If we don't have any allowed parameters set, allow
477		if len(permissions.AllowedParameters) == 0 {
478			ret.Allowed = true
479			return
480		}
481
482		_, allowedAll := permissions.AllowedParameters["*"]
483		if len(permissions.AllowedParameters) == 1 && allowedAll {
484			ret.Allowed = true
485			return
486		}
487
488		for parameter, value := range req.Data {
489			valueSlice, ok := permissions.AllowedParameters[strings.ToLower(parameter)]
490			// Requested parameter is not in allowed list
491			if !ok && !allowedAll {
492				return
493			}
494
495			// If the value doesn't exists in the allowed values slice,
496			// deny
497			if ok && !valueInParameterList(value, valueSlice) {
498				return
499			}
500		}
501	}
502
503	ret.Allowed = true
504	return
505}
506
507type wcPathDescr struct {
508	firstWCOrGlob int
509	wildcards     int
510	isPrefix      bool
511	wcPath        string
512	perms         *ACLPermissions
513}
514
515// CheckAllowedFromNonExactPaths returns permissions corresponding to a
516// matching path with wildcards/globs. If bareMount is true, the path should
517// correspond to a mount prefix, and what is returned is either a non-nil set
518// of permissions from some allowed path underneath the mount (for use in mount
519// access checks), or nil indicating no non-deny permissions were found.
520func (a *ACL) CheckAllowedFromNonExactPaths(path string, bareMount bool) *ACLPermissions {
521	wcPathDescrs := make([]wcPathDescr, 0, len(a.segmentWildcardPaths)+1)
522
523	less := func(i, j int) bool {
524		// In the case of multiple matches, we use this priority order,
525		// which tries to most closely match longest-prefix:
526		//
527		// * First glob or wildcard position (prefer foo/a* over foo/+,
528		//   foo/bar/+/baz over foo/+/bar/baz)
529		// * Whether it's a prefix (prefer foo/+/bar over foo/+/ba*,
530		//   foo/+ over foo/*)
531		// * Number of wildcard segments (prefer foo/bar/+/baz over foo/+/+/baz)
532		// * Length check (prefer foo/+/bar/ba* over foo/+/bar/b*)
533		// * Lexicographical ordering (preferring less, arbitrarily)
534		//
535		// That final case (lexigraphical) should never really come up. It's more
536		// of a throwing-up-hands scenario akin to panic("should not be here")
537		// statements, but less panicky.
538
539		pdi, pdj := wcPathDescrs[i], wcPathDescrs[j]
540
541		// If the first wildcard (+) or glob (*) occurs earlier in pdi,
542		// pdi is lower priority
543		if pdi.firstWCOrGlob < pdj.firstWCOrGlob {
544			return true
545		} else if pdi.firstWCOrGlob > pdj.firstWCOrGlob {
546			return false
547		}
548
549		// If pdi ends in * and pdj doesn't, pdi is lower priority
550		if pdi.isPrefix && !pdj.isPrefix {
551			return true
552		} else if !pdi.isPrefix && pdj.isPrefix {
553			return false
554		}
555
556		// If pdi has more wc segs, pdi is lower priority
557		if pdi.wildcards > pdj.wildcards {
558			return true
559		} else if pdi.wildcards < pdj.wildcards {
560			return false
561		}
562
563		// If pdi is shorter, it is lower priority
564		if len(pdi.wcPath) < len(pdj.wcPath) {
565			return true
566		} else if len(pdi.wcPath) > len(pdj.wcPath) {
567			return false
568		}
569
570		// If pdi is smaller lexicographically, it is lower priority
571		if pdi.wcPath < pdj.wcPath {
572			return true
573		} else if pdi.wcPath > pdj.wcPath {
574			return false
575		}
576		return false
577	}
578
579	// Find a prefix rule if any.
580	{
581		prefix, raw, ok := a.prefixRules.LongestPrefix(path)
582		if ok {
583			if len(a.segmentWildcardPaths) == 0 {
584				return raw.(*ACLPermissions)
585			}
586			wcPathDescrs = append(wcPathDescrs, wcPathDescr{
587				firstWCOrGlob: len(prefix),
588				wcPath:        prefix,
589				isPrefix:      true,
590				perms:         raw.(*ACLPermissions),
591			})
592		}
593	}
594
595	if len(a.segmentWildcardPaths) == 0 {
596		return nil
597	}
598
599	pathParts := strings.Split(path, "/")
600
601SWCPATH:
602	for fullWCPath := range a.segmentWildcardPaths {
603		if fullWCPath == "" {
604			continue
605		}
606		pd := wcPathDescr{firstWCOrGlob: strings.Index(fullWCPath, "+")}
607
608		currWCPath := fullWCPath
609		if currWCPath[len(currWCPath)-1] == '*' {
610			pd.isPrefix = true
611			currWCPath = currWCPath[0 : len(currWCPath)-1]
612		}
613		pd.wcPath = currWCPath
614
615		splitCurrWCPath := strings.Split(currWCPath, "/")
616
617		if !bareMount && len(pathParts) < len(splitCurrWCPath) {
618			// check if the path coming in is shorter; if so it can't match
619			continue
620		}
621		if !bareMount && !pd.isPrefix && len(splitCurrWCPath) != len(pathParts) {
622			// If it's not a prefix we expect the same number of segments
623			continue
624		}
625
626		segments := make([]string, 0, len(splitCurrWCPath))
627		for i, aclPart := range splitCurrWCPath {
628			switch {
629			case aclPart == "+":
630				pd.wildcards++
631				segments = append(segments, pathParts[i])
632
633			case aclPart == pathParts[i]:
634				segments = append(segments, pathParts[i])
635
636			case pd.isPrefix && i == len(splitCurrWCPath)-1 && strings.HasPrefix(pathParts[i], aclPart):
637				segments = append(segments, pathParts[i:]...)
638
639			case !bareMount:
640				// Found a mismatch, give up on this segmentWildcardPath
641				continue SWCPATH
642			}
643
644			// -2 because we're always invoked with a trailing "/" in case bareMount.
645			if bareMount && i == len(pathParts)-2 {
646				joinedPath := strings.Join(segments, "/") + "/"
647				// Check the current joined path so far. If we find a prefix,
648				// check permissions. If they're defined but not deny, success.
649				if strings.HasPrefix(joinedPath, path) {
650					permissions := a.segmentWildcardPaths[fullWCPath].(*ACLPermissions)
651					if permissions.CapabilitiesBitmap&DenyCapabilityInt == 0 && permissions.CapabilitiesBitmap > 0 {
652						return permissions
653					}
654				}
655				continue SWCPATH
656			}
657		}
658		pd.perms = a.segmentWildcardPaths[fullWCPath].(*ACLPermissions)
659		wcPathDescrs = append(wcPathDescrs, pd)
660	}
661
662	if bareMount || len(wcPathDescrs) == 0 {
663		return nil
664	}
665
666	// We don't do this in the bare mount check because we don't care about
667	// priority, we only care about any capability at all.
668	sort.Slice(wcPathDescrs, less)
669
670	return wcPathDescrs[len(wcPathDescrs)-1].perms
671}
672
673func (c *Core) performPolicyChecks(ctx context.Context, acl *ACL, te *logical.TokenEntry, req *logical.Request, inEntity *identity.Entity, opts *PolicyCheckOpts) *AuthResults {
674	ret := new(AuthResults)
675
676	// First, perform normal ACL checks if requested. The only time no ACL
677	// should be applied is if we are only processing EGPs against a login
678	// path in which case opts.Unauth will be set.
679	if acl != nil && !opts.Unauth {
680		ret.ACLResults = acl.AllowOperation(ctx, req, false)
681		ret.RootPrivs = ret.ACLResults.RootPrivs
682		// Root is always allowed; skip Sentinel/MFA checks
683		if ret.ACLResults.IsRoot {
684			//logger.Warn("token is root, skipping checks")
685			ret.Allowed = true
686			return ret
687		}
688		if !ret.ACLResults.Allowed {
689			return ret
690		}
691		if !ret.RootPrivs && opts.RootPrivsRequired {
692			return ret
693		}
694	}
695
696	c.performEntPolicyChecks(ctx, acl, te, req, inEntity, opts, ret)
697
698	return ret
699}
700
701func valueInParameterList(v interface{}, list []interface{}) bool {
702	// Empty list is equivalent to the item always existing in the list
703	if len(list) == 0 {
704		return true
705	}
706
707	return valueInSlice(v, list)
708}
709
710func valueInSlice(v interface{}, list []interface{}) bool {
711	for _, el := range list {
712		if el == nil || v == nil {
713			// It doesn't seem possible to set up a nil entry in the list, but it is possible
714			// to pass in a null entry in the API request being checked. Just in case,
715			// nil will match nil.
716			if el == v {
717				return true
718			}
719		} else if reflect.TypeOf(el).String() == "string" && reflect.TypeOf(v).String() == "string" {
720			item := el.(string)
721			val := v.(string)
722
723			if strutil.GlobbedStringsMatch(item, val) {
724				return true
725			}
726		} else if reflect.DeepEqual(el, v) {
727			return true
728		}
729	}
730
731	return false
732}
733