674666a60a71709c8b3d0b1b51602b0b5f1a6649
[gd/samba-autobuild/.git] / source4 / libcli / clidfs.c
1 /* 
2    Unix SMB/CIFS implementation.
3    Dfs routines
4    Copyright (C) James Myers 2003
5    
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10    
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15    
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 #include "includes.h"
22
23 BOOL smbcli_client_initialize(struct smbcli_client* context,
24                            const char* sockops,
25                            char* username, char* password, char* workgroup,
26                            int flags)
27 {
28         int i;
29         for (i=0; i < DFS_MAX_CLUSTER_SIZE ; i++) {
30                 context->cli[i] = smbcli_raw_initialise();
31         }
32         context->sockops = sockops;
33         context->username = username;
34         context->password = password;
35         context->workgroup = workgroup;
36         context->connection_flags = flags;
37         if (flags & SMBCLI_FULL_CONNECTION_USE_DFS)
38                 context->use_dfs = True;
39         context->number_members = DFS_MAX_CLUSTER_SIZE; 
40         return True;
41 }
42
43 /****************************************************************************
44  Interpret a Dfs referral structure.
45  The length of the structure is returned
46  The structure of a Dfs referral depends on the info level.
47 ****************************************************************************/
48
49 static int interpret_referral(struct smbcli_state *cli,
50                                    int level,char *p,referral_info *rinfo)
51 {
52         char* q;
53         int version, size;
54
55         version = SVAL(p,0);
56         size = SVAL(p,2);
57         rinfo->server_type = SVAL(p,4);
58         rinfo->referral_flags = SVAL(p,6);
59         rinfo->proximity = SVAL(p,8);
60         rinfo->ttl = SVAL(p,10);
61         rinfo->pathOffset = SVAL(p,12);
62         rinfo->altPathOffset = SVAL(p,14);
63         rinfo->nodeOffset = SVAL(p,16);
64         DEBUG(3,("referral version=%d, size=%d, server_type=%d, flags=0x%x, proximity=%d, ttl=%d, pathOffset=%d, altPathOffset=%d, nodeOffset=%d\n",
65                 version, size, rinfo->server_type, rinfo->referral_flags,
66                 rinfo->proximity, rinfo->ttl, rinfo->pathOffset,
67                 rinfo->altPathOffset, rinfo->nodeOffset));
68
69         q = (char*)(p + (rinfo->pathOffset));
70         //printf("p=%p, q=%p, offset=%d\n", p, q, rinfo->pathOffset);
71         //printf("hex=0x%x, string=%s\n", q, q);
72         clistr_pull(cli, rinfo->path, q,
73                     sizeof(rinfo->path),
74                     -1, STR_TERMINATE);
75         DEBUG(4,("referral path=%s\n", rinfo->path));
76         q = (char*)(p + (rinfo->altPathOffset)/sizeof(char));
77         if (rinfo->altPathOffset > 0)
78                 clistr_pull(cli, rinfo->altPath, q,
79                     sizeof(rinfo->altPath),
80                     -1, STR_TERMINATE);
81         DEBUG(4,("referral alt path=%s\n", rinfo->altPath));
82         q = (char*)(p + (rinfo->nodeOffset)/sizeof(char));
83         if (rinfo->nodeOffset > 0)
84                 clistr_pull(cli, rinfo->node, q,
85                     sizeof(rinfo->node),
86                     -1, STR_TERMINATE);
87         DEBUG(4,("referral node=%s\n", rinfo->node));
88         fstrcpy(rinfo->host, &rinfo->node[1]);
89         p = strchr_m(&rinfo->host[1],'\\');
90         if (!p) {
91                 printf("invalid referral node %s\n", rinfo->node);
92                 return -1;
93         }
94         *p = 0;
95         rinfo->share = talloc_strdup(cli->mem_ctx, p+1);
96         DEBUG(3,("referral host=%s share=%s\n",
97                 rinfo->host, rinfo->share));
98         return size;
99 }
100
101 #if 0
102 int smbcli_select_dfs_referral(struct smbcli_state *cli, dfs_info* dinfo)
103 {
104         return (int)sys_random()%dinfo->number_referrals;
105 }
106
107 int smbcli_get_dfs_referral(struct smbcli_state *cli,const char *Fname, dfs_info* dinfo)
108 {
109         struct smb_trans2 parms;
110         int info_level;
111         char *p;
112         pstring fname;
113         int i;
114         char *rparam=NULL, *rdata=NULL;
115         int param_len, data_len;        
116         uint16_t setup;
117         pstring param;
118         DATA_BLOB trans_param, trans_data;
119
120         /* NT uses 260, OS/2 uses 2. Both accept 1. */
121         info_level = (cli->capabilities&CAP_NT_SMBS)?260:1;
122
123         pstrcpy(fname,Fname);
124
125         setup = TRANSACT2_GET_DFS_REFERRAL ;
126         SSVAL(param,0,SMBCLI_DFS_MAX_REFERRAL_LEVEL); /* attribute */
127         p = param+2;
128         p += clistr_push(cli, param+2, fname, -1, 
129                          STR_TERMINATE);
130
131         param_len = PTR_DIFF(p, param);
132         DEBUG(3,("smbcli_get_dfs_referral: sending request\n"));
133         
134         trans_param.length = param_len;
135         trans_param.data = param;
136         trans_data.length = 0;
137         trans_data.data = NULL;
138
139         if (!smbcli_send_trans(cli, SMBtrans2, 
140                             NULL,                   /* Name */
141                             -1, 0,                  /* fid, flags */
142                             &setup, 1, 0,           /* setup, length, max */
143                             &trans_param, 10,   /* param, length, max */
144                             &trans_data, 
145                             cli->max_xmit /* data, length, max */
146                             )) {
147                 return 0;
148         }
149
150         if (!smbcli_receive_trans(cli, SMBtrans2, 
151                                &rparam, &param_len,
152                                &rdata, &data_len) &&
153                    smbcli_is_dos_error(cli)) {
154            return 0;
155         }
156         //printf("smbcli_get_dfs_referral: received response, rdata=%p, rparam=%p\n",
157         //      rdata, rparam);
158
159     if (smbcli_is_error(cli) || !rdata) 
160                 return 0;
161
162         /* parse out some important return info */
163         //printf("smbcli_get_dfs_referral: valid response\n");
164         p = rdata;
165         dinfo->path_consumed = SVAL(p,0);
166         dinfo->number_referrals = SVAL(p,2);
167         dinfo->referral_flags = SVAL(p,4);
168         DEBUG(3,("smbcli_get_dfs_referral: path_consumed=%d, # referrals=%d, flags=0x%x\n",
169                 dinfo->path_consumed, dinfo->number_referrals,
170                 dinfo->referral_flags));
171
172         /* point to the referral bytes */
173         p+=8;
174         for (i=0; i < dinfo->number_referrals; i++) {
175                 p += interpret_referral(cli,info_level,p,&dinfo->referrals[i]);
176         }
177
178         SAFE_FREE(rdata);
179         SAFE_FREE(rparam);
180
181         DEBUG(3,("received %d Dfs referrals\n",
182                          dinfo->number_referrals));
183                          
184         dinfo->selected_referral = smbcli_select_dfs_referral(cli, dinfo);
185         DEBUG(3, ("selected Dfs referral %d %s\n",
186                 dinfo->selected_referral, dinfo->referrals[dinfo->selected_referral].node));
187
188         return(dinfo->number_referrals);
189 }
190 #endif
191
192 /* check if the server produced Dfs redirect */
193 BOOL smbcli_check_dfs_redirect(struct smbcli_state* c, char* fname,
194                 dfs_info* dinfo)
195 {
196                 //printf("check_dfs_redirect: error %s\n",
197                 //      smbcli_errstr(c));
198         if (smbcli_is_dos_error(c)) {
199                         printf("got dos error\n");
200                 return False;
201
202         } else {
203                 NTSTATUS status;
204
205                 /* Check NT error */
206
207                 status = smbcli_nt_error(c);
208                 //printf("got nt error 0x%x\n", status);
209
210                                 if (NT_STATUS_V(NT_STATUS_PATH_NOT_COVERED) != NT_STATUS_V(status)) {
211                         return False;
212                 }
213         }
214     /* execute trans2 getdfsreferral */
215     //printf("check_dfs_redirect: process referral\n");
216     //smbcli_get_dfs_referral(c, fname, dinfo);
217         return True;
218 }
219
220 int smbcli_dfs_open_connection(struct smbcli_client* cluster,
221                 char* host, char* share, int flags)
222 {
223         int i;
224         BOOL retry;
225         struct smbcli_state* c;
226         
227         // check if already connected
228         for (i=0; i < DFS_MAX_CLUSTER_SIZE; i++) {
229                 if (cluster->cli[i]->in_use && strequal(host, smbcli_state_get_host(cluster->cli[i]))
230                                 && strequal(share, smbcli_state_get_share(cluster->cli[i]))) {
231                         DEBUG(3,("smbcli_dfs_open_connection: already connected to \\\\%s\\%s\n", host, share));
232                         return i;
233                 }
234         }
235         // open connection
236         DEBUG(3,("smbcli_dfs_open_connection: opening \\\\%s\\%s %s@%s\n",
237                 host, share, cluster->username, cluster->workgroup));
238         for (i=0; i < DFS_MAX_CLUSTER_SIZE; i++) {
239                 if (!cluster->cli[i]->in_use) {
240                         break;
241                 }
242         }
243         if (i >= DFS_MAX_CLUSTER_SIZE)
244                 return -1;
245
246         c = cluster->cli[i];
247         if (NT_STATUS_IS_ERR(smbcli_full_connection(&c,
248                              NULL, host, NULL, 0,
249                              share, "?????",
250                              cluster->username, cluster->workgroup, 
251                              cluster->password, flags,
252                              &retry)))
253                 return -1;
254         smbcli_state_set_sockopt(cluster->cli[i], cluster->sockops);
255         smbcli_state_set_host(cluster->cli[i], host);
256         smbcli_state_set_share(cluster->cli[i], share);
257         cluster->cli[i]->in_use = True;
258         DEBUG(3,("smbcli_dfs_open_connection: connected \\\\%s\\%s (%d) %s@%s\n",
259                 smbcli_state_get_host(cluster->cli[i]), smbcli_state_get_share(cluster->cli[i]), i,
260                 cluster->username, cluster->workgroup));
261
262         return i;
263 }
264
265 /**********************************************************************
266   Parse the pathname  of the form \hostname\service\reqpath
267   into the dfs_path structure 
268  **********************************************************************/
269
270 BOOL smbcli_parse_dfs_path(char* pathname, struct dfs_path* pdp)
271 {
272         pstring pathname_local;
273         char* p,*temp;
274
275         pstrcpy(pathname_local,pathname);
276         p = temp = pathname_local;
277
278         ZERO_STRUCTP(pdp);
279
280         trim_string(temp,"\\","\\");
281         DEBUG(10,("temp in smbcli_parse_dfs_path: .%s. after trimming \\'s\n",temp));
282
283         /* now tokenize */
284         /* parse out hostname */
285         p = strchr(temp,'\\');
286         if(p == NULL)
287                 return False;
288         *p = '\0';
289         pstrcpy(pdp->hostname,temp);
290         DEBUG(10,("hostname: %s\n",pdp->hostname));
291
292         /* parse out servicename */
293         temp = p+1;
294         p = strchr(temp,'\\');
295         if(p == NULL) {
296                 pstrcpy(pdp->servicename,temp);
297                 pdp->reqpath[0] = '\0';
298                 return True;
299         }
300         *p = '\0';
301         pstrcpy(pdp->servicename,temp);
302         DEBUG(10,("servicename: %s\n",pdp->servicename));
303
304         /* rest is reqpath */
305         pstrcpy(pdp->reqpath, p+1);
306
307         DEBUG(10,("rest of the path: %s\n",pdp->reqpath));
308         return True;
309 }
310
311 char* rebuild_filename(char *referral_fname, struct smbcli_state* c,
312                 char* fname, int path_consumed)
313 {
314         const char *template = "\\\\%s\\%s\\%s";
315         struct dfs_path dp;
316         
317         // TODO: handle consumed length
318         DEBUG(3,("rebuild_filename: %s, %d consumed of %d\n",
319                 fname, path_consumed, strlen(fname)));
320         if (smbcli_parse_dfs_path(fname, &dp)) {
321                 DEBUG(3,("rebuild_filename: reqpath=%s\n",
322                         dp.reqpath));
323                 asprintf(&referral_fname,
324                         template, smbcli_state_get_host(c),
325                         smbcli_state_get_share(c), dp.reqpath);
326         }
327         else
328                 return NULL;
329         DEBUG(3,("rebuild_filename: %s -> %s\n", fname, referral_fname));
330         return referral_fname;
331 }
332
333 /****************************************************************************
334  Open a file (allowing for Dfs referral).
335 ****************************************************************************/
336
337 int smbcli_dfs_open(struct smbcli_client* cluster, int *server,
338         char *fname_src, int flags, int share_mode)
339 {
340         int referral_number;
341         dfs_info dinfo;
342         char *referral_fname;
343         int fnum;
344         
345         DEBUG(3,("smbcli_dfs_open: open %s on server %s(%d)\n",
346                         fname_src, smbcli_state_get_host(cluster->cli[*server]), *server));
347         cluster->cli[*server]->dfs_referral = *server;
348         if ((fnum = smbcli_open(cluster->cli[*server], fname_src, flags, share_mode)) < 0) {
349                 if (smbcli_check_dfs_redirect(cluster->cli[*server], fname_src, &dinfo)) {
350                         // choose referral, check if already connected, open if not
351                         referral_number = dinfo.selected_referral;
352                         DEBUG(3,("smbcli_dfs_open: redirecting to %s\n", dinfo.referrals[referral_number].node));
353                         cluster->cli[*server]->dfs_referral = smbcli_dfs_open_connection(cluster,
354                                 dinfo.referrals[referral_number].host,
355                                 dinfo.referrals[referral_number].share,
356                                 cluster->connection_flags);
357                         *server = cluster->cli[*server]->dfs_referral;
358                         if (server < 0)
359                                 return False;
360                         // rebuild file name and retry operation.
361                         if (rebuild_filename(referral_fname, cluster->cli[*server], fname_src, dinfo.path_consumed) == NULL)
362                                 return False;
363                         fname_src = referral_fname;
364                         DEBUG(3,("smbcli_dfs_open: Dfs open %s on server %s(%d)\n",
365                                 fname_src, smbcli_state_get_host(cluster->cli[*server]), *server));
366                         fnum = smbcli_open(cluster->cli[*server], fname_src, flags, share_mode);
367                 }
368                 if (smbcli_is_error(cluster->cli[*server])) {
369                         printf("smbcli_dfs_open: open of %s failed (%s)\n",
370                                 fname_src, smbcli_errstr(cluster->cli[*server]));
371                         return -1;
372                 }
373         }
374         DEBUG(3,("smbcli_dfs_open: open %s fnum=%d\n",
375                         fname_src, fnum));
376         return fnum;
377 }
378
379 /****************************************************************************
380  Delete a file (allowing for Dfs referral).
381 ****************************************************************************/
382
383 NTSTATUS smbcli_nt_unlink(struct smbcli_client* cluster, int *server,
384         char *fname_src, uint16_t FileAttributes)
385 {
386         int referral_number;
387         dfs_info dinfo;
388         char *referral_fname;
389         struct smb_unlink parms;
390         
391         DEBUG(3,("smbcli_nt_unlink: delete %s on server %s(%d), attributes=0x%x\n",
392                         fname_src, smbcli_state_get_host(cluster->cli[*server]), *server,
393                         FileAttributes));
394         cluster->cli[*server]->dfs_referral = *server;
395         parms.in.pattern = fname_src;
396         parms.in.dirtype = FileAttributes;                      
397         if (NT_STATUS_IS_ERR(smbcli_raw_unlink(cluster->cli[*server], &parms))) {
398                 printf("smbcli_nt_unlink: delete of %s failed (%s)\n",
399                                 fname_src, smbcli_errstr(cluster->cli[*server]));
400                 if (smbcli_check_dfs_redirect(cluster->cli[*server], fname_src, &dinfo)) {
401                         // choose referral, check if already connected, open if not
402                         referral_number = dinfo.selected_referral;
403                         DEBUG(3,("smbcli_nt_unlink: redirecting to %s\n", dinfo.referrals[referral_number].node));
404                         cluster->cli[*server]->dfs_referral = smbcli_dfs_open_connection(cluster,
405                                 dinfo.referrals[referral_number].host,
406                                 dinfo.referrals[referral_number].share,
407                                 cluster->connection_flags);
408                         *server = cluster->cli[*server]->dfs_referral;
409                         if (server < 0)
410                                 return NT_STATUS_INTERNAL_ERROR;
411                         // rebuild file name and retry operation.
412                         if (rebuild_filename(referral_fname, cluster->cli[*server], fname_src, dinfo.path_consumed) == NULL)
413                                 return NT_STATUS_INTERNAL_ERROR;
414                         fname_src = referral_fname;
415                         DEBUG(3,("smbcli_nt_unlink: Dfs delete %s on server %s(%d)\n",
416                                 fname_src, smbcli_state_get_host(cluster->cli[*server]), *server));
417                         smbcli_raw_unlink(cluster->cli[*server], &parms);
418                 }
419                 if (smbcli_is_error(cluster->cli[*server])) {
420                         printf("smbcli_nt_unlink: delete of %s failed (%s)\n",
421                                 fname_src, smbcli_errstr(cluster->cli[*server]));
422                 }
423         }
424         return smbcli_nt_error(cluster->cli[*server]);
425 }
426
427 /****************************************************************************
428  Rename a file (allowing for Dfs referral).
429 ****************************************************************************/
430
431 BOOL smbcli_dfs_rename(struct smbcli_client* cluster, int *server,
432         char *fname_src, char *fname_dst)
433 {
434         int referral_number;
435         dfs_info dinfo;
436         char *referral_fname;
437         
438         DEBUG(3,("smbcli_dfs_rename: rename %s to %s on server %s(%d)\n",
439                         fname_src, fname_dst, smbcli_state_get_host(cluster->cli[*server]), *server));
440         cluster->cli[*server]->dfs_referral = *server;
441         if (!smbcli_rename(cluster->cli[*server], fname_src, fname_dst)) {
442                 if (smbcli_check_dfs_redirect(cluster->cli[*server], fname_src, &dinfo)) {
443                         // choose referral, check if already connected, open if not
444                         referral_number = dinfo.selected_referral;
445                         DEBUG(3,("smbcli_dfs_rename: redirecting to %s\n", dinfo.referrals[referral_number].node));
446                         cluster->cli[*server]->dfs_referral = smbcli_dfs_open_connection(cluster,
447                                 dinfo.referrals[referral_number].host,
448                                 dinfo.referrals[referral_number].share,
449                                 cluster->connection_flags);
450                         *server = cluster->cli[*server]->dfs_referral;
451                         if (server < 0)
452                                 return False;
453                         // rebuild file name and retry operation.
454                         if (rebuild_filename(referral_fname, cluster->cli[*server], fname_src, dinfo.path_consumed) == NULL)
455                                 return False;
456                         fname_src = referral_fname;
457                         DEBUG(3,("smbcli_dfs_rename: Dfs rename %s to %s on server %s(%d)\n",
458                                 fname_src, fname_dst, smbcli_state_get_host(cluster->cli[*server]), *server));
459                         smbcli_rename(cluster->cli[*server], fname_src, fname_dst);
460                 }
461                 if (smbcli_is_error(cluster->cli[*server])) {
462                         printf("smbcli_dfs_rename: rename of %s to %s failed (%s)\n",
463                                 fname_src, fname_dst, smbcli_errstr(cluster->cli[*server]));
464                         return False;
465                 }
466         }
467         return True;
468 }
469
470 /****************************************************************************
471  Make directory (allowing for Dfs referral).
472 ****************************************************************************/
473
474 BOOL smbcli_dfs_mkdir(struct smbcli_client* cluster, int *server,
475         char *fname_src)
476 {
477         int referral_number;
478         dfs_info dinfo;
479         char *referral_fname;
480         
481         DEBUG(3,("smbcli_dfs_mkdir: mkdir %s on server %s(%d)\n",
482                         fname_src, smbcli_state_get_host(cluster->cli[*server]), *server));
483         cluster->cli[*server]->dfs_referral = *server;                  
484         if (!smbcli_mkdir(cluster->cli[*server], fname_src)) {
485                 printf("smbcli_dfs_mkdir: mkdir of %s failed (%s)\n",
486                                 fname_src, smbcli_errstr(cluster->cli[*server]));
487                 if (smbcli_check_dfs_redirect(cluster->cli[*server], fname_src, &dinfo)) {
488                         // choose referral, check if already connected, open if not
489                         referral_number = dinfo.selected_referral;
490                         DEBUG(3,("smbcli_dfs_mkdir: redirecting to %s\n", dinfo.referrals[referral_number].node));
491                         cluster->cli[*server]->dfs_referral = smbcli_dfs_open_connection(cluster,
492                                 dinfo.referrals[referral_number].host,
493                                 dinfo.referrals[referral_number].share,
494                                 cluster->connection_flags);
495                         *server = cluster->cli[*server]->dfs_referral;
496                         if (server < 0)
497                                 return False;
498                         // rebuild file name and retry operation.
499                         if (rebuild_filename(referral_fname, cluster->cli[*server], fname_src, dinfo.path_consumed) == NULL)
500                                 return False;
501                         fname_src = referral_fname;
502                         DEBUG(3,("smbcli_dfs_mkdir: Dfs mkdir %s on server %s(%d)\n",
503                                 fname_src, smbcli_state_get_host(cluster->cli[*server]), *server));
504                         smbcli_mkdir(cluster->cli[*server], fname_src);
505                 }
506                 if (smbcli_is_error(cluster->cli[*server])) {
507                         printf("smbcli_dfs_mkdir: mkdir of %s failed (%s)\n",
508                                 fname_src, smbcli_errstr(cluster->cli[*server]));
509                         return False;
510                 }
511         }
512         return True;
513 }
514
515 /****************************************************************************
516  Remove directory (allowing for Dfs referral).
517 ****************************************************************************/
518
519 BOOL smbcli_dfs_rmdir(struct smbcli_client* cluster, int *server,
520         char *fname_src)
521 {
522         int referral_number;
523         dfs_info dinfo;
524         char *referral_fname;
525         
526         DEBUG(3,("smbcli_dfs_rmdir: rmdir %s on server %s(%d)\n",
527                         fname_src, smbcli_state_get_host(cluster->cli[*server]), *server));
528         cluster->cli[*server]->dfs_referral = *server;                  
529         if (!smbcli_rmdir(cluster->cli[*server], fname_src)) {
530                 printf("smbcli_dfs_rmdir: rmdir of %s failed (%s)\n",
531                                 fname_src, smbcli_errstr(cluster->cli[*server]));
532                 if (smbcli_check_dfs_redirect(cluster->cli[*server], fname_src, &dinfo)) {
533                         // choose referral, check if already connected, open if not
534                         referral_number = dinfo.selected_referral;
535                         DEBUG(3,("smbcli_dfs_rmdir: redirecting to %s\n", dinfo.referrals[referral_number].node));
536                         cluster->cli[*server]->dfs_referral = smbcli_dfs_open_connection(cluster,
537                                 dinfo.referrals[referral_number].host,
538                                 dinfo.referrals[referral_number].share,
539                                 cluster->connection_flags);
540                         *server = cluster->cli[*server]->dfs_referral;
541                         if (server < 0)
542                                 return False;
543                         // rebuild file name and retry operation.
544                         if (rebuild_filename(referral_fname, cluster->cli[*server], fname_src, dinfo.path_consumed) == NULL)
545                                 return False;
546                         fname_src = referral_fname;
547                         DEBUG(3,("smbcli_dfs_rmdir: Dfs rmdir %s on server %s(%d)\n",
548                                 fname_src, smbcli_state_get_host(cluster->cli[*server]), *server));
549                         smbcli_rmdir(cluster->cli[*server], fname_src);
550                 }
551                 if (smbcli_is_error(cluster->cli[*server])) {
552                         printf("smbcli_dfs_rmdir: rmdir of %s failed (%s)\n",
553                                 fname_src, smbcli_errstr(cluster->cli[*server]));
554                         return False;
555                 }
556         }
557         return True;
558 }