1 #include "../burp.h"
2 #include "../alloc.h"
3 #include "../asfd.h"
4 #include "../async.h"
5 #include "../cmd.h"
6 #include "../conf.h"
7 #include "../conffile.h"
8 #include "../fsops.h"
9 #include "../handy.h"
10 #include "../incexc_recv.h"
11 #include "../incexc_send.h"
12 #include "../iobuf.h"
13 #include "../log.h"
14 #include "../pathcmp.h"
15 #include "../prepend.h"
16 #include "autoupgrade.h"
17 #include "extra_comms.h"
18 
19 #include <librsync.h>
20 
append_to_feat(char ** feat,const char * str)21 static int append_to_feat(char **feat, const char *str)
22 {
23 	char *tmp=NULL;
24 	if(!*feat)
25 	{
26 		if(!(*feat=strdup_w(str, __func__)))
27 			return -1;
28 		return 0;
29 	}
30 	if(!(tmp=prepend(*feat, str)))
31 		return -1;
32 	free_w(feat);
33 	*feat=tmp;
34 	return 0;
35 }
36 
37 // It is unfortunate that we are having to figure out the server-initiated
38 // restore paths here instead of setting it in a struct sdirs.
39 // But doing the extra_comms needs to come before setting the sdirs, because
40 // extra_comms sets up a bunch of settings that sdirs need to know.
get_restorepath_proto1(struct conf ** cconfs)41 static char *get_restorepath_proto1(struct conf **cconfs)
42 {
43 	char *tmp=NULL;
44 	char *restorepath=NULL;
45 	if((tmp=prepend_s(get_string(cconfs[OPT_DIRECTORY]),
46 		get_string(cconfs[OPT_CNAME]))))
47 			restorepath=prepend_s(tmp, "restore");
48 	free_w(&tmp);
49 	return restorepath;
50 }
51 
get_restorepath_proto2(struct conf ** cconfs)52 static char *get_restorepath_proto2(struct conf **cconfs)
53 {
54 	char *tmp1=NULL;
55 	char *tmp2=NULL;
56 	char *restorepath=NULL;
57 	if(!(tmp1=prepend_s(get_string(cconfs[OPT_DIRECTORY]),
58 		get_string(cconfs[OPT_DEDUP_GROUP]))))
59 			goto error;
60 	if(!(tmp2=prepend_s(tmp1, "clients")))
61 		goto error;
62 	free_w(&tmp1);
63 	if(!(tmp1=prepend_s(tmp2, get_string(cconfs[OPT_CNAME]))))
64 		goto error;
65 	if(!(restorepath=prepend_s(tmp1, "restore")))
66 		goto error;
67 	goto end;
68 error:
69 	free_w(&restorepath);
70 end:
71 	free_w(&tmp1);
72 	free_w(&tmp2);
73 	return restorepath;
74 }
75 
set_restore_path(struct conf ** cconfs,char ** feat)76 static int set_restore_path(struct conf **cconfs, char **feat)
77 {
78 	int ret=-1;
79 	char *restorepath1=NULL;
80 	char *restorepath2=NULL;
81 	if(!(restorepath1=get_restorepath_proto1(cconfs))
82 	  || !(restorepath2=get_restorepath_proto2(cconfs)))
83 		goto end;
84 	if(is_reg_lstat(restorepath1)==1
85 	  && set_string(cconfs[OPT_RESTORE_PATH], restorepath1))
86 		goto end;
87 	else if(is_reg_lstat(restorepath2)==1
88 	  && set_string(cconfs[OPT_RESTORE_PATH], restorepath2))
89 		goto end;
90 	if(get_string(cconfs[OPT_RESTORE_PATH])
91 	  && append_to_feat(feat, "srestore:"))
92 		goto end;
93 	ret=0;
94 end:
95 	free_w(&restorepath1);
96 	free_w(&restorepath2);
97 	return ret;
98 }
99 
100 struct vers
101 {
102 	long min;
103 	long cli;
104 	long ser;
105 	long feat_list;
106 	long directory_tree;
107 	long burp2;
108 	long counters_json;
109 };
110 
send_features(struct asfd * asfd,struct conf ** cconfs,struct vers * vers)111 static int send_features(struct asfd *asfd, struct conf **cconfs,
112 	struct vers *vers)
113 {
114 	int ret=-1;
115 	char *feat=NULL;
116 	enum protocol protocol=get_protocol(cconfs);
117 	struct strlist *startdir=get_strlist(cconfs[OPT_STARTDIR]);
118 	struct strlist *incglob=get_strlist(cconfs[OPT_INCGLOB]);
119 
120 	if(append_to_feat(&feat, "extra_comms_begin ok:")
121 		/* clients can autoupgrade */
122 	  || append_to_feat(&feat, "autoupgrade:")
123 		/* clients can give server incexc conf so that the
124 		   server knows better what to do on resume */
125 	  || append_to_feat(&feat, "incexc:")
126 		/* clients can give the server an alternative client
127 		   to restore from */
128 	  || append_to_feat(&feat, "orig_client:")
129 		/* clients can tell the server what kind of system they are. */
130           || append_to_feat(&feat, "uname:")
131           || append_to_feat(&feat, "failover:")
132           || append_to_feat(&feat, "vss_restore:")
133           || append_to_feat(&feat, "regex_icase:"))
134 		goto end;
135 
136 	/* Clients can receive restore initiated from the server. */
137 	if(set_restore_path(cconfs, &feat))
138 		goto end;
139 
140 	/* Clients can receive incexc conf from the server.
141 	   Only give it as an option if the server has some starting
142 	   directory configured in the clientconfdir. */
143 	if((startdir || incglob)
144 	  && append_to_feat(&feat, "sincexc:"))
145 		goto end;
146 
147 	if(vers->cli>=vers->counters_json)
148 	{
149 		/* Clients can be sent cntrs on resume/verify/restore. */
150 		if(append_to_feat(&feat, "counters_json:"))
151 			goto end;
152 	}
153 
154 	// We support CMD_MESSAGE.
155 	if(append_to_feat(&feat, "msg:"))
156 		goto end;
157 
158 	if(protocol==PROTO_AUTO)
159 	{
160 		/* If the server is configured to use either protocol, let the
161 		   client know that it can choose. */
162 		logp("Server is using protocol=0 (auto)\n");
163 		if(append_to_feat(&feat, "csetproto:"))
164 			goto end;
165 	}
166 	else
167 	{
168 		char p[32]="";
169 		/* Tell the client what we are going to use. */
170 		logp("Server is using protocol=%d\n", (int)protocol);
171 		snprintf(p, sizeof(p), "forceproto=%d:", (int)protocol);
172 		if(append_to_feat(&feat, p))
173 			goto end;
174 	}
175 
176 #ifdef HAVE_BLAKE2
177 	if(append_to_feat(&feat, "rshash=blake2:"))
178 		goto end;
179 #endif
180 
181 	if(append_to_feat(&feat, "seed:"))
182 		goto end;
183 
184 	//printf("feat: %s\n", feat);
185 
186 	if(asfd->write_str(asfd, CMD_GEN, feat))
187 	{
188 		logp("problem in extra_comms\n");
189 		goto end;
190 	}
191 
192 	ret=0;
193 end:
194 	free_w(&feat);
195 	return ret;
196 }
197 
do_autoupgrade(struct asfd * asfd,struct vers * vers,struct conf ** globalcs)198 static int do_autoupgrade(struct asfd *asfd, struct vers *vers,
199 	struct conf **globalcs)
200 {
201 	int ret=-1;
202 	char *os=NULL;
203 	struct iobuf *rbuf=asfd->rbuf;
204 	const char *autoupgrade_dir=get_string(globalcs[OPT_AUTOUPGRADE_DIR]);
205 
206 	if(!(os=strdup_w(rbuf->buf+strlen("autoupgrade:"), __func__)))
207 		goto end;
208 	iobuf_free_content(rbuf);
209 	ret=0;
210 	if(os && *os)
211 	{
212 		// Sanitise path separators
213 		for(char *i=os; *i; ++i)
214 			if(*i == '/' || *i == '\\' || *i == ':')
215 				*i='-';
216 
217 		ret=autoupgrade_server(asfd, vers->ser,
218 			vers->cli, os, get_cntr(globalcs),
219 			autoupgrade_dir);
220 	}
221 end:
222 	free_w(&os);
223 	return ret;
224 }
225 
setup_seed(struct asfd * asfd,struct conf ** cconfs,struct iobuf * rbuf,const char * what,enum conf_opt opt)226 static int setup_seed(
227 	struct asfd *asfd,
228 	struct conf **cconfs,
229 	struct iobuf *rbuf,
230 	const char *what,
231 	enum conf_opt opt
232 ) {
233 	int ret=-1;
234 	char *tmp=NULL;
235 	char *str=NULL;
236 
237 	str=rbuf->buf+strlen(what)+1;
238 	strip_trailing_slashes(&str);
239 
240 	if(!is_absolute(str))
241 	{
242 		char msg[128];
243 		snprintf(msg, sizeof(msg), "A %s needs to be absolute!", what);
244 		log_and_send(asfd, msg);
245 		goto end;
246 	}
247 	if(opt==OPT_SEED_SRC && *str!='/')
248 	{
249 printf("here: %s\n", str);
250 		// More windows hacks - add a slash to the beginning of things
251 		// like 'C:'.
252 		if(astrcat(&tmp, "/", __func__)
253 		  || astrcat(&tmp, str, __func__))
254 			goto end;
255 		str=tmp;
256 	}
257 	if(set_string(cconfs[opt], str))
258 		goto end;
259 	ret=0;
260 end:
261 	free_w(&tmp);
262 	return ret;
263 }
264 
extra_comms_read(struct async * as,struct vers * vers,int * srestore,char ** incexc,struct conf ** globalcs,struct conf ** cconfs)265 static int extra_comms_read(struct async *as,
266 	struct vers *vers, int *srestore,
267 	char **incexc, struct conf **globalcs, struct conf **cconfs)
268 {
269 	int ret=-1;
270 	struct asfd *asfd;
271 	struct iobuf *rbuf;
272 	asfd=as->asfd;
273 	rbuf=asfd->rbuf;
274 
275 	while(1)
276 	{
277 		iobuf_free_content(rbuf);
278 		if(asfd->read(asfd)) goto end;
279 
280 		if(rbuf->cmd!=CMD_GEN)
281 		{
282 			iobuf_log_unexpected(rbuf, __func__);
283 			goto end;
284 		}
285 
286 		if(!strcmp(rbuf->buf, "extra_comms_end"))
287 		{
288 			if(asfd->write_str(asfd, CMD_GEN, "extra_comms_end ok"))
289 				goto end;
290 			break;
291 		}
292 		else if(!strncmp_w(rbuf->buf, "autoupgrade:"))
293 		{
294 			if(do_autoupgrade(asfd, vers, globalcs))
295 				goto end;
296 		}
297 		else if(!strcmp(rbuf->buf, "srestore ok"))
298 		{
299 			char *restore_path=get_string(cconfs[OPT_RESTORE_PATH]);
300 			if(!restore_path)
301 			{
302 				logp("got srestore ok without a restore_path");
303 				goto end;
304 			}
305 
306 			iobuf_free_content(rbuf);
307 			// Client can accept the restore.
308 			// Load the restore config, then send it.
309 			*srestore=1;
310 			// Need to wipe out OPT_INCEXDIR, as it is needed for
311 			// srestore includes. If it is not wiped out, it can
312 			// interfere if cconfs[OPT_RESTORE_PATH] contained no
313 			// includes.
314 			set_strlist(cconfs[OPT_INCEXCDIR], NULL);
315 			if(conf_parse_incexcs_path(cconfs, restore_path)
316 			  || incexc_send_server_restore(asfd, cconfs))
317 				goto end;
318 			// Do not unlink it here - wait until
319 			// the client says that it wants to do the
320 			// restore.
321 			// Also need to leave it around if the
322 			// restore is to an alternative client, so
323 			// that the code below that reloads the config
324 			// can read it again.
325 			// NOTE: that appears to be in
326 			// src/server/run_action.c::client_can_restore()
327 			//unlink(get_string(cconfs[OPT_RESTORE_PATH]));
328 		}
329 		else if(!strcmp(rbuf->buf, "srestore not ok"))
330 		{
331 			const char *restore_path=get_string(
332 				cconfs[OPT_RESTORE_PATH]);
333 			// Client will not accept the restore.
334 			if (restore_path)
335 				unlink(restore_path);
336 			if(set_string(cconfs[OPT_RESTORE_PATH], NULL))
337 				goto end;
338 			logp("Client not accepting server initiated restore.\n");
339 		}
340 		else if(!strcmp(rbuf->buf, "sincexc ok"))
341 		{
342 			// Client can accept incexc conf from the
343 			// server.
344 			iobuf_free_content(rbuf);
345 			if(incexc_send_server(asfd, cconfs))
346 				goto end;
347 		}
348 		else if(!strcmp(rbuf->buf, "incexc"))
349 		{
350 			// Client is telling server its incexc
351 			// configuration so that it can better decide
352 			// what to do on resume.
353 			iobuf_free_content(rbuf);
354 			if(incexc_recv_server(asfd, incexc, globalcs))
355 				goto end;
356 			if(*incexc)
357 			{
358 				char *tmp=NULL;
359 				char comp[32]="";
360 				snprintf(comp, sizeof(comp),
361 					"compression = %d\n",
362 					get_int(cconfs[OPT_COMPRESSION]));
363 				if(!(tmp=prepend(*incexc, comp)))
364 					goto end;
365 				free_w(incexc);
366 				*incexc=tmp;
367 			}
368 		}
369 		else if(!strcmp(rbuf->buf, "counters_json ok"))
370 		{
371 			// Client can accept counters on
372 			// resume/verify/restore.
373 			logp("Client supports being sent json counters.\n");
374 			set_int(cconfs[OPT_SEND_CLIENT_CNTR], 1);
375 		}
376 		else if(!strncmp_w(rbuf->buf, "uname=")
377 		  && strlen(rbuf->buf)>strlen("uname="))
378 		{
379 			char *uname=rbuf->buf+strlen("uname=");
380 			if(!strncasecmp("Windows", uname, strlen("Windows")))
381 				set_int(cconfs[OPT_CLIENT_IS_WINDOWS], 1);
382 		}
383 		else if(!strncmp_w(rbuf->buf, "orig_client=")
384 		  && strlen(rbuf->buf)>strlen("orig_client="))
385 		{
386 			if(conf_switch_to_orig_client(globalcs, cconfs,
387 				rbuf->buf+strlen("orig_client=")))
388 					goto end;
389 			// If this started out as a server-initiated
390 			// restore, need to load the restore file
391 			// again.
392 			if(*srestore)
393 			{
394 				if(conf_parse_incexcs_path(cconfs,
395 					get_string(cconfs[OPT_RESTORE_PATH])))
396 						goto end;
397 			}
398 			if(asfd->write_str(asfd, CMD_GEN, "orig_client ok"))
399 				goto end;
400 		}
401 		else if(!strncmp_w(rbuf->buf, "restore_spool="))
402 		{
403 			// Removed.
404 		}
405 		else if(!strncmp_w(rbuf->buf, "protocol="))
406 		{
407 			char msg[128]="";
408 			// Client wants to set protocol.
409 			enum protocol protocol;
410 			enum protocol cprotocol;
411 			const char *cliproto=NULL;
412 			protocol=get_protocol(cconfs);
413 			cliproto=rbuf->buf+strlen("protocol=");
414 			cprotocol=atoi(cliproto);
415 
416 			if(protocol!=PROTO_AUTO)
417 			{
418 				if(protocol==cprotocol)
419 				{
420 					logp("Client is forcing protocol=%d\n", (int)protocol);
421 					continue;
422 				}
423 				snprintf(msg, sizeof(msg), "Client is trying to use protocol=%d but server is set to protocol=%d\n", (int)cprotocol, (int)protocol);
424 				log_and_send(asfd, msg);
425 				goto end;
426 			}
427 			else if(cprotocol==PROTO_1)
428 			{
429 				set_protocol(cconfs, cprotocol);
430 				set_protocol(globalcs, cprotocol);
431 			}
432 			else if(cprotocol==PROTO_2)
433 			{
434 				set_protocol(cconfs, cprotocol);
435 				set_protocol(globalcs, cprotocol);
436 			}
437 			else
438 			{
439 				snprintf(msg, sizeof(msg), "Client is trying to use protocol=%s, which is unknown\n", cliproto);
440 				log_and_send(asfd, msg);
441 				goto end;
442 			}
443 			logp("Client has set protocol=%d\n",
444 				(int)get_protocol(cconfs));
445 		}
446 		else if(!strncmp_w(rbuf->buf, "rshash=blake2"))
447 		{
448 #ifdef HAVE_BLAKE2
449 			set_e_rshash(cconfs[OPT_RSHASH], RSHASH_BLAKE2);
450 			set_e_rshash(globalcs[OPT_RSHASH], RSHASH_BLAKE2);
451 #else
452 			logp("Client is trying to use librsync hash blake2, but server does not support it.\n");
453 			goto end;
454 #endif
455 		}
456 		else if(!strncmp_w(rbuf->buf, "msg"))
457 		{
458 			set_int(cconfs[OPT_MESSAGE], 1);
459 			set_int(globalcs[OPT_MESSAGE], 1);
460 		}
461 		else if(!strncmp_w(rbuf->buf, "backup_failovers_left="))
462 		{
463 			int l;
464 			l=atoi(rbuf->buf+strlen("backup_failovers_left="));
465 			set_int(cconfs[OPT_BACKUP_FAILOVERS_LEFT], l);
466 			set_int(globalcs[OPT_BACKUP_FAILOVERS_LEFT], l);
467 		}
468 		else if(!strncmp_w(rbuf->buf, "seed_src="))
469 		{
470 			if(setup_seed(asfd, cconfs,
471 				rbuf, "seed_src", OPT_SEED_SRC))
472 					goto end;
473 		}
474 		else if(!strncmp_w(rbuf->buf, "seed_dst="))
475 		{
476 			if(setup_seed(asfd, cconfs,
477 				rbuf, "seed_dst", OPT_SEED_DST))
478 					goto end;
479 		}
480 		else if(!strncmp_w(rbuf->buf, "vss_restore=off"))
481 		{
482 			set_int(cconfs[OPT_VSS_RESTORE], VSS_RESTORE_OFF);
483 			set_int(globalcs[OPT_VSS_RESTORE], VSS_RESTORE_OFF);
484 		}
485 		else if(!strncmp_w(rbuf->buf, "vss_restore=strip"))
486 		{
487 			set_int(cconfs[OPT_VSS_RESTORE], VSS_RESTORE_OFF_STRIP);
488 			set_int(globalcs[OPT_VSS_RESTORE], VSS_RESTORE_OFF_STRIP);
489 		}
490 		else if(!strncmp_w(rbuf->buf, "regex_icase=1"))
491 		{
492 			set_int(cconfs[OPT_REGEX_CASE_INSENSITIVE], 1);
493 			set_int(globalcs[OPT_REGEX_CASE_INSENSITIVE], 1);
494 		}
495 		else
496 		{
497 			iobuf_log_unexpected(rbuf, __func__);
498 			goto end;
499 		}
500 	}
501 
502 	ret=0;
503 end:
504 	iobuf_free_content(rbuf);
505 	return ret;
506 }
507 
vers_init(struct vers * vers,struct conf ** cconfs)508 static int vers_init(struct vers *vers, struct conf **cconfs)
509 {
510 	memset(vers, 0, sizeof(struct vers));
511 	return ((vers->min=version_to_long("1.2.7"))<0
512 	  || (vers->cli=version_to_long(get_string(cconfs[OPT_PEER_VERSION])))<0
513 	  || (vers->ser=version_to_long(PACKAGE_VERSION))<0
514 	  || (vers->feat_list=version_to_long("1.3.0"))<0
515 	  || (vers->directory_tree=version_to_long("1.3.6"))<0
516 	  || (vers->burp2=version_to_long("2.0.0"))<0
517 	  || (vers->counters_json=version_to_long("2.0.46"))<0);
518 }
519 
check_seed(struct asfd * asfd,struct conf ** cconfs)520 static int check_seed(struct asfd *asfd, struct conf **cconfs)
521 {
522 	char msg[128]="";
523 	const char *src=get_string(cconfs[OPT_SEED_SRC]);
524 	const char *dst=get_string(cconfs[OPT_SEED_DST]);
525 	if(!src && !dst)
526 		return 0;
527 	if(src && dst)
528 	{
529 		logp("Seeding '%s' -> '%s'\n", src, dst);
530 		return 0;
531 	}
532 	snprintf(msg, sizeof(msg),
533 		"You must specify %s and %s options together, or not at all.",
534 			cconfs[OPT_SEED_SRC]->field,
535 			cconfs[OPT_SEED_DST]->field);
536 	log_and_send(asfd, msg);
537 	return -1;
538 }
539 
extra_comms(struct async * as,char ** incexc,int * srestore,struct conf ** confs,struct conf ** cconfs)540 int extra_comms(struct async *as,
541 	char **incexc, int *srestore, struct conf **confs, struct conf **cconfs)
542 {
543 	struct vers vers;
544 	struct asfd *asfd;
545 	asfd=as->asfd;
546 	//char *restorepath=NULL;
547 	const char *peer_version=NULL;
548 
549 	if(vers_init(&vers, cconfs))
550 		goto error;
551 
552 	if(vers.cli<vers.directory_tree)
553 	{
554 		set_int(confs[OPT_DIRECTORY_TREE], 0);
555 		set_int(cconfs[OPT_DIRECTORY_TREE], 0);
556 	}
557 
558 	// Clients before 1.2.7 did not know how to do extra comms, so skip
559 	// this section for them.
560 	if(vers.cli<vers.min)
561 		return 0;
562 
563 	if(asfd_read_expect(asfd, CMD_GEN, "extra_comms_begin"))
564 	{
565 		logp("problem reading in extra_comms\n");
566 		goto error;
567 	}
568 	// Want to tell the clients the extra comms features that are
569 	// supported, so that new clients are more likely to work with old
570 	// servers.
571 	if(vers.cli==vers.feat_list)
572 	{
573 		// 1.3.0 did not support the feature list.
574 		if(asfd->write_str(asfd, CMD_GEN, "extra_comms_begin ok"))
575 		{
576 			logp("problem writing in extra_comms\n");
577 			goto error;
578 		}
579 	}
580 	else
581 	{
582 		if(send_features(asfd, cconfs, &vers))
583 			goto error;
584 	}
585 
586 	if(extra_comms_read(as, &vers, srestore, incexc, confs, cconfs))
587 		goto error;
588 
589 	peer_version=get_string(cconfs[OPT_PEER_VERSION]);
590 
591 	// This needs to come after extra_comms_read, as the client might
592 	// have set PROTO_1 or PROTO_2.
593 	switch(get_protocol(cconfs))
594 	{
595 		case PROTO_AUTO:
596 			// The protocol has not been specified. Make a choice.
597 			if(vers.cli<vers.burp2)
598 			{
599 				// Client is burp-1.x.x, use protocol1.
600 				set_protocol(confs, PROTO_1);
601 				set_protocol(cconfs, PROTO_1);
602 				logp("Client is %s-%s - using protocol=%d\n",
603 					PACKAGE_TARNAME,
604 					peer_version, PROTO_1);
605 			}
606 			else
607 			{
608 				// Client is burp-2.x.x, use protocol2.
609 				// This will probably never be reached because
610 				// the negotiation will take care of it.
611 				/*
612 				set_protocol(confs, PROTO_2);
613 				set_protocol(cconfs, PROTO_2);
614 				logp("Client is %s-%s - using protocol=%d\n",
615 					PACKAGE_TARNAME,
616 					peer_version, PROTO_2);
617 				*/
618 				// PROTO_1 is safer for now.
619 				set_protocol(confs, PROTO_1);
620 				set_protocol(cconfs, PROTO_1);
621 				logp("Client is %s-%s - using protocol=%d\n",
622 					PACKAGE_TARNAME,
623 					peer_version, PROTO_1);
624 			}
625 			break;
626 		case PROTO_1:
627 			// It is OK for the client to be burp1 and for the
628 			// server to be forced to protocol1.
629 			break;
630 		case PROTO_2:
631 			if(vers.cli>=vers.burp2)
632 				break;
633 			logp("protocol=%d is set server side, "
634 			  "but client is %s version %s\n",
635 			  PROTO_2, PACKAGE_TARNAME, peer_version);
636 			goto error;
637 	}
638 
639 	if(get_protocol(cconfs)==PROTO_1)
640 	{
641 		if(get_e_rshash(cconfs[OPT_RSHASH])==RSHASH_UNSET)
642 		{
643 			set_e_rshash(confs[OPT_RSHASH], RSHASH_MD4);
644 			set_e_rshash(cconfs[OPT_RSHASH], RSHASH_MD4);
645 		}
646 	}
647 
648 	if(check_seed(asfd, cconfs))
649 		goto error;
650 
651 	return 0;
652 error:
653 	return -1;
654 }
655