Bump version to 21.06.18.1
[LibreOffice.git] / stoc / source / security / access_controller.cxx
blob856c493a355f6d01adb49281b5d3c2ca7fcc4816
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <vector>
23 #include <osl/diagnose.h>
24 #include <osl/mutex.hxx>
25 #include <osl/thread.hxx>
27 #include <rtl/ustrbuf.hxx>
28 #include <sal/log.hxx>
30 #include <uno/current_context.h>
31 #include <uno/lbnames.h>
33 #include <cppuhelper/implbase.hxx>
34 #include <cppuhelper/compbase.hxx>
35 #include <cppuhelper/supportsservice.hxx>
37 #include <com/sun/star/uno/XCurrentContext.hpp>
38 #include <com/sun/star/uno/DeploymentException.hpp>
39 #include <com/sun/star/uno/XComponentContext.hpp>
40 #include <com/sun/star/lang/DisposedException.hpp>
41 #include <com/sun/star/lang/XServiceInfo.hpp>
42 #include <com/sun/star/lang/XInitialization.hpp>
43 #include <com/sun/star/security/XAccessController.hpp>
44 #include <com/sun/star/security/XPolicy.hpp>
46 #include "lru_cache.h"
47 #include "permissions.h"
49 #include <memory>
51 #define SERVICE_NAME "com.sun.star.security.AccessController"
52 #define USER_CREDS "access-control.user-credentials"
55 using namespace ::std;
56 using namespace ::osl;
57 using namespace ::cppu;
58 using namespace ::com::sun::star;
59 using namespace css::uno;
60 using namespace stoc_sec;
62 namespace {
64 // static stuff initialized when loading lib
65 OUString s_envType = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
66 const char s_acRestriction[] = "access-control.restriction";
69 /** ac context intersects permissions of two ac contexts
71 class acc_Intersection
72 : public WeakImplHelper< security::XAccessControlContext >
74 Reference< security::XAccessControlContext > m_x1, m_x2;
76 acc_Intersection(
77 Reference< security::XAccessControlContext > const & x1,
78 Reference< security::XAccessControlContext > const & x2 );
80 public:
81 static Reference< security::XAccessControlContext > create(
82 Reference< security::XAccessControlContext > const & x1,
83 Reference< security::XAccessControlContext > const & x2 );
85 // XAccessControlContext impl
86 virtual void SAL_CALL checkPermission(
87 Any const & perm ) override;
90 acc_Intersection::acc_Intersection(
91 Reference< security::XAccessControlContext > const & x1,
92 Reference< security::XAccessControlContext > const & x2 )
93 : m_x1( x1 )
94 , m_x2( x2 )
97 Reference< security::XAccessControlContext > acc_Intersection::create(
98 Reference< security::XAccessControlContext > const & x1,
99 Reference< security::XAccessControlContext > const & x2 )
101 if (! x1.is())
102 return x2;
103 if (! x2.is())
104 return x1;
105 return new acc_Intersection( x1, x2 );
108 void acc_Intersection::checkPermission(
109 Any const & perm )
111 m_x1->checkPermission( perm );
112 m_x2->checkPermission( perm );
115 /** ac context unifies permissions of two ac contexts
117 class acc_Union
118 : public WeakImplHelper< security::XAccessControlContext >
120 Reference< security::XAccessControlContext > m_x1, m_x2;
122 acc_Union(
123 Reference< security::XAccessControlContext > const & x1,
124 Reference< security::XAccessControlContext > const & x2 );
126 public:
127 static Reference< security::XAccessControlContext > create(
128 Reference< security::XAccessControlContext > const & x1,
129 Reference< security::XAccessControlContext > const & x2 );
131 // XAccessControlContext impl
132 virtual void SAL_CALL checkPermission(
133 Any const & perm ) override;
136 acc_Union::acc_Union(
137 Reference< security::XAccessControlContext > const & x1,
138 Reference< security::XAccessControlContext > const & x2 )
139 : m_x1( x1 )
140 , m_x2( x2 )
143 Reference< security::XAccessControlContext > acc_Union::create(
144 Reference< security::XAccessControlContext > const & x1,
145 Reference< security::XAccessControlContext > const & x2 )
147 if (! x1.is())
148 return Reference< security::XAccessControlContext >(); // unrestricted
149 if (! x2.is())
150 return Reference< security::XAccessControlContext >(); // unrestricted
151 return new acc_Union( x1, x2 );
154 void acc_Union::checkPermission(
155 Any const & perm )
159 m_x1->checkPermission( perm );
161 catch (security::AccessControlException &)
163 m_x2->checkPermission( perm );
167 /** ac context doing permission checks on static permissions
169 class acc_Policy
170 : public WeakImplHelper< security::XAccessControlContext >
172 PermissionCollection m_permissions;
174 public:
175 explicit acc_Policy(
176 PermissionCollection const & permissions )
177 : m_permissions( permissions )
180 // XAccessControlContext impl
181 virtual void SAL_CALL checkPermission(
182 Any const & perm ) override;
185 void acc_Policy::checkPermission(
186 Any const & perm )
188 m_permissions.checkPermission( perm );
191 /** current context overriding dynamic ac restriction
193 class acc_CurrentContext
194 : public WeakImplHelper< XCurrentContext >
196 Reference< XCurrentContext > m_xDelegate;
197 Any m_restriction;
199 public:
200 acc_CurrentContext(
201 Reference< XCurrentContext > const & xDelegate,
202 Reference< security::XAccessControlContext > const & xRestriction );
204 // XCurrentContext impl
205 virtual Any SAL_CALL getValueByName( OUString const & name ) override;
208 acc_CurrentContext::acc_CurrentContext(
209 Reference< XCurrentContext > const & xDelegate,
210 Reference< security::XAccessControlContext > const & xRestriction )
211 : m_xDelegate( xDelegate )
213 if (xRestriction.is())
215 m_restriction <<= xRestriction;
217 // return empty any otherwise on getValueByName(), not null interface
220 Any acc_CurrentContext::getValueByName( OUString const & name )
222 if (name == s_acRestriction)
224 return m_restriction;
226 else if (m_xDelegate.is())
228 return m_xDelegate->getValueByName( name );
230 else
232 return Any();
237 Reference< security::XAccessControlContext > getDynamicRestriction(
238 Reference< XCurrentContext > const & xContext )
240 if (xContext.is())
242 Any acc(xContext->getValueByName(s_acRestriction));
243 if (typelib_TypeClass_INTERFACE == acc.pType->eTypeClass)
245 // avoid ref-counting
246 OUString const & typeName =
247 OUString::unacquired( &acc.pType->pTypeName );
248 if ( typeName == "com.sun.star.security.XAccessControlContext" )
250 return Reference< security::XAccessControlContext >(
251 *static_cast< security::XAccessControlContext ** >( acc.pData ) );
253 else // try to query
255 return Reference< security::XAccessControlContext >::query(
256 *static_cast< XInterface ** >( acc.pData ) );
260 return Reference< security::XAccessControlContext >();
263 class cc_reset
265 void * m_cc;
266 public:
267 explicit cc_reset( void * cc )
268 : m_cc( cc ) {}
269 ~cc_reset()
270 { ::uno_setCurrentContext( m_cc, s_envType.pData, nullptr ); }
273 struct MutexHolder
275 Mutex m_mutex;
277 typedef WeakComponentImplHelper<
278 security::XAccessController, lang::XServiceInfo, lang::XInitialization > t_helper;
281 class AccessController
282 : public MutexHolder
283 , public t_helper
285 Reference< XComponentContext > m_xComponentContext;
287 Reference< security::XPolicy > m_xPolicy;
288 Reference< security::XPolicy > const & getPolicy();
290 // mode
291 enum class Mode { Off, On, DynamicOnly, SingleUser, SingleDefaultUser };
292 Mode m_mode;
294 PermissionCollection m_defaultPermissions;
295 // for single-user mode
296 PermissionCollection m_singleUserPermissions;
297 OUString m_singleUserId;
298 bool m_defaultPerm_init;
299 bool m_singleUser_init;
300 // for multi-user mode
301 lru_cache< OUString, PermissionCollection, OUStringHash, equal_to< OUString > >
302 m_user2permissions;
304 ThreadData m_rec;
305 typedef vector< pair< OUString, Any > > t_rec_vec;
306 void clearPostPoned();
307 void checkAndClearPostPoned();
309 PermissionCollection getEffectivePermissions(
310 Reference< XCurrentContext > const & xContext,
311 Any const & demanded_perm );
313 protected:
314 virtual void SAL_CALL disposing() override;
316 public:
317 explicit AccessController( Reference< XComponentContext > const & xComponentContext );
319 // XInitialization impl
320 virtual void SAL_CALL initialize(
321 Sequence< Any > const & arguments ) override;
323 // XAccessController impl
324 virtual void SAL_CALL checkPermission(
325 Any const & perm ) override;
326 virtual Any SAL_CALL doRestricted(
327 Reference< security::XAction > const & xAction,
328 Reference< security::XAccessControlContext > const & xRestriction ) override;
329 virtual Any SAL_CALL doPrivileged(
330 Reference< security::XAction > const & xAction,
331 Reference< security::XAccessControlContext > const & xRestriction ) override;
332 virtual Reference< security::XAccessControlContext > SAL_CALL getContext() override;
334 // XServiceInfo impl
335 virtual OUString SAL_CALL getImplementationName() override;
336 virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName ) override;
337 virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
340 AccessController::AccessController( Reference< XComponentContext > const & xComponentContext )
341 : t_helper( m_mutex )
342 , m_xComponentContext( xComponentContext )
343 , m_mode( Mode::On ) // default
344 , m_defaultPerm_init( false )
345 , m_singleUser_init( false )
346 , m_rec( nullptr )
348 // The .../mode value had originally been set in
349 // cppu::add_access_control_entries (cppuhelper/source/servicefactory.cxx)
350 // to something other than "off" depending on various UNO_AC* bootstrap
351 // variables that are no longer supported, so this is mostly dead code now:
352 OUString mode;
353 if (m_xComponentContext->getValueByName( "/services/" SERVICE_NAME "/mode" ) >>= mode)
355 if ( mode == "off" )
357 m_mode = Mode::Off;
359 else if ( mode == "on" )
361 m_mode = Mode::On;
363 else if ( mode == "dynamic-only" )
365 m_mode = Mode::DynamicOnly;
367 else if ( mode == "single-user" )
369 m_xComponentContext->getValueByName(
370 "/services/" SERVICE_NAME "/single-user-id" ) >>= m_singleUserId;
371 if (m_singleUserId.isEmpty())
373 throw RuntimeException(
374 "expected a user id in component context entry "
375 "\"/services/" SERVICE_NAME "/single-user-id\"!",
376 static_cast<OWeakObject *>(this) );
378 m_mode = Mode::SingleUser;
380 else if ( mode == "single-default-user" )
382 m_mode = Mode::SingleDefaultUser;
386 // switch on caching for Mode::DynamicOnly and Mode::On (shareable multi-user process)
387 if (Mode::On != m_mode && Mode::DynamicOnly != m_mode)
388 return;
390 sal_Int32 cacheSize = 0; // multi-user cache size
391 if (! (m_xComponentContext->getValueByName(
392 "/services/" SERVICE_NAME "/user-cache-size" ) >>= cacheSize))
394 cacheSize = 128; // reasonable default?
396 #ifdef __CACHE_DIAGNOSE
397 cacheSize = 2;
398 #endif
399 m_user2permissions.setSize( cacheSize );
402 void AccessController::disposing()
404 m_mode = Mode::Off; // avoid checks from now on xxx todo review/ better Mode::DynamicOnly?
405 m_xPolicy.clear();
406 m_xComponentContext.clear();
409 // XInitialization impl
411 void AccessController::initialize(
412 Sequence< Any > const & arguments )
414 // xxx todo: review for forking
415 // portal forking hack: re-initialize for another user-id
416 if (Mode::SingleUser != m_mode) // only if in single-user mode
418 throw RuntimeException(
419 "invalid call: ac must be in \"single-user\" mode!", static_cast<OWeakObject *>(this) );
421 OUString userId;
422 arguments[ 0 ] >>= userId;
423 if ( userId.isEmpty() )
425 throw RuntimeException(
426 "expected a user-id as first argument!", static_cast<OWeakObject *>(this) );
428 // assured that no sync is necessary: no check happens at this forking time
429 m_singleUserId = userId;
430 m_singleUser_init = false;
434 Reference< security::XPolicy > const & AccessController::getPolicy()
436 // get policy singleton
437 if (! m_xPolicy.is())
439 Reference< security::XPolicy > xPolicy;
440 m_xComponentContext->getValueByName(
441 "/singletons/com.sun.star.security.thePolicy" ) >>= xPolicy;
442 if (!xPolicy.is())
444 throw SecurityException(
445 "cannot get policy singleton!", static_cast<OWeakObject *>(this) );
448 MutexGuard guard( m_mutex );
449 if (! m_xPolicy.is())
451 m_xPolicy = xPolicy;
454 return m_xPolicy;
457 #ifdef __DIAGNOSE
458 static void dumpPermissions(
459 PermissionCollection const & collection, OUString const & userId = OUString() )
461 OUStringBuffer buf( 48 );
462 if (!userId.isEmpty())
464 buf.append( "> dumping permissions of user \"" );
465 buf.append( userId );
466 buf.append( "\":" );
468 else
470 buf.append( "> dumping default permissions:" );
472 SAL_INFO("stoc", buf.makeStringAndClear() );
473 Sequence< OUString > permissions( collection.toStrings() );
474 OUString const * p = permissions.getConstArray();
475 for ( sal_Int32 nPos = 0; nPos < permissions.getLength(); ++nPos )
477 SAL_INFO("stoc", p[ nPos ] );
479 SAL_INFO("stoc", "> permission dump done" );
481 #endif
484 void AccessController::clearPostPoned()
486 delete static_cast< t_rec_vec * >( m_rec.getData() );
487 m_rec.setData( nullptr );
490 void AccessController::checkAndClearPostPoned()
492 // check postponed permissions
493 std::unique_ptr< t_rec_vec > rec( static_cast< t_rec_vec * >( m_rec.getData() ) );
494 m_rec.setData( nullptr ); // takeover ownership
495 OSL_ASSERT(rec);
496 if (!rec)
497 return;
499 t_rec_vec const& vec = *rec;
500 switch (m_mode)
502 case Mode::SingleUser:
504 OSL_ASSERT( m_singleUser_init );
505 for (const auto & p : vec)
507 OSL_ASSERT( m_singleUserId == p.first );
508 m_singleUserPermissions.checkPermission( p.second );
510 break;
512 case Mode::SingleDefaultUser:
514 OSL_ASSERT( m_defaultPerm_init );
515 for (const auto & p : vec)
517 OSL_ASSERT( p.first.isEmpty() ); // default-user
518 m_defaultPermissions.checkPermission( p.second );
520 break;
522 case Mode::On:
524 for (const auto & p : vec)
526 PermissionCollection const * pPermissions;
527 // lookup policy for user
529 MutexGuard guard( m_mutex );
530 pPermissions = m_user2permissions.lookup( p.first );
532 OSL_ASSERT( pPermissions );
533 if (pPermissions)
535 pPermissions->checkPermission( p.second );
538 break;
540 default:
541 OSL_FAIL( "### this should never be called in this ac mode!" );
542 break;
546 /** this is the only function calling the policy singleton and thus has to take care
547 of recurring calls!
549 @param demanded_perm (if not empty) is the demanded permission of a checkPermission() call
550 which will be postponed for recurring calls
552 PermissionCollection AccessController::getEffectivePermissions(
553 Reference< XCurrentContext > const & xContext,
554 Any const & demanded_perm )
556 OUString userId;
558 switch (m_mode)
560 case Mode::SingleUser:
562 if (m_singleUser_init)
563 return m_singleUserPermissions;
564 userId = m_singleUserId;
565 break;
567 case Mode::SingleDefaultUser:
569 if (m_defaultPerm_init)
570 return m_defaultPermissions;
571 break;
573 case Mode::On:
575 if (xContext.is())
577 xContext->getValueByName( USER_CREDS ".id" ) >>= userId;
579 if ( userId.isEmpty() )
581 throw SecurityException(
582 "cannot determine current user in multi-user ac!", static_cast<OWeakObject *>(this) );
585 // lookup policy for user
586 MutexGuard guard( m_mutex );
587 PermissionCollection const * pPermissions = m_user2permissions.lookup( userId );
588 if (pPermissions)
589 return *pPermissions;
590 break;
592 default:
593 OSL_FAIL( "### this should never be called in this ac mode!" );
594 return PermissionCollection();
597 // call on policy
598 // iff this is a recurring call for the default user, then grant all permissions
599 t_rec_vec * rec = static_cast< t_rec_vec * >( m_rec.getData() );
600 if (rec) // tls entry exists => this is recursive call
602 if (demanded_perm.hasValue())
604 // enqueue
605 rec->push_back( pair< OUString, Any >( userId, demanded_perm ) );
607 #ifdef __DIAGNOSE
608 SAL_INFO("stoc", "> info: recurring call of user: " << userId );
609 #endif
610 return PermissionCollection( new AllPermission() );
612 else // no tls
614 rec = new t_rec_vec;
615 m_rec.setData( rec );
618 try // calls on API
620 // init default permissions
621 if (! m_defaultPerm_init)
623 PermissionCollection defaultPermissions(
624 getPolicy()->getDefaultPermissions() );
625 // assign
626 MutexGuard guard( m_mutex );
627 if (! m_defaultPerm_init)
629 m_defaultPermissions = defaultPermissions;
630 m_defaultPerm_init = true;
632 #ifdef __DIAGNOSE
633 dumpPermissions( m_defaultPermissions );
634 #endif
637 PermissionCollection ret;
639 // init user permissions
640 switch (m_mode)
642 case Mode::SingleUser:
644 ret = PermissionCollection(
645 getPolicy()->getPermissions( userId ), m_defaultPermissions );
647 // assign
648 MutexGuard guard( m_mutex );
649 if (m_singleUser_init)
651 ret = m_singleUserPermissions;
653 else
655 m_singleUserPermissions = ret;
656 m_singleUser_init = true;
659 #ifdef __DIAGNOSE
660 dumpPermissions( ret, userId );
661 #endif
662 break;
664 case Mode::SingleDefaultUser:
666 ret = m_defaultPermissions;
667 break;
669 case Mode::On:
671 ret = PermissionCollection(
672 getPolicy()->getPermissions( userId ), m_defaultPermissions );
674 // cache
675 MutexGuard guard( m_mutex );
676 m_user2permissions.set( userId, ret );
678 #ifdef __DIAGNOSE
679 dumpPermissions( ret, userId );
680 #endif
681 break;
683 default:
684 break;
687 // check postponed
688 checkAndClearPostPoned();
689 return ret;
691 catch (const security::AccessControlException & exc) // wrapped into DeploymentException
693 clearPostPoned(); // safety: exception could have happened before checking postponed?
694 throw DeploymentException( "deployment error (AccessControlException occurred): " + exc.Message, exc.Context );
696 catch (RuntimeException &)
698 // don't check postponed, just cleanup
699 clearPostPoned();
700 delete static_cast< t_rec_vec * >( m_rec.getData() );
701 m_rec.setData( nullptr );
702 throw;
704 catch (Exception &)
706 // check postponed permissions first
707 // => AccessControlExceptions are errors, user exceptions not!
708 checkAndClearPostPoned();
709 throw;
711 catch (...)
713 // don't check postponed, just cleanup
714 clearPostPoned();
715 throw;
719 // XAccessController impl
721 void AccessController::checkPermission(
722 Any const & perm )
724 if (rBHelper.bDisposed)
726 throw lang::DisposedException(
727 "checkPermission() call on disposed AccessController!", static_cast<OWeakObject *>(this) );
730 if (Mode::Off == m_mode)
731 return;
733 // first dynamic check of ac contexts
734 Reference< XCurrentContext > xContext;
735 ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr );
736 Reference< security::XAccessControlContext > xACC( getDynamicRestriction( xContext ) );
737 if (xACC.is())
739 xACC->checkPermission( perm );
742 if (Mode::DynamicOnly == m_mode)
743 return;
745 // then static check
746 getEffectivePermissions( xContext, perm ).checkPermission( perm );
749 Any AccessController::doRestricted(
750 Reference< security::XAction > const & xAction,
751 Reference< security::XAccessControlContext > const & xRestriction )
753 if (rBHelper.bDisposed)
755 throw lang::DisposedException(
756 "doRestricted() call on disposed AccessController!", static_cast<OWeakObject *>(this) );
759 if (Mode::Off == m_mode) // optimize this way, because no dynamic check will be performed
760 return xAction->run();
762 if (xRestriction.is())
764 Reference< XCurrentContext > xContext;
765 ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr );
767 // override restriction
768 Reference< XCurrentContext > xNewContext(
769 new acc_CurrentContext( xContext, acc_Intersection::create(
770 xRestriction, getDynamicRestriction( xContext ) ) ) );
771 ::uno_setCurrentContext( xNewContext.get(), s_envType.pData, nullptr );
772 cc_reset reset( xContext.get() );
773 return xAction->run();
775 else
777 return xAction->run();
781 Any AccessController::doPrivileged(
782 Reference< security::XAction > const & xAction,
783 Reference< security::XAccessControlContext > const & xRestriction )
785 if (rBHelper.bDisposed)
787 throw lang::DisposedException(
788 "doPrivileged() call on disposed AccessController!", static_cast<OWeakObject *>(this) );
791 if (Mode::Off == m_mode) // no dynamic check will be performed
793 return xAction->run();
796 Reference< XCurrentContext > xContext;
797 ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr );
799 Reference< security::XAccessControlContext > xOldRestr(
800 getDynamicRestriction( xContext ) );
802 if (xOldRestr.is()) // previous restriction
804 // override restriction
805 Reference< XCurrentContext > xNewContext(
806 new acc_CurrentContext( xContext, acc_Union::create( xRestriction, xOldRestr ) ) );
807 ::uno_setCurrentContext( xNewContext.get(), s_envType.pData, nullptr );
808 cc_reset reset( xContext.get() );
809 return xAction->run();
811 else // no previous restriction => never current restriction
813 return xAction->run();
817 Reference< security::XAccessControlContext > AccessController::getContext()
819 if (rBHelper.bDisposed)
821 throw lang::DisposedException(
822 "getContext() call on disposed AccessController!", static_cast<OWeakObject *>(this) );
825 if (Mode::Off == m_mode) // optimize this way, because no dynamic check will be performed
827 return new acc_Policy( PermissionCollection( new AllPermission() ) );
830 Reference< XCurrentContext > xContext;
831 ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr );
833 return acc_Intersection::create(
834 getDynamicRestriction( xContext ),
835 new acc_Policy( getEffectivePermissions( xContext, Any() ) ) );
838 // XServiceInfo impl
840 OUString AccessController::getImplementationName()
842 return "com.sun.star.security.comp.stoc.AccessController";
845 sal_Bool AccessController::supportsService( OUString const & serviceName )
847 return cppu::supportsService(this, serviceName);
850 Sequence< OUString > AccessController::getSupportedServiceNames()
852 Sequence<OUString> aSNS { SERVICE_NAME };
853 return aSNS;
858 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
859 com_sun_star_security_comp_stoc_AccessController_get_implementation(
860 css::uno::XComponentContext *context,
861 css::uno::Sequence<css::uno::Any> const &)
863 return cppu::acquire(new AccessController(context));
866 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */