Rebuild if headers changed.
[rsync/rsync.git] / socket.c
... / ...
CommitLineData
1/* -*- c-file-style: "linux" -*-
2
3 Copyright (C) 1992-2001 by Andrew Tridgell <tridge@samba.org>
4 Copyright (C) 2001 by Martin Pool <mbp@samba.org>
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/*
22 socket functions used in rsync
23
24 */
25
26#include "rsync.h"
27
28#ifndef HAVE_GETADDRINFO
29#include "lib/addrinfo.h"
30#endif
31
32extern int af;
33
34/* Establish a proxy connection on an open socket to a web roxy by
35 * using the CONNECT method. */
36static int establish_proxy_connection(int fd, char *host, int port)
37{
38 char buffer[1024];
39 char *cp;
40
41 snprintf(buffer, sizeof(buffer), "CONNECT %s:%d HTTP/1.0\r\n\r\n", host, port);
42 if (write(fd, buffer, strlen(buffer)) != strlen(buffer)) {
43 rprintf(FERROR, "failed to write to proxy: %s\n",
44 strerror(errno));
45 return -1;
46 }
47
48 for (cp = buffer; cp < &buffer[sizeof(buffer) - 1]; cp++) {
49 if (read(fd, cp, 1) != 1) {
50 rprintf(FERROR, "failed to read from proxy: %s\n",
51 strerror(errno));
52 return -1;
53 }
54 if (*cp == '\n')
55 break;
56 }
57
58 if (*cp != '\n')
59 cp++;
60 *cp-- = '\0';
61 if (*cp == '\r')
62 *cp = '\0';
63 if (strncmp(buffer, "HTTP/", 5) != 0) {
64 rprintf(FERROR, "bad response from proxy - %s\n",
65 buffer);
66 return -1;
67 }
68 for (cp = &buffer[5]; isdigit(*cp) || (*cp == '.'); cp++)
69 ;
70 while (*cp == ' ')
71 cp++;
72 if (*cp != '2') {
73 rprintf(FERROR, "bad response from proxy - %s\n",
74 buffer);
75 return -1;
76 }
77 /* throw away the rest of the HTTP header */
78 while (1) {
79 for (cp = buffer; cp < &buffer[sizeof(buffer) - 1];
80 cp++) {
81 if (read(fd, cp, 1) != 1) {
82 rprintf(FERROR, "failed to read from proxy: %s\n",
83 strerror(errno));
84 return -1;
85 }
86 if (*cp == '\n')
87 break;
88 }
89 if ((cp > buffer) && (*cp == '\n'))
90 cp--;
91 if ((cp == buffer) && ((*cp == '\n') || (*cp == '\r')))
92 break;
93 }
94 return 0;
95}
96
97
98
99/** Open a socket to a tcp remote host with the specified port .
100 *
101 * Based on code from Warren. Proxy support by Stephen Rothwell
102 *
103 *
104 * @param bind_address Local address to use. Normally NULL to get the stack default.
105 **/
106int open_socket_out(char *host, int port, const char *bind_address)
107{
108 int type = SOCK_STREAM;
109 int error;
110 int s;
111 int result;
112 struct addrinfo hints, *res0, *res;
113 char portbuf[10];
114 char *h;
115 int proxied = 0;
116 char buffer[1024];
117 char *cp;
118
119 /* if we have a RSYNC_PROXY env variable then redirect our
120 * connetcion via a web proxy at the given address. The format
121 * is hostname:port */
122 h = getenv("RSYNC_PROXY");
123 proxied = (h != NULL) && (*h != '\0');
124
125 if (proxied) {
126 strlcpy(buffer, h, sizeof(buffer));
127 cp = strchr(buffer, ':');
128 if (cp == NULL) {
129 rprintf(FERROR,
130 "invalid proxy specification: should be HOST:PORT\n");
131 return -1;
132 }
133 *cp++ = '\0';
134 strcpy(portbuf, cp);
135 h = buffer;
136 } else {
137 snprintf(portbuf, sizeof(portbuf), "%d", port);
138 h = host;
139 }
140
141 memset(&hints, 0, sizeof(hints));
142 hints.ai_family = af;
143 hints.ai_socktype = type;
144 error = getaddrinfo(h, portbuf, &hints, &res0);
145 if (error) {
146 rprintf(FERROR, "getaddrinfo: %s\n", gai_strerror(error));
147 return -1;
148 }
149
150 s = -1;
151 for (res = res0; res; res = res->ai_next) {
152 s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
153 if (s < 0)
154 continue;
155
156 if (bind_address) {
157 struct addrinfo bhints, *bres;
158
159 memset(&bhints, 0, sizeof(bhints));
160 bhints.ai_family = res->ai_family;
161 bhints.ai_socktype = type;
162 bhints.ai_flags = AI_PASSIVE;
163 error = getaddrinfo(bind_address, NULL, &bhints, &bres);
164 if (error) {
165 rprintf(FERROR, "getaddrinfo: %s\n", gai_strerror(error));
166 continue;
167 }
168 if (bres->ai_next) {
169 rprintf(FERROR, "getaddrinfo: resolved to multiple hosts\n");
170 freeaddrinfo(bres);
171 continue;
172 }
173 bind(s, bres->ai_addr, bres->ai_addrlen);
174 }
175
176 if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
177 close(s);
178 s = -1;
179 continue;
180 }
181 if (proxied &&
182 establish_proxy_connection(s, host, port) != 0) {
183 close(s);
184 s = -1;
185 continue;
186 } else
187 break;
188 }
189 freeaddrinfo(res0);
190 if (s < 0) {
191 rprintf(FERROR, "failed to connect to %s - %s\n", h, strerror(errno));
192 return -1;
193 }
194 return s;
195}
196
197
198/**
199 * Open an outgoing socket, but allow for it to be intercepted by
200 * $RSYNC_CONNECT_PROG, which will execute a program across a TCP
201 * socketpair rather than really opening a socket.
202 *
203 * We use this primarily in testing to detect TCP flow bugs, but not
204 * cause security problems by really opening remote connections.
205 *
206 * This is based on the Samba LIBSMB_PROG feature.
207 *
208 * @param bind_address Local address to use. Normally NULL to get the stack default.
209 **/
210int open_socket_out_wrapped (char *host,
211 int port,
212 const char *bind_address)
213{
214 char *prog;
215
216 if ((prog = getenv ("RSYNC_CONNECT_PROG")) != NULL)
217 return sock_exec (prog);
218 else
219 return open_socket_out (host, port, bind_address);
220}
221
222
223
224/**
225 * Open a socket of the specified type, port and address for incoming data
226 *
227 * @param bind_address Local address to bind, or NULL to allow it to
228 * default.
229 **/
230static int open_socket_in(int type, int port, const char *bind_address)
231{
232 int one=1;
233 int s;
234 struct addrinfo hints, *res;
235 char portbuf[10];
236 int error;
237
238 memset(&hints, 0, sizeof(hints));
239 hints.ai_family = af;
240 hints.ai_socktype = type;
241 hints.ai_flags = AI_PASSIVE;
242 snprintf(portbuf, sizeof(portbuf), "%d", port);
243 error = getaddrinfo(bind_address, portbuf, &hints, &res);
244 if (error) {
245 rprintf(FERROR, "getaddrinfo: %s\n", gai_strerror(error));
246 return -1;
247 }
248 if (res->ai_next) {
249 rprintf(FERROR, "getaddrinfo: resolved to multiple hosts\n");
250 freeaddrinfo(res);
251 return -1;
252 }
253
254 s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
255 if (s < 0) {
256 rprintf(FERROR, RSYNC_NAME ": socket failed\n");
257 freeaddrinfo(res);
258 return -1;
259 }
260
261 setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char *)&one,sizeof(one));
262
263 /* now we've got a socket - we need to bind it */
264 if (bind(s, res->ai_addr, res->ai_addrlen) < 0) {
265 rprintf(FERROR, RSYNC_NAME ": bind failed on port %d\n", port);
266 freeaddrinfo(res);
267 close(s);
268 return -1;
269 }
270
271 return s;
272}
273
274
275/*
276 * Determine if a file descriptor is in fact a socket
277 */
278int is_a_socket(int fd)
279{
280 int v;
281 socklen_t l;
282 l = sizeof(int);
283
284 /* Parameters to getsockopt, setsockopt etc are very
285 * unstandardized across platforms, so don't be surprised if
286 * there are compiler warnings on e.g. SCO OpenSwerver or AIX.
287 * It seems they all eventually get the right idea.
288 *
289 * Debian says: ``The fifth argument of getsockopt and
290 * setsockopt is in reality an int [*] (and this is what BSD
291 * 4.* and libc4 and libc5 have). Some POSIX confusion
292 * resulted in the present socklen_t. The draft standard has
293 * not been adopted yet, but glibc2 already follows it and
294 * also has socklen_t [*]. See also accept(2).''
295 *
296 * We now return to your regularly scheduled programming. */
297 return(getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&v, &l) == 0);
298}
299
300
301void start_accept_loop(int port, int (*fn)(int ))
302{
303 int s;
304 extern char *bind_address;
305
306 /* open an incoming socket */
307 s = open_socket_in(SOCK_STREAM, port, bind_address);
308 if (s == -1)
309 exit_cleanup(RERR_SOCKETIO);
310
311 /* ready to listen */
312 if (listen(s, 5) == -1) {
313 close(s);
314 exit_cleanup(RERR_SOCKETIO);
315 }
316
317
318 /* now accept incoming connections - forking a new process
319 for each incoming connection */
320 while (1) {
321 fd_set fds;
322 int fd;
323 struct sockaddr_storage addr;
324 int in_addrlen = sizeof(addr);
325
326 /* close log file before the potentially very long select so
327 file can be trimmed by another process instead of growing
328 forever */
329 log_close();
330
331 FD_ZERO(&fds);
332 FD_SET(s, &fds);
333
334 if (select(s+1, &fds, NULL, NULL, NULL) != 1) {
335 continue;
336 }
337
338 if(!FD_ISSET(s, &fds)) continue;
339
340 fd = accept(s,(struct sockaddr *)&addr,&in_addrlen);
341
342 if (fd == -1) continue;
343
344 signal(SIGCHLD, SIG_IGN);
345
346 /* we shouldn't have any children left hanging around
347 but I have had reports that on Digital Unix zombies
348 are produced, so this ensures that they are reaped */
349#ifdef WNOHANG
350 while (waitpid(-1, NULL, WNOHANG) > 0);
351#endif
352
353 if (fork()==0) {
354 close(s);
355 /* open log file in child before possibly giving
356 up privileges */
357 log_open();
358 _exit(fn(fd));
359 }
360
361 close(fd);
362 }
363}
364
365
366enum SOCK_OPT_TYPES {OPT_BOOL,OPT_INT,OPT_ON};
367
368struct
369{
370 char *name;
371 int level;
372 int option;
373 int value;
374 int opttype;
375} socket_options[] = {
376 {"SO_KEEPALIVE", SOL_SOCKET, SO_KEEPALIVE, 0, OPT_BOOL},
377 {"SO_REUSEADDR", SOL_SOCKET, SO_REUSEADDR, 0, OPT_BOOL},
378 {"SO_BROADCAST", SOL_SOCKET, SO_BROADCAST, 0, OPT_BOOL},
379#ifdef TCP_NODELAY
380 {"TCP_NODELAY", IPPROTO_TCP, TCP_NODELAY, 0, OPT_BOOL},
381#endif
382#ifdef IPTOS_LOWDELAY
383 {"IPTOS_LOWDELAY", IPPROTO_IP, IP_TOS, IPTOS_LOWDELAY, OPT_ON},
384#endif
385#ifdef IPTOS_THROUGHPUT
386 {"IPTOS_THROUGHPUT", IPPROTO_IP, IP_TOS, IPTOS_THROUGHPUT, OPT_ON},
387#endif
388#ifdef SO_SNDBUF
389 {"SO_SNDBUF", SOL_SOCKET, SO_SNDBUF, 0, OPT_INT},
390#endif
391#ifdef SO_RCVBUF
392 {"SO_RCVBUF", SOL_SOCKET, SO_RCVBUF, 0, OPT_INT},
393#endif
394#ifdef SO_SNDLOWAT
395 {"SO_SNDLOWAT", SOL_SOCKET, SO_SNDLOWAT, 0, OPT_INT},
396#endif
397#ifdef SO_RCVLOWAT
398 {"SO_RCVLOWAT", SOL_SOCKET, SO_RCVLOWAT, 0, OPT_INT},
399#endif
400#ifdef SO_SNDTIMEO
401 {"SO_SNDTIMEO", SOL_SOCKET, SO_SNDTIMEO, 0, OPT_INT},
402#endif
403#ifdef SO_RCVTIMEO
404 {"SO_RCVTIMEO", SOL_SOCKET, SO_RCVTIMEO, 0, OPT_INT},
405#endif
406 {NULL,0,0,0,0}};
407
408
409
410/****************************************************************************
411set user socket options
412****************************************************************************/
413void set_socket_options(int fd, char *options)
414{
415 char *tok;
416 if (!options || !*options) return;
417
418 options = strdup(options);
419
420 if (!options) out_of_memory("set_socket_options");
421
422 for (tok=strtok(options, " \t,"); tok; tok=strtok(NULL," \t,")) {
423 int ret=0,i;
424 int value = 1;
425 char *p;
426 int got_value = 0;
427
428 if ((p = strchr(tok,'='))) {
429 *p = 0;
430 value = atoi(p+1);
431 got_value = 1;
432 }
433
434 for (i=0;socket_options[i].name;i++)
435 if (strcmp(socket_options[i].name,tok)==0)
436 break;
437
438 if (!socket_options[i].name) {
439 rprintf(FERROR,"Unknown socket option %s\n",tok);
440 continue;
441 }
442
443 switch (socket_options[i].opttype) {
444 case OPT_BOOL:
445 case OPT_INT:
446 ret = setsockopt(fd,socket_options[i].level,
447 socket_options[i].option,(char *)&value,sizeof(int));
448 break;
449
450 case OPT_ON:
451 if (got_value)
452 rprintf(FERROR,"syntax error - %s does not take a value\n",tok);
453
454 {
455 int on = socket_options[i].value;
456 ret = setsockopt(fd,socket_options[i].level,
457 socket_options[i].option,(char *)&on,sizeof(int));
458 }
459 break;
460 }
461
462 if (ret != 0)
463 rprintf(FERROR, "failed to set socket option %s: %s\n", tok,
464 strerror(errno));
465 }
466
467 free(options);
468}
469
470/****************************************************************************
471become a daemon, discarding the controlling terminal
472****************************************************************************/
473void become_daemon(void)
474{
475 int i;
476
477 if (fork()) {
478 _exit(0);
479 }
480
481 /* detach from the terminal */
482#ifdef HAVE_SETSID
483 setsid();
484#else
485#ifdef TIOCNOTTY
486 i = open("/dev/tty", O_RDWR);
487 if (i >= 0) {
488 ioctl(i, (int) TIOCNOTTY, (char *)0);
489 close(i);
490 }
491#endif /* TIOCNOTTY */
492#endif
493 /* make sure that stdin, stdout an stderr don't stuff things
494 up (library functions, for example) */
495 for (i=0;i<3;i++) {
496 close(i);
497 open("/dev/null", O_RDWR);
498 }
499}
500
501/*******************************************************************
502 return the IP addr of the client as a string
503 ******************************************************************/
504char *client_addr(int fd)
505{
506 struct sockaddr_storage ss;
507 int length = sizeof(ss);
508 static char addr_buf[100];
509 static int initialised;
510
511 if (initialised) return addr_buf;
512
513 initialised = 1;
514
515 if (getpeername(fd, (struct sockaddr *)&ss, &length)) {
516 exit_cleanup(RERR_SOCKETIO);
517 }
518
519 getnameinfo((struct sockaddr *)&ss, length,
520 addr_buf, sizeof(addr_buf), NULL, 0, NI_NUMERICHOST);
521 return addr_buf;
522}
523
524
525/*******************************************************************
526 return the DNS name of the client
527 ******************************************************************/
528char *client_name(int fd)
529{
530 struct sockaddr_storage ss;
531 int length = sizeof(ss);
532 static char name_buf[100];
533 static char port_buf[100];
534 char *def = "UNKNOWN";
535 static int initialised;
536 struct addrinfo hints, *res, *res0;
537 int error;
538
539 if (initialised) return name_buf;
540
541 initialised = 1;
542
543 strcpy(name_buf,def);
544
545 if (getpeername(fd, (struct sockaddr *)&ss, &length)) {
546 exit_cleanup(RERR_SOCKETIO);
547 }
548
549#ifdef INET6
550 if (ss.ss_family == AF_INET6 &&
551 IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)&ss)->sin6_addr)) {
552 struct sockaddr_in6 sin6;
553 struct sockaddr_in *sin;
554
555 memcpy(&sin6, &ss, sizeof(sin6));
556 sin = (struct sockaddr_in *)&ss;
557 memset(sin, 0, sizeof(*sin));
558 sin->sin_family = AF_INET;
559 length = sizeof(struct sockaddr_in);
560#ifdef HAVE_SOCKADDR_LEN
561 sin->sin_len = length;
562#endif
563 sin->sin_port = sin6.sin6_port;
564 memcpy(&sin->sin_addr, &sin6.sin6_addr.s6_addr[12],
565 sizeof(sin->sin_addr));
566 }
567#endif
568
569 /* reverse lookup */
570 if (getnameinfo((struct sockaddr *)&ss, length,
571 name_buf, sizeof(name_buf), port_buf, sizeof(port_buf),
572 NI_NAMEREQD | NI_NUMERICSERV) != 0) {
573 strcpy(name_buf, def);
574 rprintf(FERROR, "reverse name lookup failed\n");
575 }
576
577 /* forward lookup */
578 memset(&hints, 0, sizeof(hints));
579 hints.ai_family = PF_UNSPEC;
580 hints.ai_flags = AI_CANONNAME;
581 hints.ai_socktype = SOCK_STREAM;
582 error = getaddrinfo(name_buf, port_buf, &hints, &res0);
583 if (error) {
584 strcpy(name_buf, def);
585 rprintf(FERROR, "forward name lookup failed\n");
586 return name_buf;
587 }
588
589 /* XXX sin6_flowinfo and other fields */
590 for (res = res0; res; res = res->ai_next) {
591 if (res->ai_family != ss.ss_family)
592 continue;
593 if (res->ai_addrlen != length)
594 continue;
595 if (memcmp(res->ai_addr, &ss, res->ai_addrlen) == 0)
596 break;
597 }
598
599 /* TODO: Do a forward lookup as well to prevent spoofing */
600
601 if (res == NULL) {
602 strcpy(name_buf, def);
603 rprintf(FERROR,
604 "reverse name lookup mismatch - spoofed address?\n");
605 }
606
607 freeaddrinfo(res0);
608 return name_buf;
609}
610
611/**
612 Convert a string to an IP address. The string can be a name or
613 dotted decimal number.
614
615 Returns a pointer to a static in_addr struct -- if you call this
616 more than once then you should copy it.
617*/
618struct in_addr *ip_address(const char *str)
619{
620 static struct in_addr ret;
621 struct hostent *hp;
622
623 if (!str) {
624 rprintf (FERROR, "ip_address received NULL name\n");
625 return NULL;
626 }
627
628 /* try as an IP address */
629 if (inet_aton(str, &ret) != 0) {
630 return &ret;
631 }
632
633 /* otherwise assume it's a network name of some sort and use
634 gethostbyname */
635 if ((hp = gethostbyname (str)) == 0) {
636 rprintf(FERROR, "gethostbyname failed for \"%s\": unknown host?\n",str);
637 return NULL;
638 }
639
640 if (hp->h_addr == NULL) {
641 rprintf(FERROR, "gethostbyname: host address is invalid for host \"%s\"\n",str);
642 return NULL;
643 }
644
645 if (hp->h_length > sizeof ret) {
646 rprintf(FERROR, "gethostbyname: host address for \"%s\" is too large\n",
647 str);
648 return NULL;
649 }
650
651 if (hp->h_addrtype != AF_INET) {
652 rprintf (FERROR, "gethostname: host address for \"%s\" is not IPv4\n",
653 str);
654 return NULL;
655 }
656
657 /* This is kind of difficult. The only field in ret is
658 s_addr, which is the IP address as a 32-bit int. On
659 UNICOS, s_addr is in fact a *bitfield* for reasons best
660 know to Cray. This means we can't memcpy in to it. On the
661 other hand, h_addr is a char*, so we can't just assign.
662
663 Since there's meant to be only one field inside the in_addr
664 structure we will try just copying over the top and see how
665 that goes. */
666 memcpy (&ret, hp->h_addr, hp->h_length);
667
668 return &ret;
669}
670
671
672
673/*******************************************************************
674this is like socketpair but uses tcp. It is used by the Samba
675regression test code
676The function guarantees that nobody else can attach to the socket,
677or if they do that this function fails and the socket gets closed
678returns 0 on success, -1 on failure
679the resulting file descriptors are symmetrical
680 ******************************************************************/
681static int socketpair_tcp(int fd[2])
682{
683 int listener;
684 struct sockaddr_in sock;
685 struct sockaddr_in sock2;
686 socklen_t socklen = sizeof(sock);
687 int connect_done = 0;
688
689 fd[0] = fd[1] = listener = -1;
690
691 memset(&sock, 0, sizeof(sock));
692
693 if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1) goto failed;
694
695 memset(&sock2, 0, sizeof(sock2));
696#ifdef HAVE_SOCK_SIN_LEN
697 sock2.sin_len = sizeof(sock2);
698#endif
699 sock2.sin_family = PF_INET;
700
701 bind(listener, (struct sockaddr *)&sock2, sizeof(sock2));
702
703 if (listen(listener, 1) != 0) goto failed;
704
705 if (getsockname(listener, (struct sockaddr *)&sock, &socklen) != 0) goto failed;
706
707 if ((fd[1] = socket(PF_INET, SOCK_STREAM, 0)) == -1) goto failed;
708
709 set_nonblocking(fd[1]);
710
711 sock.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
712
713 if (connect(fd[1],(struct sockaddr *)&sock,sizeof(sock)) == -1) {
714 if (errno != EINPROGRESS) goto failed;
715 } else {
716 connect_done = 1;
717 }
718
719 if ((fd[0] = accept(listener, (struct sockaddr *)&sock, &socklen)) == -1) goto failed;
720
721 close(listener);
722 if (connect_done == 0) {
723 if (connect(fd[1],(struct sockaddr *)&sock,sizeof(sock)) != 0
724 && errno != EISCONN) goto failed;
725 }
726
727 set_blocking (fd[1]);
728
729 /* all OK! */
730 return 0;
731
732 failed:
733 if (fd[0] != -1) close(fd[0]);
734 if (fd[1] != -1) close(fd[1]);
735 if (listener != -1) close(listener);
736 return -1;
737}
738
739
740/*******************************************************************
741run a program on a local tcp socket, this is used to launch smbd
742when regression testing
743the return value is a socket which is attached to a subprocess
744running "prog". stdin and stdout are attached. stderr is left
745attached to the original stderr
746 ******************************************************************/
747int sock_exec(const char *prog)
748{
749 int fd[2];
750 if (socketpair_tcp(fd) != 0) {
751 rprintf (FERROR, RSYNC_NAME
752 ": socketpair_tcp failed (%s)\n",
753 strerror(errno));
754 return -1;
755 }
756 if (fork() == 0) {
757 close(fd[0]);
758 close(0);
759 close(1);
760 dup(fd[1]);
761 dup(fd[1]);
762 if (verbose > 3)
763 fprintf (stderr,
764 RSYNC_NAME ": execute socket program \"%s\"\n",
765 prog);
766 exit (system (prog));
767 }
768 close (fd[1]);
769 return fd[0];
770}
771
772
773