xref: /aosp_15_r20/external/tpm2-tss/src/tss2-fapi/api/Fapi_ChangeAuth.c (revision 758e9fba6fc9adbf15340f70c73baee7b168b1c9)
1 /* SPDX-License-Identifier: BSD-2-Clause */
2 /*******************************************************************************
3  * Copyright 2018-2019, Fraunhofer SIT sponsored by Infineon Technologies AG
4  * All rights reserved.
5  ******************************************************************************/
6 
7 #ifdef HAVE_CONFIG_H
8 #include <config.h>
9 #endif
10 
11 #include <stdlib.h>
12 #include <errno.h>
13 #include <unistd.h>
14 #include <errno.h>
15 #include <string.h>
16 
17 #include "tss2_fapi.h"
18 #include "fapi_int.h"
19 #include "fapi_util.h"
20 #include "tss2_esys.h"
21 #define LOGMODULE fapi
22 #include "util/log.h"
23 #include "util/aux_util.h"
24 #include "fapi_crypto.h"
25 
26 /** One-Call function for Fapi_ChangeAuth
27  *
28  * Changes the Authorization data of an entity found at keyPath. The parameter
29  * authValue is a 0-terminated UTF-8 encoded password.
30  * If it is longer than the digest size of the entity's nameAlg, it will be
31  * hashed according the the TPM specification part 1, rev 138, section 19.6.4.3.
32  *
33  * @param[in,out] context The FAPI_CONTEXT
34  * @param[in] entityPath The path to the entity to modify
35  * @param[in] authValue The new 0-terminated password to set for the entity.
36  *            May be NULL
37  *
38  * @retval TSS2_RC_SUCCESS: if the function call was a success.
39  * @retval TSS2_FAPI_RC_BAD_REFERENCE: if context or entityPath is NULL.
40  * @retval TSS2_FAPI_RC_BAD_CONTEXT: if context corruption is detected.
41  * @retval TSS2_FAPI_RC_BAD_PATH: if entityPath does not map to a FAPI entity.
42  * @retval TSS2_FAPI_RC_BAD_SEQUENCE: if the context has an asynchronous
43  *         operation already pending.
44  * @retval TSS2_FAPI_RC_IO_ERROR: if the data cannot be saved.
45  * @retval TSS2_FAPI_RC_MEMORY: if the FAPI cannot allocate enough memory for
46  *         internal operations or return parameters.
47  * @retval TSS2_FAPI_RC_NO_TPM if FAPI was initialized in no-TPM-mode via its
48  *         config file.
49  * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
50  *         the function.
51  * @retval TSS2_FAPI_RC_PATH_NOT_FOUND if a FAPI object path was not found
52  *         during authorization.
53  * @retval TSS2_FAPI_RC_KEY_NOT_FOUND if a key was not found.
54  * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
55  *         this function needs to be called again.
56  * @retval TSS2_FAPI_RC_GENERAL_FAILURE if an internal error occurred.
57  * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN if a required authorization callback
58  *         is not set.
59  * @retval TSS2_FAPI_RC_AUTHORIZATION_FAILED if the authorization attempt fails.
60  * @retval TSS2_FAPI_RC_POLICY_UNKNOWN if policy search for a certain policy digest
61  *         was not successful.
62  * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
63  */
64 TSS2_RC
Fapi_ChangeAuth(FAPI_CONTEXT * context,char const * entityPath,char const * authValue)65 Fapi_ChangeAuth(
66     FAPI_CONTEXT *context,
67     char   const *entityPath,
68     char   const *authValue)
69 {
70     LOG_TRACE("called for context:%p", context);
71 
72     TSS2_RC r, r2;
73 
74     /* Check for NULL parameters */
75     check_not_null(context);
76     check_not_null(entityPath);
77 
78     /* Check whether TCTI and ESYS are initialized */
79     return_if_null(context->esys, "Command can't be executed in none TPM mode.",
80                    TSS2_FAPI_RC_NO_TPM);
81 
82     /* If the async state automata of FAPI shall be tested, then we must not set
83        the timeouts of ESYS to blocking mode.
84        During testing, the mssim tcti will ensure multiple re-invocations.
85        Usually however the synchronous invocations of FAPI shall instruct ESYS
86        to block until a result is available. */
87 #ifndef TEST_FAPI_ASYNC
88     r = Esys_SetTimeout(context->esys, TSS2_TCTI_TIMEOUT_BLOCK);
89     return_if_error_reset_state(r, "Set Timeout to blocking");
90 #endif /* TEST_FAPI_ASYNC */
91 
92     r = Fapi_ChangeAuth_Async(context, entityPath, authValue);
93     return_if_error_reset_state(r, "Entity_ChangeAuth");
94 
95     do {
96         /* We wait for file I/O to be ready if the FAPI state automata
97            are in a file I/O state. */
98         r = ifapi_io_poll(&context->io);
99         return_if_error(r, "Something went wrong with IO polling");
100 
101         /* Repeatedly call the finish function, until FAPI has transitioned
102            through all execution stages / states of this invocation. */
103         r = Fapi_ChangeAuth_Finish(context);
104     } while ((r & ~TSS2_RC_LAYER_MASK) == TSS2_BASE_RC_TRY_AGAIN);
105 
106     /* Reset the ESYS timeout to non-blocking, immediate response. */
107     r2 = Esys_SetTimeout(context->esys, 0);
108     return_if_error(r2, "Set Timeout to non-blocking");
109 
110     return_if_error_reset_state(r, "Entity_ChangeAuth");
111 
112     LOG_TRACE("finished");
113     return TSS2_RC_SUCCESS;
114 }
115 
116 /** Asynchronous function for Fapi_ChangeAuth
117  *
118  * Changes the Authorization data of an entity found at keyPath. The parameter
119  * authValue is a 0-terminated UTF-8 encoded password.
120  * If it is longer than the digest size of the entity's nameAlg, it will be
121  * hashed according the the TPM specification part 1, rev 138, section 19.6.4.3.
122  *
123  * Call Fapi_ChangeAuth_Finish to finish the execution of this command.
124 
125  * @param[in,out] context The FAPI_CONTEXT
126  * @param[in] entityPath The path to the entity to modify
127  * @param[in] authValue The new 0-terminated password to set for the entity.
128  *        May be NULL
129  *
130  * @retval TSS2_RC_SUCCESS: if the function call was a success.
131  * @retval TSS2_FAPI_RC_BAD_REFERENCE: if context or entityPath is NULL.
132  * @retval TSS2_FAPI_RC_BAD_CONTEXT: if context corruption is detected.
133  * @retval TSS2_FAPI_RC_BAD_PATH: if entityPath does not map to a FAPI entity.
134  * @retval TSS2_FAPI_RC_BAD_SEQUENCE: if the context has an asynchronous
135  *         operation already pending.
136  * @retval TSS2_FAPI_RC_IO_ERROR: if the data cannot be saved.
137  * @retval TSS2_FAPI_RC_MEMORY: if the FAPI cannot allocate enough memory for
138  *         internal operations or return parameters.
139  * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
140  *         the function.
141  * @retval TSS2_FAPI_RC_NO_TPM if FAPI was initialized in no-TPM-mode via its
142  *         config file.
143  * @retval TSS2_FAPI_RC_PATH_NOT_FOUND if a FAPI object path was not found
144  *         during authorization.
145  * @retval TSS2_FAPI_RC_KEY_NOT_FOUND if a key was not found.
146  */
147 TSS2_RC
Fapi_ChangeAuth_Async(FAPI_CONTEXT * context,char const * entityPath,char const * authValue)148 Fapi_ChangeAuth_Async(
149     FAPI_CONTEXT  *context,
150     char    const *entityPath,
151     char    const *authValue)
152 {
153     LOG_TRACE("called for context:%p", context);
154     LOG_TRACE("entityPath: %s", entityPath);
155     LOG_TRACE("authValue: %s", authValue);
156 
157     TSS2_RC r;
158 
159     /* Check for NULL parameters */
160     check_not_null(context);
161     check_not_null(entityPath);
162 
163     /* Helpful pointer aliases */
164     IFAPI_Entity_ChangeAuth * command = &(context->cmd.Entity_ChangeAuth);
165 
166     /* Reset all context-internal session state information. */
167     r = ifapi_session_init(context);
168     return_if_error(r, "Initialize Entity_ChangeAuth");
169 
170     /* Copy parameters to context for use during _Finish. */
171     context->loadKey.parent_handle = ESYS_TR_NONE;
172     command->handle = ESYS_TR_NONE;
173     memset(&command->object, 0, sizeof(IFAPI_OBJECT));
174     strdup_check(command->entityPath, entityPath, r, error_cleanup);
175     if (authValue != NULL) {
176         strdup_check(command->authValue, authValue, r, error_cleanup);
177     } else {
178         strdup_check(command->authValue, "", r, error_cleanup);
179     }
180     command->handle = ESYS_TR_NONE;
181 
182     /* Get a session for further authorizing and integrity checking the
183        subsequent ChangeAuth calls. */
184     r = ifapi_get_sessions_async(context,
185                                  IFAPI_SESSION_GENEK | IFAPI_SESSION1,
186                                  TPMA_SESSION_DECRYPT, 0);
187     goto_if_error_reset_state(r, "Create sessions", error_cleanup);
188 
189     /* Copy new auth value to appropriate structure in context */
190     if (command->authValue) {
191         if (strlen(command->authValue) > sizeof(TPMU_HA)) {
192             LOG_ERROR("authValue to big. (Should be <= %zu", sizeof(TPMU_HA));
193             r = TSS2_FAPI_RC_BAD_VALUE;
194             goto error_cleanup;
195         }
196 
197         command->newAuthValue.size =
198             strlen(command->authValue);
199         memcpy(&command->newAuthValue.buffer[0],
200                command->authValue,
201                command->newAuthValue.size);
202     } else {
203         command->newAuthValue.size = 0;
204     }
205 
206     /* Initialize the context state for this operation. */
207     context->state = ENTITY_CHANGE_AUTH_WAIT_FOR_SESSION;
208 
209     LOG_TRACE("finished");
210     return TSS2_RC_SUCCESS;
211 
212 error_cleanup:
213     /* Cleanup duplicated input parameters that were copied before. */
214     SAFE_FREE(command->entityPath);
215     SAFE_FREE(command->authValue);
216     return r;
217 }
218 
219 /** Asynchronous finish function for Fapi_ChangeAuth
220  *
221  * This function should be called after a previous Fapi_ChangeAuth_Async.
222  *
223  * @param[in,out] context The FAPI_CONTEXT
224  *
225  * @retval TSS2_RC_SUCCESS: if the function call was a success.
226  * @retval TSS2_FAPI_RC_BAD_REFERENCE: if context is NULL.
227  * @retval TSS2_FAPI_RC_BAD_CONTEXT: if context corruption is detected.
228  * @retval TSS2_FAPI_RC_BAD_SEQUENCE: if the context has an asynchronous
229  *         operation already pending.
230  * @retval TSS2_FAPI_RC_IO_ERROR: if the data cannot be saved.
231  * @retval TSS2_FAPI_RC_MEMORY: if the FAPI cannot allocate enough memory for
232  *         internal operations or return parameters.
233  * @retval TSS2_FAPI_RC_TRY_AGAIN: if the asynchronous operation is not yet
234  *         complete. Call this function again later.
235  * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
236  *         the function.
237  * @retval TSS2_FAPI_RC_GENERAL_FAILURE if an internal error occurred.
238  * @retval TSS2_FAPI_RC_PATH_NOT_FOUND if a FAPI object path was not found
239  *         during authorization.
240  * @retval TSS2_FAPI_RC_KEY_NOT_FOUND if a key was not found.
241  * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN if a required authorization callback
242  *         is not set.
243  * @retval TSS2_FAPI_RC_AUTHORIZATION_FAILED if the authorization attempt fails.
244  * @retval TSS2_FAPI_RC_POLICY_UNKNOWN if policy search for a certain policy digest
245  *         was not successful.
246  * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
247  */
248 TSS2_RC
Fapi_ChangeAuth_Finish(FAPI_CONTEXT * context)249 Fapi_ChangeAuth_Finish(
250     FAPI_CONTEXT *context)
251 {
252     LOG_TRACE("called for context:%p", context);
253 
254     TSS2_RC r;
255     ESYS_TR auth_session;
256 
257     /* Check for NULL parameters */
258     check_not_null(context);
259 
260     /* Helpful pointers */
261     IFAPI_Entity_ChangeAuth * command = &(context->cmd.Entity_ChangeAuth);
262     IFAPI_OBJECT * object = &command->object;
263     const IFAPI_PROFILE *profile;
264 
265     switch (context->state) {
266         statecase(context->state, ENTITY_CHANGE_AUTH_WAIT_FOR_SESSION)
267             /* Retrieve profile information for subsequent commands. */
268             r = ifapi_profiles_get(&context->profiles, command->entityPath,
269                     &profile);
270             goto_if_error_reset_state(r, " FAPI create session", error_cleanup);
271 
272             /* Finish starting the session establishment. */
273             r = ifapi_get_sessions_finish(context, profile, profile->nameAlg);
274             return_try_again(r);
275 
276             goto_if_error_reset_state(r, " FAPI create session", error_cleanup);
277 
278             /* If the referenced entity is an NV-Index, load its metadata from
279                the keystore. */
280             if (ifapi_path_type_p(command->entityPath,
281                     IFAPI_NV_PATH)) {
282                 r = ifapi_keystore_load_async(&context->keystore, &context->io,
283                         command->entityPath);
284                 return_if_error_reset_state(r, "Could not open: %s",
285                         command->entityPath);
286 
287                 /* Set the correct re-entry state for handling NV-index entities. */
288                 context->state = ENTITY_CHANGE_AUTH_WAIT_FOR_NV_READ;
289                 return TSS2_FAPI_RC_TRY_AGAIN;
290             }
291 
292             /* Check if the referenced entity is a hierarchy. */
293             command->hierarchy_handle =
294                 ifapi_get_hierary_handle(command->entityPath);
295 
296             if (command->hierarchy_handle) {
297                 /* Set the correct re-entry state for handling hierarchies. */
298                 context->state = ENTITY_CHANGE_AUTH_HIERARCHY_READ;
299                 /* Load the hierarchy's metadata from the keystore. */
300                 r = ifapi_keystore_load_async(&context->keystore, &context->io,
301                         command->entityPath);
302                 return_if_error_reset_state(r, "Could not open: %s",
303                         command->entityPath);
304 
305                 return TSS2_FAPI_RC_TRY_AGAIN;
306             }
307 
308             /* At this point, the referenced entity must be a key.
309                Load the key's metadata from the keystore. */
310             r = ifapi_load_keys_async(context, command->entityPath);
311             goto_if_error(r, "Load keys.", error_cleanup);
312 
313             fallthrough;
314 
315         statecase(context->state, ENTITY_CHANGE_AUTH_WAIT_FOR_KEY)
316             r = ifapi_load_keys_finish(context, IFAPI_NOT_FLUSH_PARENT,
317                     &command->handle,
318                     &command->key_object);
319             return_try_again(r);
320 
321             fallthrough;
322 
323         statecase(context->state, ENTITY_CHANGE_AUTH_WAIT_FOR_KEY_AUTH)
324             /* Authorize the object with the old authorization */
325             object = command->key_object;
326             r = ifapi_authorize_object(context, object, &auth_session);
327             return_try_again(r);
328             goto_if_error_reset_state(r, "Authorize key.", error_cleanup);
329 
330             /* Call to change the Authorization of the key. */
331             r = Esys_ObjectChangeAuth_Async(context->esys,
332                     command->handle,
333                     context->loadKey.parent_handle,
334                     auth_session,
335                     ESYS_TR_NONE, ESYS_TR_NONE,
336                     &command->newAuthValue);
337             goto_if_error(r, "Error: Sign", error_cleanup);
338 
339             fallthrough;
340 
341         statecase(context->state, ENTITY_CHANGE_AUTH_AUTH_SENT)
342             r = Esys_ObjectChangeAuth_Finish(context->esys,
343                     &command->newPrivate);
344             return_try_again(r);
345 
346             goto_if_error(r, "Error: Entity ChangeAuth", error_cleanup);
347 
348             object = command->key_object;
349             object->misc.key.private.size = command->newPrivate->size;
350 
351             /* Store the new private key blob to the context to be stored to
352                the keystore. */
353             free(object->misc.key.private.buffer);
354             object->misc.key.private.buffer = malloc(object->misc.key.private.size);
355             goto_if_null2(object->misc.key.private.buffer, "Out of memory.",
356                     r, TSS2_FAPI_RC_MEMORY, error_cleanup);
357 
358             memcpy(object->misc.key.private.buffer,
359                     &command->newPrivate->buffer[0],
360                     object->misc.key.private.size);
361             free(command->newPrivate);
362 
363             /* Flush the key with the old authorization form the TPM. */
364             r = Esys_FlushContext_Async(context->esys,
365                     command->handle);
366             goto_if_error(r, "Error: FlushContext", error_cleanup);
367 
368             command->handle = ESYS_TR_NONE;
369 
370             fallthrough;
371 
372         statecase(context->state, ENTITY_CHANGE_AUTH_WAIT_FOR_FLUSH)
373             r = Esys_FlushContext_Finish(context->esys);
374             return_try_again(r);
375 
376             goto_if_error(r, "Error: ObjectChangeAuth", error_cleanup);
377 
378             /* Flush the parent key as well. */
379             if (!context->loadKey.parent_handle_persistent
380                     && context->loadKey.parent_handle != ESYS_TR_NONE) {
381                 r = Esys_FlushContext_Async(context->esys, context->loadKey.parent_handle);
382                 goto_if_error(r, "Flush parent", error_cleanup);
383 
384                 context->loadKey.parent_handle = ESYS_TR_NONE;
385                 return TSS2_FAPI_RC_TRY_AGAIN;
386             }
387 
388             /* Store information about whether the new authorization is an
389                empty authorization or an actual password. */
390             object = command->key_object;
391 
392             if (strlen(command->authValue) > 0)
393                 object->misc.key.with_auth = TPM2_YES;
394             else
395                 object->misc.key.with_auth = TPM2_NO;
396             fallthrough;
397 
398         statecase(context->state, ENTITY_CHANGE_AUTH_WRITE_PREPARE)
399             /* Perform serialization of the esys object if necessary */
400             r = ifapi_esys_serialize_object(context->esys, object);
401             goto_if_error(r, "Prepare serialization", error_cleanup);
402 
403             /* Start writing the NV object to the key store */
404             r = ifapi_keystore_store_async(&context->keystore, &context->io,
405                     command->entityPath,
406                     object);
407             goto_if_error_reset_state(r, "Could not open: %sh", error_cleanup,
408                     command->entityPath);
409             fallthrough;
410 
411         statecase(context->state, ENTITY_CHANGE_AUTH_WRITE)
412             /* Finish writing the object to the key store */
413             r = ifapi_keystore_store_finish(&context->keystore, &context->io);
414             return_try_again(r);
415             return_if_error_reset_state(r, "write_finish failed");
416 
417             fallthrough;
418 
419         statecase(context->state, ENTITY_CHANGE_AUTH_CLEANUP)
420             /* Clean up the session information and reset the state and be done. */
421             r = ifapi_cleanup_session(context);
422             try_again_or_error_goto(r, "Cleanup", error_cleanup);
423 
424             context->state = _FAPI_STATE_INIT;
425             LOG_TRACE("success");
426             break;
427 
428         statecase(context->state, ENTITY_CHANGE_AUTH_WAIT_FOR_NV_READ)
429             /* The is the re-entry in case of an NV-index as referenced object.
430                All code between the check for the entity type above and this
431                place was skipped in case of an NV-index. */
432             r = ifapi_keystore_load_finish(&context->keystore, &context->io,
433                     &command->object);
434             return_try_again(r);
435             return_if_error_reset_state(r, "read_finish failed");
436 
437             /* Initialize the esys-object for the NV-index. */
438             r = ifapi_initialize_object(context->esys, &command->object);
439             goto_if_error_reset_state(r, "Initialize NV object", error_cleanup);
440 
441             fallthrough;
442 
443         statecase(context->state, ENTITY_CHANGE_AUTH_WAIT_FOR_NV_AUTH)
444             /* Authorize the object with with the policies
445                auth value and command code */
446             r = ifapi_authorize_object(context, object, &auth_session);
447             return_try_again(r);
448             goto_if_error(r, "Authorize NV object.", error_cleanup);
449 
450             /* Change the NV index's AuthValue. */
451             r = Esys_NV_ChangeAuth_Async(context->esys,
452                     context->nv_cmd.nv_object.handle,
453                     auth_session,
454                     ESYS_TR_NONE,
455                     ESYS_TR_NONE,
456                     &command->newAuthValue);
457             goto_if_error(r, "Error: NV_ChangeAuth", error_cleanup);
458 
459             fallthrough;
460 
461         statecase(context->state, ENTITY_CHANGE_AUTH_WAIT_FOR_NV_CHANGE_AUTH)
462             r = Esys_NV_ChangeAuth_Finish(context->esys);
463             return_try_again(r);
464 
465             goto_if_error(r, "Error: Entity ChangeAuth", error_cleanup);
466 
467             /* Update the information about whether the new Auth is an empty
468                authorization or an actual password. */
469             if (strlen(command->authValue) > 0)
470                 object->misc.nv.with_auth = TPM2_YES;
471             else
472                 object->misc.nv.with_auth = TPM2_NO;
473 
474             /* Jump over to the AUTH_WRITE_PREPARE state for storing the
475                new metadata to the keystore. */
476             context->state = ENTITY_CHANGE_AUTH_WRITE_PREPARE;
477             return TSS2_FAPI_RC_TRY_AGAIN;
478 
479         statecase(context->state, ENTITY_CHANGE_AUTH_HIERARCHY_READ)
480             /* This is the re-entry point if the referenced entity is a
481                hierarchy. All code between the check for the entity type
482                and this place is skipped in case of a hierarchy. */
483             r = ifapi_keystore_load_finish(&context->keystore, &context->io, object);
484             return_try_again(r);
485             return_if_error_reset_state(r, "read_finish failed");
486 
487             /* Initialize the esys object for the hierarhcy. */
488             r = ifapi_initialize_object(context->esys, &command->object);
489             goto_if_error_reset_state(r, "Initialize NV object", error_cleanup);
490 
491             command->object.handle
492                 = command->hierarchy_handle;
493 
494             fallthrough;
495 
496         statecase(context->state, ENTITY_CHANGE_AUTH_HIERARCHY_AUTHORIZE)
497             /* Authorize against the hierarhcy. */
498             r = ifapi_authorize_object(context, &command->object, &auth_session);
499             return_try_again(r);
500             goto_if_error(r, "Authorize hierarchy.", error_cleanup);
501 
502             fallthrough;
503 
504         statecase(context->state, ENTITY_CHANGE_AUTH_HIERARCHY_CHANGE_AUTH)
505             /* Change the hierarchy authorization. */
506             r = ifapi_change_auth_hierarchy(context,
507                     command->hierarchy_handle,
508                     &command->object,
509                     &command->newAuthValue);
510             return_try_again(r);
511             goto_if_error(r, "Change auth hierarchy.", error_cleanup);
512 
513             /* Jump over to the AUTH_WRITE_PREPARE state for storing the
514                new metadata to the keystore. */
515             context->state = ENTITY_CHANGE_AUTH_WRITE_PREPARE;
516             return TSS2_FAPI_RC_TRY_AGAIN;
517 
518         statecasedefault(context->state);
519     }
520 
521 error_cleanup:
522     /* In error cases object might not be flushed. */
523     if (context->loadKey.parent_handle != ESYS_TR_NONE)
524         Esys_FlushContext(context->esys, context->loadKey.parent_handle);
525     if (command->handle != ESYS_TR_NONE)
526         Esys_FlushContext(context->esys, command->handle);
527     ifapi_session_clean(context);
528     ifapi_cleanup_ifapi_object(object);
529     ifapi_cleanup_ifapi_object(context->loadKey.key_object);
530     ifapi_cleanup_ifapi_object(&context->loadKey.auth_object);
531     ifapi_cleanup_ifapi_object(&context->createPrimary.pkey_object);
532     ifapi_cleanup_ifapi_object(command->key_object);
533     SAFE_FREE(command->entityPath);
534     SAFE_FREE(command->authValue);
535     LOG_TRACE("finished");
536     return r;
537 }
538