1 /*===========================================================================
2 *
3 *                            PUBLIC DOMAIN NOTICE
4 *               National Center for Biotechnology Information
5 *
6 *  This software/database is a "United States Government Work" under the
7 *  terms of the United States Copyright Act.  It was written as part of
8 *  the author's official duties as a United States Government employee and
9 *  thus cannot be copyrighted.  This software/database is freely available
10 *  to the public for use. The National Library of Medicine and the U.S.
11 *  Government have not placed any restriction on its use or reproduction.
12 *
13 *  Although all reasonable efforts have been taken to ensure the accuracy
14 *  and reliability of the software and data, the NLM and the U.S.
15 *  Government do not and cannot warrant the performance or results that
16 *  may be obtained by using this software or data. The NLM and the U.S.
17 *  Government disclaim all warranties, express or implied, including
18 *  warranties of performance, merchantability or fitness for any particular
19 *  purpose.
20 *
21 *  Please cite the author in any work or product based on this material.
22 *
23 * ===========================================================================
24 *
25 */
26 
27 struct AWS;
28 #define CLOUD_IMPL struct AWS
29 
30 #include <cloud/extern.h>
31 #include <cloud/impl.h>
32 
33 #include <cloud/aws.h>
34 
35 #include <klib/debug.h> /* DBGMSG */
36 #include <klib/rc.h>
37 #include <klib/status.h>
38 #include <klib/strings.h> /* ENV_MAGIC_CE_TOKEN */
39 #include <klib/text.h>
40 #include <klib/printf.h>
41 
42 #include <kfg/config.h>
43 #include <kfg/kfg-priv.h>
44 #include <kfg/properties.h>
45 #include <kns/endpoint.h>
46 #include <kns/socket.h>
47 #include <kns/http.h>
48 #include <kfs/directory.h>
49 #include <kfs/file.h>
50 
51 #include <assert.h>
52 
53 #include "aws-priv.h" /* AWSDoAuthentication */
54 #include "cloud-cmn.h" /* KNSManager_Read */
55 #include "cloud-priv.h"
56 
57 static rc_t PopulateCredentials ( AWS * self );
58 
59 /* Destroy
60  */
61 static
AWSDestroy(AWS * self)62 rc_t CC AWSDestroy ( AWS * self )
63 {
64     free ( self -> region );
65     free ( self -> output );
66     free ( self -> access_key_id );
67     free ( self -> secret_access_key );
68     free ( self -> profile );
69     return CloudWhack ( & self -> dad );
70 }
71 
KNSManager_GetAWSLocation(const KNSManager * self,char * buffer,size_t bsize)72 static rc_t KNSManager_GetAWSLocation(
73     const KNSManager * self, char *buffer, size_t bsize)
74 {
75     return KNSManager_Read(self, buffer, bsize,
76         "http://169.254.169.254/latest/meta-data/placement/availability-zone",
77         NULL, NULL);
78 }
79 
80 /* envCE
81  * Get Compute Environment Token from environment variable
82  *
83  * NB. this is a one-shot function, but atomic is not important
84  */
envCE()85 static char const *envCE()
86 {
87     static bool firstTime = true;
88     char const *const env = firstTime ? getenv(ENV_MAGIC_CE_TOKEN) : NULL;
89     firstTime = false;
90     if ( env != NULL && *env != 0 )
91         DBGMSG(DBG_VFS, DBG_FLAG(DBG_VFS_PATH),
92                ("Got location from environment\n"));
93     return env;
94 }
95 
96 /* readCE
97  * Get Compute Environment Token by reading from provider network
98  */
readCE(AWS const * const self,size_t size,char location[])99 static rc_t readCE(AWS const *const self, size_t size, char location[])
100 {
101     char document[4096] = "";
102     char pkcs7[4096] = "";
103     rc_t rc;
104 
105     DBGMSG(DBG_VFS, DBG_FLAG(DBG_VFS_PATH),
106            ("Reading location from provider\n"));
107     rc = KNSManager_Read(self->dad.kns, document, sizeof document,
108                  "http://169.254.169.254/latest/dynamic/instance-identity/document",
109                  NULL, NULL);
110     if (rc) return rc;
111 
112     rc = KNSManager_Read(self->dad.kns, pkcs7, sizeof pkcs7,
113                  "http://169.254.169.254/latest/dynamic/instance-identity/pkcs7",
114                  NULL, NULL);
115     if (rc) return rc;
116 
117     return MakeLocation(pkcs7, document, location, size);
118 }
119 
120 /* MakeComputeEnvironmentToken
121  *  contact cloud provider to get proof of execution environment in form of a token
122  */
123 static
AWSMakeComputeEnvironmentToken(const AWS * self,const String ** ce_token)124 rc_t CC AWSMakeComputeEnvironmentToken ( const AWS * self, const String ** ce_token )
125 {
126     assert(self);
127 
128     if (!self->dad.user_agrees_to_reveal_instance_identity)
129         return RC(rcCloud, rcProvider, rcIdentifying,
130                   rcCondition, rcUnauthorized);
131     else {
132         char const *const env = envCE();
133         char location[4096] = "";
134         rc_t const rc = env == NULL ? readCE(self, sizeof(location), location) : 0;
135         if (rc == 0) {
136             String s;
137             StringInitCString(&s, env != NULL ? env : location);
138             return StringCopy(ce_token, &s);
139         }
140         return rc;
141     }
142 }
143 
144 /* AwsGetLocation
145  */
AwsGetLocation(const AWS * self,const String ** location)146 static rc_t AwsGetLocation(const AWS * self, const String ** location)
147 {
148     rc_t rc = 0;
149 
150     char zone[64] = "";
151     char buffer[64] = "";
152 
153     assert(self);
154 
155     rc = KNSManager_GetAWSLocation(self->dad.kns, zone, sizeof zone);
156 
157     if (rc == 0)
158         rc = string_printf(buffer, sizeof buffer, NULL, "s3.%s", zone);
159 
160     if (rc == 0) {
161         String s;
162         StringInitCString(&s, buffer);
163         rc = StringCopy(location, &s);
164     }
165 
166     return rc;
167 }
168 
169 
170 /* AddComputeEnvironmentTokenForSigner
171  *  prepare a request object with a compute environment token
172  *  for use by an SDL-associated "signer" service
173  */
174 static
AWSAddComputeEnvironmentTokenForSigner(const AWS * self,KClientHttpRequest * req)175 rc_t CC AWSAddComputeEnvironmentTokenForSigner ( const AWS * self, KClientHttpRequest * req )
176 {
177     const String * ce_token = NULL;
178     rc_t rc = AWSMakeComputeEnvironmentToken(self, &ce_token);
179 
180     if (rc == 0) {
181         rc = KHttpRequestAddPostParam(req, "ident=%S", ce_token);
182         StringWhack(ce_token);
183     }
184 
185     return rc;
186 }
187 
188 /* AddAuthentication
189  *  prepare a request object with credentials for authentication
190  */
AWSAddAuthentication(const AWS * self,KClientHttpRequest * req,const char * http_method)191 static rc_t CC AWSAddAuthentication ( const AWS * self,
192     KClientHttpRequest * req, const char * http_method )
193 {
194     return AWSDoAuthentication(self, req, http_method, false);
195 }
196 
197 /* AddUserPaysCredentials
198  *  prepare a request object with credentials for user-pays
199  */
200 static
AWSAddUserPaysCredentials(const AWS * self,KClientHttpRequest * req,const char * http_method)201 rc_t CC AWSAddUserPaysCredentials ( const AWS * self, KClientHttpRequest * req, const char * http_method )
202 {
203     assert(self);
204 
205     if (self->dad.user_agrees_to_pay)
206         return AWSDoAuthentication(self, req, http_method, true);
207     else
208         return 0;
209 }
210 
211 static Cloud_vt_v1 AWS_vt_v1 =
212 {
213     1, 0,
214 
215     AWSDestroy,
216     AWSMakeComputeEnvironmentToken,
217     AwsGetLocation,
218     AWSAddComputeEnvironmentTokenForSigner,
219     AWSAddAuthentication,
220     AWSAddUserPaysCredentials
221 };
222 
223 
224 /* MakeAWS
225  *  make an instance of an AWS cloud interface
226  */
CloudMgrMakeAWS(const CloudMgr * self,AWS ** p_aws)227 LIB_EXPORT rc_t CC CloudMgrMakeAWS ( const CloudMgr * self, AWS ** p_aws )
228 {
229     rc_t rc;
230 //TODO: check self, aws
231     AWS * aws = calloc ( 1, sizeof * aws );
232     if ( aws == NULL )
233     {
234         rc = RC ( rcCloud, rcMgr, rcAllocating, rcMemory, rcExhausted );
235     }
236     else
237     {
238         /* capture from self->kfg */
239         bool user_agrees_to_pay = false;
240         bool user_agrees_to_reveal_instance_identity = false;
241         if (self != NULL) {
242             KConfig_Get_User_Accept_Aws_Charges(self->kfg,
243                 &user_agrees_to_pay);
244             KConfig_Get_Report_Cloud_Instance_Identity(self->kfg,
245                 &user_agrees_to_reveal_instance_identity);
246         }
247 
248         rc = CloudInit ( & aws -> dad, ( const Cloud_vt * ) & AWS_vt_v1, "AWS", self -> kns, user_agrees_to_pay,
249             user_agrees_to_reveal_instance_identity );
250         if ( rc == 0 )
251         {
252             rc = PopulateCredentials( aws );
253             if ( rc == 0 )
254             {
255                 * p_aws = aws;
256             }
257             else
258             {
259                 CloudRelease( & aws -> dad );
260             }
261         }
262         else
263         {
264             free ( aws );
265         }
266 
267     }
268 
269     return rc;
270 }
271 
272 /* AddRef
273  * Release
274  */
AWSAddRef(const AWS * self)275 LIB_EXPORT rc_t CC AWSAddRef ( const AWS * self )
276 {
277     return CloudAddRef ( & self -> dad );
278 }
279 
AWSRelease(const AWS * self)280 LIB_EXPORT rc_t CC AWSRelease ( const AWS * self )
281 {
282     return CloudRelease ( & self -> dad );
283 }
284 
285 /* Cast
286  *  cast from a Cloud to an AWS type or vice versa
287  *  allows us to apply cloud-specific interface to cloud object
288  *
289  *  returns a new reference, meaning the "self" must still be released
290  */
AWSToCloud(const AWS * cself,Cloud ** cloud)291 LIB_EXPORT rc_t CC AWSToCloud ( const AWS * cself, Cloud ** cloud )
292 {
293     rc_t rc;
294 
295     if ( cloud == NULL )
296         rc = RC ( rcCloud, rcProvider, rcCasting, rcParam, rcNull );
297     else
298     {
299         if ( cself == NULL )
300             rc = 0;
301         else
302         {
303             AWS * self = ( AWS * ) cself;
304 
305             rc = CloudAddRef ( & self -> dad );
306             if ( rc == 0 )
307             {
308                 * cloud = & self -> dad;
309                 return 0;
310             }
311         }
312 
313         * cloud = NULL;
314     }
315 
316     return rc;
317 }
318 
319 /* WithinAWS
320  *  answers true if within AWS
321  */
CloudMgrWithinAWS(const CloudMgr * self)322 bool CloudMgrWithinAWS ( const CloudMgr * self )
323 {
324 #if 0
325     KEndPoint ep;
326     /* describe address 169.254.169.254 on port 80 */
327     KNSManagerInitIPv4Endpoint ( self -> kns, & ep,
328                                  ( ( 169 << 24 ) |
329                                    ( 254 << 16 ) |
330                                    ( 169 <<  8 ) |
331                                    ( 254 <<  0 ) ), 80 );
332 
333 #endif
334 
335     char buffer[999] = "";
336 
337     assert(self);
338 
339     return KNSManager_GetAWSLocation(self->kns, buffer, sizeof buffer) == 0;
340 }
341 
342 /*** Finding/loading credentials  */
343 
aws_extract_key_value_pair(const String * source,String * key,String * val)344 static rc_t aws_extract_key_value_pair (
345     const String *source, String *key, String *val )
346 {
347     String k, v;
348     const char *start = source->addr;
349     const char *end = start + source->size;
350 
351     char *eql = string_chr ( start, source->size, '=' );
352     if ( eql == NULL )
353         return RC (rcCloud, rcChar, rcSearching, rcFormat, rcInvalid );
354 
355     /* key */
356     StringInit ( &k, start, eql - start, string_len ( start, eql - start ) );
357     StringTrim ( &k, key );
358 
359     start = eql + 1;
360 
361     /* value */
362     StringInit ( &v, start, end - start, string_len ( start, end - start ) );
363     StringTrim ( &v, val );
364 
365     return 0;
366 }
367 
368 /*TODO: improve error handling (at least report) */
aws_parse_file(AWS * self,const KFile * cred_file)369 static void aws_parse_file ( AWS * self, const KFile *cred_file )
370 {
371     rc_t rc;
372     size_t buf_size;
373     size_t num_read;
374 
375     char * buffer;
376     uint64_t file_size;
377 
378     assert ( self != NULL );
379     assert ( self -> profile != NULL );
380 
381     rc = KFileSize ( cred_file, &file_size );
382     if ( rc != 0 )
383         return;
384 
385     buf_size = ( size_t ) file_size;
386     if ( sizeof buf_size < sizeof file_size && ( uint64_t ) buf_size != file_size )
387         return;
388 
389     buffer = malloc ( buf_size );
390     rc = KFileReadAll ( cred_file, 0, buffer, buf_size, &num_read );
391 
392     if ( rc != 0 )
393     {
394         free ( buffer );
395     }
396 	else
397 	{
398 		String bracket;
399 		String profile;
400 		const String *temp1;
401 		const String *brack_profile;
402 
403 		const char *start = buffer;
404 		const char *end = start + buf_size;
405 		const char *sep = start;
406 		bool in_profile = false;
407 
408 		CONST_STRING ( &bracket, "[" );
409 
410 		StringInitCString( & profile, self -> profile );
411 
412 		StringConcat ( &temp1, &bracket, &profile );
413 		CONST_STRING ( &bracket, "]" );
414 		StringConcat ( &brack_profile, temp1, &bracket );
415 
416 		--sep;
417 
418 
419 		for ( ; start < end; start = sep + 1 ) {
420 			rc_t rc;
421 			String string, trim;
422 			String key, value;
423 			String access_key_id, secret_access_key;
424 			String region, output;
425 
426 			sep = string_chr ( start, end - start, '\n' );
427 			if ( sep == NULL ) sep = (char *)end;
428 
429 			StringInit (
430 				&string, start, sep - start, string_len ( start, sep - start ) );
431 
432 			StringTrim ( &string, &trim );
433 			/* check for empty line and skip */
434 			if ( StringLength ( &trim ) == 0 ) continue;
435 			/*
436 					{
437 						char *p = string_dup ( trim.addr, StringLength ( &trim ) );
438 						fprintf ( stderr, "line: %s\n", p );
439 						free ( p );
440 					}
441 			*/
442 			/* check for comment line and skip */
443 			if ( trim.addr[0] == '#' ) continue;
444 
445 			/* check for [profile] line */
446 			if ( trim.addr[0] == '[' ) {
447 				in_profile = StringEqual ( &trim, brack_profile );
448 				continue;
449 			}
450 
451 			if ( !in_profile ) continue;
452 
453 			/* check for key/value pairs and skip if none found */
454 			rc = aws_extract_key_value_pair ( &trim, &key, &value );
455 			if ( rc != 0 ) continue;
456 
457 			/* now check keys we are looking for and populate the node*/
458 
459 			CONST_STRING ( &access_key_id, "aws_access_key_id" );
460 			CONST_STRING ( &secret_access_key, "aws_secret_access_key" );
461 
462 			if ( StringCaseEqual ( &key, &access_key_id ) ) {
463 				free ( self -> access_key_id );
464 				self -> access_key_id = string_dup ( value . addr, value . size );
465 			}
466 
467 			if ( StringCaseEqual ( &key, &secret_access_key ) ) {
468 				free ( self -> secret_access_key );
469 				self -> secret_access_key = string_dup ( value . addr, value . size );
470 			}
471 
472 			CONST_STRING ( &region, "region" );
473 			CONST_STRING ( &output, "output" );
474 
475 			if ( StringCaseEqual ( &key, &region ) ) {
476 				free ( self -> region );
477 				self -> region = string_dup ( value . addr, value . size );
478 			}
479 			if ( StringCaseEqual ( &key, &output ) ) {
480 				free ( self -> output );
481 				self -> output = string_dup ( value . addr, value . size );
482 			}
483 
484 		}
485 
486 		StringWhack ( temp1 );
487 		StringWhack ( brack_profile );
488 		free ( buffer );
489 	}
490 }
491 
make_home_node(char * path,size_t path_size)492 static void make_home_node ( char *path, size_t path_size )
493 {
494     size_t num_read;
495     const char *home;
496 
497     KConfig * kfg;
498     rc_t rc = KConfigMakeLocal( & kfg, NULL );
499     if ( rc == 0 )
500     {
501         const KConfigNode *home_node;
502 
503         /* Check to see if home node exists */
504         rc = KConfigOpenNodeRead ( kfg, &home_node, "HOME" );
505         if ( home_node == NULL ) {
506             /* just grab the HOME env variable */
507             home = getenv ( "HOME" );
508             if ( home != NULL && *home != 0 ) {
509                 num_read = string_copy_measure ( path, path_size, home );
510                 if ( num_read >= path_size ) path[0] = 0;
511             }
512         } else {
513             /* if it exists check for a path */
514             rc = KConfigNodeRead ( home_node, 0, path, path_size, &num_read, NULL );
515             if ( rc != 0 ) {
516                 home = getenv ( "HOME" );
517                 if ( home != NULL && *home != 0 ) {
518                     num_read = string_copy_measure ( path, path_size, home );
519                     if ( num_read >= path_size ) path[0] = 0;
520                 }
521             }
522 
523             KConfigNodeRelease ( home_node );
524         }
525 
526         KConfigRelease ( kfg );
527     }
528 }
529 
LoadCredentials(AWS * self)530 static rc_t LoadCredentials ( AWS * self  )
531 {
532     const char *conf_env = getenv ( "AWS_CONFIG_FILE" );
533     const char *cred_env = getenv ( "AWS_SHARED_CREDENTIAL_FILE" );
534 
535 	KDirectory *wd = NULL;
536     rc_t rc = KDirectoryNativeDir ( &wd );
537     if ( rc ) return rc;
538 
539     if ( conf_env && *conf_env != 0 )
540     {
541         const KFile *cred_file = NULL;
542         rc = KDirectoryOpenFileRead ( wd, &cred_file, "%s", conf_env );
543         if ( rc == 0 )
544         {
545             aws_parse_file ( self, cred_file );
546             KFileRelease ( cred_file );
547         }
548         KDirectoryRelease ( wd );
549         return rc;
550     }
551 
552     if ( cred_env && *cred_env != 0 )
553     {
554         const KFile *cred_file = NULL;
555         rc = KDirectoryOpenFileRead ( wd, &cred_file, "%s", cred_env );
556         if ( rc == 0 )
557         {
558             aws_parse_file ( self, cred_file );
559             KFileRelease ( cred_file );
560         }
561         KDirectoryRelease ( wd );
562         return rc;
563     }
564 
565     {
566         char home[4096] = "";
567         make_home_node ( home, sizeof home );
568 
569         if ( home[0] != 0 )
570         {
571             char aws_path[4096] = "";
572             size_t num_writ = 0;
573             rc = string_printf ( aws_path, sizeof aws_path, &num_writ, "%s/.aws", home );
574             if ( rc == 0 && num_writ != 0 )
575             {
576                 const KFile *cred_file = NULL;
577                 rc = KDirectoryOpenFileRead ( wd, &cred_file, "%s%s", aws_path, "/credentials" );
578                 if ( rc == 0 )
579                 {
580                     aws_parse_file ( self, cred_file );
581                     KFileRelease ( cred_file );
582 
583                     rc = KDirectoryOpenFileRead ( wd, &cred_file, "%s%s", aws_path, "/config" );
584                     if ( rc == 0 )
585                     {
586                         aws_parse_file ( self, cred_file );
587                         KFileRelease ( cred_file );
588                     }
589                 }
590             }
591         }
592     }
593 
594     KDirectoryRelease ( wd );
595     return rc;
596 }
597 
598 //TODO: check results of strdups and string_dups
599 static
PopulateCredentials(AWS * self)600 rc_t PopulateCredentials ( AWS * self )
601 {
602     /* Check Environment first */
603     const char * aws_access_key_id = getenv ( "AWS_ACCESS_KEY_ID" );
604     const char * aws_secret_access_key = getenv ( "AWS_SECRET_ACCESS_KEY" );
605     const char * profile = getenv ( "AWS_PROFILE" );
606 
607     if ( aws_access_key_id != NULL && aws_secret_access_key != NULL
608         && strlen ( aws_access_key_id ) > 0
609         && strlen ( aws_secret_access_key ) > 0 )
610     {   /* Use environment variables */
611         self -> access_key_id = string_dup( aws_access_key_id, string_size( aws_access_key_id ) );
612         self -> secret_access_key = string_dup( aws_secret_access_key, string_size( aws_secret_access_key ) );
613         return 0;
614     }
615 
616     /* Get Profile */
617     if ( profile != NULL && *profile != 0 )
618     {
619         self -> profile = string_dup ( profile, string_size ( profile ) );
620     }
621     else
622     {
623         KConfig * kfg;
624         rc_t rc = KConfigMakeLocal( & kfg, NULL );
625 /*KConfigPrint(kfg,0);*/
626         if ( rc == 0 )
627         {
628             char buffer[4096];
629             size_t num_writ = 0;
630             rc = KConfig_Get_Aws_Profile ( kfg, buffer, sizeof ( buffer ), &num_writ );
631             if ( rc == 0 && num_writ > 0 )
632             {
633                 self -> profile = string_dup ( buffer, string_size ( buffer ) );
634             }
635             KConfigRelease( kfg );
636         }
637     }
638 
639     if ( self -> profile == NULL )
640     {
641         self -> profile = strdup ( "default" );
642     }
643 
644     /* OK if no credentials are found */
645     LoadCredentials ( self );
646     return 0;
647 }
648 
649 #if 0
650 
651     /* Check AWS_CONFIG_FILE and AWS_SHARED_CREDENTIAL_FILE, if specified check for credentials and/or profile name */
652     const char *conf_env = getenv ( "AWS_CONFIG_FILE" );
653     if ( conf_env == NULL || *conf_env == 0 )
654     {
655         conf_env = getenv ( "AWS_SHARED_CREDENTIAL_FILE" );
656     }
657     if ( conf_env && *conf_env != 0 )
658     {
659         KDirectory *wd = NULL;
660         const KFile *cred_file = NULL;
661 
662         rc = KDirectoryNativeDir ( &wd );
663         if ( rc != 0 )
664         {
665             return rc;
666         }
667 
668         rc = KDirectoryOpenFileRead ( wd, &cred_file, "%s", conf_env );
669         if ( rc == 0 )
670         {
671             aws_parse_file ( self, cred_file );
672             KFileRelease ( cred_file );
673         }
674         KDirectoryRelease ( wd );
675         if ( rc != 0 )
676         {
677             return rc;
678         }
679         if ( self -> access_key_id != NULL && self -> secret_access_key != NULL )
680         {
681             return 0;
682         }
683 
684         /* proceed to other sources */
685     }
686 
687     /* Check paths and parse */
688     char home[4096] = "";
689     make_home_node ( self, home, sizeof home );
690 
691     if ( home[0] != 0 ) {
692         char path[4096] = "";
693         size_t num_writ = 0;
694         rc = string_printf ( path, sizeof path, &num_writ, "%s/.aws", home );
695         if ( rc == 0 && num_writ != 0 ) {
696             /* Use config files */
697             if ( rc == 0 ) {
698                 rc = aws_find_nodes ( aws_node, path, &sprofile );
699                 if ( rc ) {
700                     /* OK if no .aws available */
701                     rc = 0;
702                 }
703             }
704         }
705     }
706 
707     KConfigNodeRelease ( aws_node );
708     return rc;
709 }
710 #endif
711 
CloudToAWS(const Cloud * self,AWS ** aws)712 LIB_EXPORT rc_t CC CloudToAWS ( const Cloud * self, AWS ** aws )
713 {
714     rc_t rc;
715 
716     if ( aws == NULL )
717         rc = RC ( rcCloud, rcProvider, rcCasting, rcParam, rcNull );
718     else
719     {
720         if ( self == NULL )
721             rc = 0;
722         else if ( self -> vt != ( const Cloud_vt * ) & AWS_vt_v1 )
723             rc = RC ( rcCloud, rcProvider, rcCasting, rcType, rcIncorrect );
724         else
725         {
726             rc = CloudAddRef ( self );
727             if ( rc == 0 )
728             {
729                 * aws = ( AWS * ) self;
730                 return 0;
731             }
732         }
733 
734         * aws = NULL;
735     }
736 
737     return rc;
738 }
739