1*aa772005SRobert Watson %{ 2*aa772005SRobert Watson /*- 3*aa772005SRobert Watson * Copyright (c) 2012 The FreeBSD Foundation 4*aa772005SRobert Watson * All rights reserved. 5*aa772005SRobert Watson * 6*aa772005SRobert Watson * This software was developed by Pawel Jakub Dawidek under sponsorship from 7*aa772005SRobert Watson * the FreeBSD Foundation. 8*aa772005SRobert Watson * 9*aa772005SRobert Watson * Redistribution and use in source and binary forms, with or without 10*aa772005SRobert Watson * modification, are permitted provided that the following conditions 11*aa772005SRobert Watson * are met: 12*aa772005SRobert Watson * 1. Redistributions of source code must retain the above copyright 13*aa772005SRobert Watson * notice, this list of conditions and the following disclaimer. 14*aa772005SRobert Watson * 2. Redistributions in binary form must reproduce the above copyright 15*aa772005SRobert Watson * notice, this list of conditions and the following disclaimer in the 16*aa772005SRobert Watson * documentation and/or other materials provided with the distribution. 17*aa772005SRobert Watson * 18*aa772005SRobert Watson * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 19*aa772005SRobert Watson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20*aa772005SRobert Watson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21*aa772005SRobert Watson * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 22*aa772005SRobert Watson * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23*aa772005SRobert Watson * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24*aa772005SRobert Watson * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25*aa772005SRobert Watson * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26*aa772005SRobert Watson * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27*aa772005SRobert Watson * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28*aa772005SRobert Watson * SUCH DAMAGE. 29*aa772005SRobert Watson * 30*aa772005SRobert Watson * $P4: //depot/projects/trustedbsd/openbsm/bin/auditdistd/parse.y#5 $ 31*aa772005SRobert Watson */ 32*aa772005SRobert Watson 33*aa772005SRobert Watson #include <config/config.h> 34*aa772005SRobert Watson 35*aa772005SRobert Watson #include <sys/types.h> 36*aa772005SRobert Watson #include <sys/queue.h> 37*aa772005SRobert Watson #include <sys/sysctl.h> 38*aa772005SRobert Watson 39*aa772005SRobert Watson #include <arpa/inet.h> 40*aa772005SRobert Watson 41*aa772005SRobert Watson #include <err.h> 42*aa772005SRobert Watson #include <errno.h> 43*aa772005SRobert Watson #include <stdio.h> 44*aa772005SRobert Watson #include <string.h> 45*aa772005SRobert Watson #include <sysexits.h> 46*aa772005SRobert Watson #include <unistd.h> 47*aa772005SRobert Watson #ifndef HAVE_STRLCPY 48*aa772005SRobert Watson #include <compat/strlcpy.h> 49*aa772005SRobert Watson #endif 50*aa772005SRobert Watson 51*aa772005SRobert Watson #include "auditdistd.h" 52*aa772005SRobert Watson #include "pjdlog.h" 53*aa772005SRobert Watson 54*aa772005SRobert Watson extern int depth; 55*aa772005SRobert Watson extern int lineno; 56*aa772005SRobert Watson 57*aa772005SRobert Watson extern FILE *yyin; 58*aa772005SRobert Watson extern char *yytext; 59*aa772005SRobert Watson 60*aa772005SRobert Watson static struct adist_config *lconfig; 61*aa772005SRobert Watson static struct adist_host *curhost; 62*aa772005SRobert Watson #define SECTION_GLOBAL 0 63*aa772005SRobert Watson #define SECTION_SENDER 1 64*aa772005SRobert Watson #define SECTION_RECEIVER 2 65*aa772005SRobert Watson static int cursection; 66*aa772005SRobert Watson 67*aa772005SRobert Watson /* Sender section. */ 68*aa772005SRobert Watson static char depth1_source[ADIST_ADDRSIZE]; 69*aa772005SRobert Watson static int depth1_checksum; 70*aa772005SRobert Watson static int depth1_compression; 71*aa772005SRobert Watson /* Sender and receiver sections. */ 72*aa772005SRobert Watson static char depth1_directory[PATH_MAX]; 73*aa772005SRobert Watson 74*aa772005SRobert Watson static bool adjust_directory(char *path); 75*aa772005SRobert Watson static bool family_supported(int family); 76*aa772005SRobert Watson 77*aa772005SRobert Watson extern void yyrestart(FILE *); 78*aa772005SRobert Watson %} 79*aa772005SRobert Watson 80*aa772005SRobert Watson %token CB 81*aa772005SRobert Watson %token CERTFILE 82*aa772005SRobert Watson %token DIRECTORY 83*aa772005SRobert Watson %token FINGERPRINT 84*aa772005SRobert Watson %token HOST 85*aa772005SRobert Watson %token KEYFILE 86*aa772005SRobert Watson %token LISTEN 87*aa772005SRobert Watson %token NAME 88*aa772005SRobert Watson %token OB 89*aa772005SRobert Watson %token PASSWORD 90*aa772005SRobert Watson %token PIDFILE 91*aa772005SRobert Watson %token RECEIVER REMOTE 92*aa772005SRobert Watson %token SENDER SOURCE 93*aa772005SRobert Watson %token TIMEOUT 94*aa772005SRobert Watson 95*aa772005SRobert Watson /* 96*aa772005SRobert Watson %type <num> checksum_type 97*aa772005SRobert Watson %type <num> compression_type 98*aa772005SRobert Watson */ 99*aa772005SRobert Watson 100*aa772005SRobert Watson %union 101*aa772005SRobert Watson { 102*aa772005SRobert Watson int num; 103*aa772005SRobert Watson char *str; 104*aa772005SRobert Watson } 105*aa772005SRobert Watson 106*aa772005SRobert Watson %token <num> NUM 107*aa772005SRobert Watson %token <str> STR 108*aa772005SRobert Watson 109*aa772005SRobert Watson %% 110*aa772005SRobert Watson 111*aa772005SRobert Watson statements: 112*aa772005SRobert Watson | 113*aa772005SRobert Watson statements statement 114*aa772005SRobert Watson ; 115*aa772005SRobert Watson 116*aa772005SRobert Watson statement: 117*aa772005SRobert Watson name_statement 118*aa772005SRobert Watson | 119*aa772005SRobert Watson pidfile_statement 120*aa772005SRobert Watson | 121*aa772005SRobert Watson timeout_statement 122*aa772005SRobert Watson | 123*aa772005SRobert Watson sender_statement 124*aa772005SRobert Watson | 125*aa772005SRobert Watson receiver_statement 126*aa772005SRobert Watson ; 127*aa772005SRobert Watson 128*aa772005SRobert Watson name_statement: NAME STR 129*aa772005SRobert Watson { 130*aa772005SRobert Watson PJDLOG_RASSERT(depth == 0, 131*aa772005SRobert Watson "The name variable can only be specificed in the global section."); 132*aa772005SRobert Watson 133*aa772005SRobert Watson if (lconfig->adc_name[0] != '\0') { 134*aa772005SRobert Watson pjdlog_error("The name variable is specified twice."); 135*aa772005SRobert Watson free($2); 136*aa772005SRobert Watson return (1); 137*aa772005SRobert Watson } 138*aa772005SRobert Watson if (strlcpy(lconfig->adc_name, $2, 139*aa772005SRobert Watson sizeof(lconfig->adc_name)) >= 140*aa772005SRobert Watson sizeof(lconfig->adc_name)) { 141*aa772005SRobert Watson pjdlog_error("The name value is too long."); 142*aa772005SRobert Watson free($2); 143*aa772005SRobert Watson return (1); 144*aa772005SRobert Watson } 145*aa772005SRobert Watson free($2); 146*aa772005SRobert Watson } 147*aa772005SRobert Watson ; 148*aa772005SRobert Watson 149*aa772005SRobert Watson pidfile_statement: PIDFILE STR 150*aa772005SRobert Watson { 151*aa772005SRobert Watson PJDLOG_RASSERT(depth == 0, 152*aa772005SRobert Watson "The pidfile variable can only be specificed in the global section."); 153*aa772005SRobert Watson 154*aa772005SRobert Watson if (lconfig->adc_pidfile[0] != '\0') { 155*aa772005SRobert Watson pjdlog_error("The pidfile variable is specified twice."); 156*aa772005SRobert Watson free($2); 157*aa772005SRobert Watson return (1); 158*aa772005SRobert Watson } 159*aa772005SRobert Watson if (strcmp($2, "none") != 0 && $2[0] != '/') { 160*aa772005SRobert Watson pjdlog_error("The pidfile variable must be set to absolute pathname or \"none\"."); 161*aa772005SRobert Watson free($2); 162*aa772005SRobert Watson return (1); 163*aa772005SRobert Watson } 164*aa772005SRobert Watson if (strlcpy(lconfig->adc_pidfile, $2, 165*aa772005SRobert Watson sizeof(lconfig->adc_pidfile)) >= 166*aa772005SRobert Watson sizeof(lconfig->adc_pidfile)) { 167*aa772005SRobert Watson pjdlog_error("The pidfile value is too long."); 168*aa772005SRobert Watson free($2); 169*aa772005SRobert Watson return (1); 170*aa772005SRobert Watson } 171*aa772005SRobert Watson free($2); 172*aa772005SRobert Watson } 173*aa772005SRobert Watson ; 174*aa772005SRobert Watson 175*aa772005SRobert Watson timeout_statement: TIMEOUT NUM 176*aa772005SRobert Watson { 177*aa772005SRobert Watson PJDLOG_ASSERT(depth == 0); 178*aa772005SRobert Watson 179*aa772005SRobert Watson lconfig->adc_timeout = $2; 180*aa772005SRobert Watson } 181*aa772005SRobert Watson ; 182*aa772005SRobert Watson 183*aa772005SRobert Watson sender_statement: SENDER sender_start sender_entries CB 184*aa772005SRobert Watson { 185*aa772005SRobert Watson PJDLOG_ASSERT(depth == 0); 186*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_SENDER); 187*aa772005SRobert Watson 188*aa772005SRobert Watson /* Configure defaults. */ 189*aa772005SRobert Watson if (depth1_checksum == -1) 190*aa772005SRobert Watson depth1_checksum = ADIST_CHECKSUM_NONE; 191*aa772005SRobert Watson if (depth1_compression == -1) 192*aa772005SRobert Watson depth1_compression = ADIST_COMPRESSION_NONE; 193*aa772005SRobert Watson if (depth1_directory[0] == '\0') { 194*aa772005SRobert Watson (void)strlcpy(depth1_directory, ADIST_DIRECTORY_SENDER, 195*aa772005SRobert Watson sizeof(depth1_directory)); 196*aa772005SRobert Watson } 197*aa772005SRobert Watson /* Empty depth1_source is ok. */ 198*aa772005SRobert Watson TAILQ_FOREACH(curhost, &lconfig->adc_hosts, adh_next) { 199*aa772005SRobert Watson if (curhost->adh_role != ADIST_ROLE_SENDER) 200*aa772005SRobert Watson continue; 201*aa772005SRobert Watson if (curhost->adh_checksum == -1) 202*aa772005SRobert Watson curhost->adh_checksum = depth1_checksum; 203*aa772005SRobert Watson if (curhost->adh_compression == -1) 204*aa772005SRobert Watson curhost->adh_compression = depth1_compression; 205*aa772005SRobert Watson if (curhost->adh_directory[0] == '\0') { 206*aa772005SRobert Watson (void)strlcpy(curhost->adh_directory, 207*aa772005SRobert Watson depth1_directory, 208*aa772005SRobert Watson sizeof(curhost->adh_directory)); 209*aa772005SRobert Watson } 210*aa772005SRobert Watson if (curhost->adh_localaddr[0] == '\0') { 211*aa772005SRobert Watson (void)strlcpy(curhost->adh_localaddr, 212*aa772005SRobert Watson depth1_source, 213*aa772005SRobert Watson sizeof(curhost->adh_localaddr)); 214*aa772005SRobert Watson } 215*aa772005SRobert Watson } 216*aa772005SRobert Watson cursection = SECTION_GLOBAL; 217*aa772005SRobert Watson } 218*aa772005SRobert Watson ; 219*aa772005SRobert Watson 220*aa772005SRobert Watson sender_start: OB 221*aa772005SRobert Watson { 222*aa772005SRobert Watson PJDLOG_ASSERT(depth == 1); 223*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_GLOBAL); 224*aa772005SRobert Watson 225*aa772005SRobert Watson cursection = SECTION_SENDER; 226*aa772005SRobert Watson depth1_checksum = -1; 227*aa772005SRobert Watson depth1_compression = -1; 228*aa772005SRobert Watson depth1_source[0] = '\0'; 229*aa772005SRobert Watson depth1_directory[0] = '\0'; 230*aa772005SRobert Watson 231*aa772005SRobert Watson #ifndef HAVE_AUDIT_SYSCALLS 232*aa772005SRobert Watson pjdlog_error("Sender functionality is not available."); 233*aa772005SRobert Watson return (1); 234*aa772005SRobert Watson #endif 235*aa772005SRobert Watson } 236*aa772005SRobert Watson ; 237*aa772005SRobert Watson 238*aa772005SRobert Watson sender_entries: 239*aa772005SRobert Watson | 240*aa772005SRobert Watson sender_entries sender_entry 241*aa772005SRobert Watson ; 242*aa772005SRobert Watson 243*aa772005SRobert Watson sender_entry: 244*aa772005SRobert Watson source_statement 245*aa772005SRobert Watson | 246*aa772005SRobert Watson directory_statement 247*aa772005SRobert Watson /* 248*aa772005SRobert Watson | 249*aa772005SRobert Watson checksum_statement 250*aa772005SRobert Watson | 251*aa772005SRobert Watson compression_statement 252*aa772005SRobert Watson */ 253*aa772005SRobert Watson | 254*aa772005SRobert Watson sender_host_statement 255*aa772005SRobert Watson ; 256*aa772005SRobert Watson 257*aa772005SRobert Watson receiver_statement: RECEIVER receiver_start receiver_entries CB 258*aa772005SRobert Watson { 259*aa772005SRobert Watson PJDLOG_ASSERT(depth == 0); 260*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_RECEIVER); 261*aa772005SRobert Watson 262*aa772005SRobert Watson /* 263*aa772005SRobert Watson * If not listen addresses were specified, 264*aa772005SRobert Watson * configure default ones. 265*aa772005SRobert Watson */ 266*aa772005SRobert Watson if (TAILQ_EMPTY(&lconfig->adc_listen)) { 267*aa772005SRobert Watson struct adist_listen *lst; 268*aa772005SRobert Watson 269*aa772005SRobert Watson if (family_supported(AF_INET)) { 270*aa772005SRobert Watson lst = calloc(1, sizeof(*lst)); 271*aa772005SRobert Watson if (lst == NULL) { 272*aa772005SRobert Watson pjdlog_error("Unable to allocate memory for listen address."); 273*aa772005SRobert Watson return (1); 274*aa772005SRobert Watson } 275*aa772005SRobert Watson (void)strlcpy(lst->adl_addr, 276*aa772005SRobert Watson ADIST_LISTEN_TLS_TCP4, 277*aa772005SRobert Watson sizeof(lst->adl_addr)); 278*aa772005SRobert Watson TAILQ_INSERT_TAIL(&lconfig->adc_listen, lst, adl_next); 279*aa772005SRobert Watson } else { 280*aa772005SRobert Watson pjdlog_debug(1, 281*aa772005SRobert Watson "No IPv4 support in the kernel, not listening on IPv4 address."); 282*aa772005SRobert Watson } 283*aa772005SRobert Watson if (family_supported(AF_INET6)) { 284*aa772005SRobert Watson lst = calloc(1, sizeof(*lst)); 285*aa772005SRobert Watson if (lst == NULL) { 286*aa772005SRobert Watson pjdlog_error("Unable to allocate memory for listen address."); 287*aa772005SRobert Watson return (1); 288*aa772005SRobert Watson } 289*aa772005SRobert Watson (void)strlcpy(lst->adl_addr, 290*aa772005SRobert Watson ADIST_LISTEN_TLS_TCP6, 291*aa772005SRobert Watson sizeof(lst->adl_addr)); 292*aa772005SRobert Watson TAILQ_INSERT_TAIL(&lconfig->adc_listen, lst, adl_next); 293*aa772005SRobert Watson } else { 294*aa772005SRobert Watson pjdlog_debug(1, 295*aa772005SRobert Watson "No IPv6 support in the kernel, not listening on IPv6 address."); 296*aa772005SRobert Watson } 297*aa772005SRobert Watson if (TAILQ_EMPTY(&lconfig->adc_listen)) { 298*aa772005SRobert Watson pjdlog_error("No address to listen on."); 299*aa772005SRobert Watson return (1); 300*aa772005SRobert Watson } 301*aa772005SRobert Watson } 302*aa772005SRobert Watson /* Configure defaults. */ 303*aa772005SRobert Watson if (depth1_directory[0] == '\0') { 304*aa772005SRobert Watson (void)strlcpy(depth1_directory, 305*aa772005SRobert Watson ADIST_DIRECTORY_RECEIVER, 306*aa772005SRobert Watson sizeof(depth1_directory)); 307*aa772005SRobert Watson } 308*aa772005SRobert Watson TAILQ_FOREACH(curhost, &lconfig->adc_hosts, adh_next) { 309*aa772005SRobert Watson if (curhost->adh_role != ADIST_ROLE_RECEIVER) 310*aa772005SRobert Watson continue; 311*aa772005SRobert Watson if (curhost->adh_directory[0] == '\0') { 312*aa772005SRobert Watson if (snprintf(curhost->adh_directory, 313*aa772005SRobert Watson sizeof(curhost->adh_directory), "%s/%s", 314*aa772005SRobert Watson depth1_directory, curhost->adh_name) >= 315*aa772005SRobert Watson (ssize_t)sizeof(curhost->adh_directory)) { 316*aa772005SRobert Watson pjdlog_error("Directory value is too long."); 317*aa772005SRobert Watson return (1); 318*aa772005SRobert Watson } 319*aa772005SRobert Watson } 320*aa772005SRobert Watson } 321*aa772005SRobert Watson cursection = SECTION_GLOBAL; 322*aa772005SRobert Watson } 323*aa772005SRobert Watson ; 324*aa772005SRobert Watson 325*aa772005SRobert Watson receiver_start: OB 326*aa772005SRobert Watson { 327*aa772005SRobert Watson PJDLOG_ASSERT(depth == 1); 328*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_GLOBAL); 329*aa772005SRobert Watson 330*aa772005SRobert Watson cursection = SECTION_RECEIVER; 331*aa772005SRobert Watson depth1_directory[0] = '\0'; 332*aa772005SRobert Watson } 333*aa772005SRobert Watson ; 334*aa772005SRobert Watson 335*aa772005SRobert Watson receiver_entries: 336*aa772005SRobert Watson | 337*aa772005SRobert Watson receiver_entries receiver_entry 338*aa772005SRobert Watson ; 339*aa772005SRobert Watson 340*aa772005SRobert Watson receiver_entry: 341*aa772005SRobert Watson listen_statement 342*aa772005SRobert Watson | 343*aa772005SRobert Watson directory_statement 344*aa772005SRobert Watson | 345*aa772005SRobert Watson certfile_statement 346*aa772005SRobert Watson | 347*aa772005SRobert Watson keyfile_statement 348*aa772005SRobert Watson | 349*aa772005SRobert Watson receiver_host_statement 350*aa772005SRobert Watson ; 351*aa772005SRobert Watson 352*aa772005SRobert Watson /* 353*aa772005SRobert Watson checksum_statement: CHECKSUM checksum_type 354*aa772005SRobert Watson { 355*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_SENDER); 356*aa772005SRobert Watson 357*aa772005SRobert Watson switch (depth) { 358*aa772005SRobert Watson case 1: 359*aa772005SRobert Watson depth1_checksum = $2; 360*aa772005SRobert Watson break; 361*aa772005SRobert Watson case 2: 362*aa772005SRobert Watson PJDLOG_ASSERT(curhost != NULL); 363*aa772005SRobert Watson curhost->adh_checksum = $2; 364*aa772005SRobert Watson break; 365*aa772005SRobert Watson default: 366*aa772005SRobert Watson PJDLOG_ABORT("checksum at wrong depth level"); 367*aa772005SRobert Watson } 368*aa772005SRobert Watson } 369*aa772005SRobert Watson ; 370*aa772005SRobert Watson 371*aa772005SRobert Watson checksum_type: 372*aa772005SRobert Watson NONE { $$ = ADIST_CHECKSUM_NONE; } 373*aa772005SRobert Watson | 374*aa772005SRobert Watson CRC32 { $$ = ADIST_CHECKSUM_CRC32; } 375*aa772005SRobert Watson | 376*aa772005SRobert Watson SHA256 { $$ = ADIST_CHECKSUM_SHA256; } 377*aa772005SRobert Watson ; 378*aa772005SRobert Watson 379*aa772005SRobert Watson compression_statement: COMPRESSION compression_type 380*aa772005SRobert Watson { 381*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_SENDER); 382*aa772005SRobert Watson 383*aa772005SRobert Watson switch (depth) { 384*aa772005SRobert Watson case 1: 385*aa772005SRobert Watson depth1_compression = $2; 386*aa772005SRobert Watson break; 387*aa772005SRobert Watson case 2: 388*aa772005SRobert Watson PJDLOG_ASSERT(curhost != NULL); 389*aa772005SRobert Watson curhost->adh_compression = $2; 390*aa772005SRobert Watson break; 391*aa772005SRobert Watson default: 392*aa772005SRobert Watson PJDLOG_ABORT("compression at wrong depth level"); 393*aa772005SRobert Watson } 394*aa772005SRobert Watson } 395*aa772005SRobert Watson ; 396*aa772005SRobert Watson 397*aa772005SRobert Watson compression_type: 398*aa772005SRobert Watson NONE { $$ = ADIST_COMPRESSION_NONE; } 399*aa772005SRobert Watson | 400*aa772005SRobert Watson LZF { $$ = ADIST_COMPRESSION_LZF; } 401*aa772005SRobert Watson ; 402*aa772005SRobert Watson */ 403*aa772005SRobert Watson 404*aa772005SRobert Watson directory_statement: DIRECTORY STR 405*aa772005SRobert Watson { 406*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_SENDER || 407*aa772005SRobert Watson cursection == SECTION_RECEIVER); 408*aa772005SRobert Watson 409*aa772005SRobert Watson switch (depth) { 410*aa772005SRobert Watson case 1: 411*aa772005SRobert Watson if (strlcpy(depth1_directory, $2, 412*aa772005SRobert Watson sizeof(depth1_directory)) >= 413*aa772005SRobert Watson sizeof(depth1_directory)) { 414*aa772005SRobert Watson pjdlog_error("Directory value is too long."); 415*aa772005SRobert Watson free($2); 416*aa772005SRobert Watson return (1); 417*aa772005SRobert Watson } 418*aa772005SRobert Watson if (!adjust_directory(depth1_directory)) 419*aa772005SRobert Watson return (1); 420*aa772005SRobert Watson break; 421*aa772005SRobert Watson case 2: 422*aa772005SRobert Watson if (cursection == SECTION_SENDER || $2[0] == '/') { 423*aa772005SRobert Watson if (strlcpy(curhost->adh_directory, $2, 424*aa772005SRobert Watson sizeof(curhost->adh_directory)) >= 425*aa772005SRobert Watson sizeof(curhost->adh_directory)) { 426*aa772005SRobert Watson pjdlog_error("Directory value is too long."); 427*aa772005SRobert Watson free($2); 428*aa772005SRobert Watson return (1); 429*aa772005SRobert Watson } 430*aa772005SRobert Watson } else /* if (cursection == SECTION_RECEIVER) */ { 431*aa772005SRobert Watson if (depth1_directory[0] == '\0') { 432*aa772005SRobert Watson pjdlog_error("Directory path must be absolute."); 433*aa772005SRobert Watson free($2); 434*aa772005SRobert Watson return (1); 435*aa772005SRobert Watson } 436*aa772005SRobert Watson if (snprintf(curhost->adh_directory, 437*aa772005SRobert Watson sizeof(curhost->adh_directory), "%s/%s", 438*aa772005SRobert Watson depth1_directory, $2) >= 439*aa772005SRobert Watson (ssize_t)sizeof(curhost->adh_directory)) { 440*aa772005SRobert Watson pjdlog_error("Directory value is too long."); 441*aa772005SRobert Watson free($2); 442*aa772005SRobert Watson return (1); 443*aa772005SRobert Watson } 444*aa772005SRobert Watson } 445*aa772005SRobert Watson break; 446*aa772005SRobert Watson default: 447*aa772005SRobert Watson PJDLOG_ABORT("directory at wrong depth level"); 448*aa772005SRobert Watson } 449*aa772005SRobert Watson free($2); 450*aa772005SRobert Watson } 451*aa772005SRobert Watson ; 452*aa772005SRobert Watson 453*aa772005SRobert Watson source_statement: SOURCE STR 454*aa772005SRobert Watson { 455*aa772005SRobert Watson PJDLOG_RASSERT(cursection == SECTION_SENDER, 456*aa772005SRobert Watson "The source variable must be in sender section."); 457*aa772005SRobert Watson 458*aa772005SRobert Watson switch (depth) { 459*aa772005SRobert Watson case 1: 460*aa772005SRobert Watson if (strlcpy(depth1_source, $2, 461*aa772005SRobert Watson sizeof(depth1_source)) >= 462*aa772005SRobert Watson sizeof(depth1_source)) { 463*aa772005SRobert Watson pjdlog_error("Source value is too long."); 464*aa772005SRobert Watson free($2); 465*aa772005SRobert Watson return (1); 466*aa772005SRobert Watson } 467*aa772005SRobert Watson break; 468*aa772005SRobert Watson case 2: 469*aa772005SRobert Watson if (strlcpy(curhost->adh_localaddr, $2, 470*aa772005SRobert Watson sizeof(curhost->adh_localaddr)) >= 471*aa772005SRobert Watson sizeof(curhost->adh_localaddr)) { 472*aa772005SRobert Watson pjdlog_error("Source value is too long."); 473*aa772005SRobert Watson free($2); 474*aa772005SRobert Watson return (1); 475*aa772005SRobert Watson } 476*aa772005SRobert Watson break; 477*aa772005SRobert Watson } 478*aa772005SRobert Watson free($2); 479*aa772005SRobert Watson } 480*aa772005SRobert Watson ; 481*aa772005SRobert Watson 482*aa772005SRobert Watson fingerprint_statement: FINGERPRINT STR 483*aa772005SRobert Watson { 484*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_SENDER); 485*aa772005SRobert Watson PJDLOG_ASSERT(depth == 2); 486*aa772005SRobert Watson 487*aa772005SRobert Watson if (strncasecmp($2, "SHA256=", 7) != 0) { 488*aa772005SRobert Watson pjdlog_error("Invalid fingerprint value."); 489*aa772005SRobert Watson free($2); 490*aa772005SRobert Watson return (1); 491*aa772005SRobert Watson } 492*aa772005SRobert Watson if (strlcpy(curhost->adh_fingerprint, $2, 493*aa772005SRobert Watson sizeof(curhost->adh_fingerprint)) >= 494*aa772005SRobert Watson sizeof(curhost->adh_fingerprint)) { 495*aa772005SRobert Watson pjdlog_error("Fingerprint value is too long."); 496*aa772005SRobert Watson free($2); 497*aa772005SRobert Watson return (1); 498*aa772005SRobert Watson } 499*aa772005SRobert Watson free($2); 500*aa772005SRobert Watson } 501*aa772005SRobert Watson ; 502*aa772005SRobert Watson 503*aa772005SRobert Watson password_statement: PASSWORD STR 504*aa772005SRobert Watson { 505*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_SENDER || 506*aa772005SRobert Watson cursection == SECTION_RECEIVER); 507*aa772005SRobert Watson PJDLOG_ASSERT(depth == 2); 508*aa772005SRobert Watson 509*aa772005SRobert Watson if (strlcpy(curhost->adh_password, $2, 510*aa772005SRobert Watson sizeof(curhost->adh_password)) >= 511*aa772005SRobert Watson sizeof(curhost->adh_password)) { 512*aa772005SRobert Watson pjdlog_error("Password value is too long."); 513*aa772005SRobert Watson bzero($2, strlen($2)); 514*aa772005SRobert Watson free($2); 515*aa772005SRobert Watson return (1); 516*aa772005SRobert Watson } 517*aa772005SRobert Watson bzero($2, strlen($2)); 518*aa772005SRobert Watson free($2); 519*aa772005SRobert Watson } 520*aa772005SRobert Watson ; 521*aa772005SRobert Watson 522*aa772005SRobert Watson certfile_statement: CERTFILE STR 523*aa772005SRobert Watson { 524*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_RECEIVER); 525*aa772005SRobert Watson PJDLOG_ASSERT(depth == 1); 526*aa772005SRobert Watson 527*aa772005SRobert Watson if (strlcpy(lconfig->adc_certfile, $2, 528*aa772005SRobert Watson sizeof(lconfig->adc_certfile)) >= 529*aa772005SRobert Watson sizeof(lconfig->adc_certfile)) { 530*aa772005SRobert Watson pjdlog_error("Certfile value is too long."); 531*aa772005SRobert Watson free($2); 532*aa772005SRobert Watson return (1); 533*aa772005SRobert Watson } 534*aa772005SRobert Watson free($2); 535*aa772005SRobert Watson } 536*aa772005SRobert Watson ; 537*aa772005SRobert Watson 538*aa772005SRobert Watson keyfile_statement: KEYFILE STR 539*aa772005SRobert Watson { 540*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_RECEIVER); 541*aa772005SRobert Watson PJDLOG_ASSERT(depth == 1); 542*aa772005SRobert Watson 543*aa772005SRobert Watson if (strlcpy(lconfig->adc_keyfile, $2, 544*aa772005SRobert Watson sizeof(lconfig->adc_keyfile)) >= 545*aa772005SRobert Watson sizeof(lconfig->adc_keyfile)) { 546*aa772005SRobert Watson pjdlog_error("Keyfile value is too long."); 547*aa772005SRobert Watson free($2); 548*aa772005SRobert Watson return (1); 549*aa772005SRobert Watson } 550*aa772005SRobert Watson free($2); 551*aa772005SRobert Watson } 552*aa772005SRobert Watson ; 553*aa772005SRobert Watson 554*aa772005SRobert Watson listen_statement: LISTEN STR 555*aa772005SRobert Watson { 556*aa772005SRobert Watson struct adist_listen *lst; 557*aa772005SRobert Watson 558*aa772005SRobert Watson PJDLOG_ASSERT(depth == 1); 559*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_RECEIVER); 560*aa772005SRobert Watson 561*aa772005SRobert Watson lst = calloc(1, sizeof(*lst)); 562*aa772005SRobert Watson if (lst == NULL) { 563*aa772005SRobert Watson pjdlog_error("Unable to allocate memory for listen address."); 564*aa772005SRobert Watson free($2); 565*aa772005SRobert Watson return (1); 566*aa772005SRobert Watson } 567*aa772005SRobert Watson if (strlcpy(lst->adl_addr, $2, sizeof(lst->adl_addr)) >= 568*aa772005SRobert Watson sizeof(lst->adl_addr)) { 569*aa772005SRobert Watson pjdlog_error("listen argument is too long."); 570*aa772005SRobert Watson free($2); 571*aa772005SRobert Watson free(lst); 572*aa772005SRobert Watson return (1); 573*aa772005SRobert Watson } 574*aa772005SRobert Watson TAILQ_INSERT_TAIL(&lconfig->adc_listen, lst, adl_next); 575*aa772005SRobert Watson free($2); 576*aa772005SRobert Watson } 577*aa772005SRobert Watson ; 578*aa772005SRobert Watson 579*aa772005SRobert Watson sender_host_statement: HOST host_start OB sender_host_entries CB 580*aa772005SRobert Watson { 581*aa772005SRobert Watson /* Put it onto host list. */ 582*aa772005SRobert Watson TAILQ_INSERT_TAIL(&lconfig->adc_hosts, curhost, adh_next); 583*aa772005SRobert Watson curhost = NULL; 584*aa772005SRobert Watson } 585*aa772005SRobert Watson ; 586*aa772005SRobert Watson 587*aa772005SRobert Watson receiver_host_statement: HOST host_start OB receiver_host_entries CB 588*aa772005SRobert Watson { 589*aa772005SRobert Watson /* Put it onto host list. */ 590*aa772005SRobert Watson TAILQ_INSERT_TAIL(&lconfig->adc_hosts, curhost, adh_next); 591*aa772005SRobert Watson curhost = NULL; 592*aa772005SRobert Watson } 593*aa772005SRobert Watson ; 594*aa772005SRobert Watson 595*aa772005SRobert Watson host_start: STR 596*aa772005SRobert Watson { 597*aa772005SRobert Watson /* Check if there is no duplicate entry. */ 598*aa772005SRobert Watson TAILQ_FOREACH(curhost, &lconfig->adc_hosts, adh_next) { 599*aa772005SRobert Watson if (strcmp(curhost->adh_name, $1) != 0) 600*aa772005SRobert Watson continue; 601*aa772005SRobert Watson if (curhost->adh_role == ADIST_ROLE_SENDER && 602*aa772005SRobert Watson cursection == SECTION_RECEIVER) { 603*aa772005SRobert Watson continue; 604*aa772005SRobert Watson } 605*aa772005SRobert Watson if (curhost->adh_role == ADIST_ROLE_RECEIVER && 606*aa772005SRobert Watson cursection == SECTION_SENDER) { 607*aa772005SRobert Watson continue; 608*aa772005SRobert Watson } 609*aa772005SRobert Watson pjdlog_error("%s host %s is configured more than once.", 610*aa772005SRobert Watson curhost->adh_role == ADIST_ROLE_SENDER ? 611*aa772005SRobert Watson "Sender" : "Receiver", curhost->adh_name); 612*aa772005SRobert Watson free($1); 613*aa772005SRobert Watson return (1); 614*aa772005SRobert Watson } 615*aa772005SRobert Watson 616*aa772005SRobert Watson curhost = calloc(1, sizeof(*curhost)); 617*aa772005SRobert Watson if (curhost == NULL) { 618*aa772005SRobert Watson pjdlog_error("Unable to allocate memory for host configuration."); 619*aa772005SRobert Watson free($1); 620*aa772005SRobert Watson return (1); 621*aa772005SRobert Watson } 622*aa772005SRobert Watson if (strlcpy(curhost->adh_name, $1, sizeof(curhost->adh_name)) >= 623*aa772005SRobert Watson sizeof(curhost->adh_name)) { 624*aa772005SRobert Watson pjdlog_error("Host name is too long."); 625*aa772005SRobert Watson free($1); 626*aa772005SRobert Watson return (1); 627*aa772005SRobert Watson } 628*aa772005SRobert Watson free($1); 629*aa772005SRobert Watson curhost->adh_role = cursection == SECTION_SENDER ? 630*aa772005SRobert Watson ADIST_ROLE_SENDER : ADIST_ROLE_RECEIVER; 631*aa772005SRobert Watson curhost->adh_version = ADIST_VERSION; 632*aa772005SRobert Watson curhost->adh_localaddr[0] = '\0'; 633*aa772005SRobert Watson curhost->adh_remoteaddr[0] = '\0'; 634*aa772005SRobert Watson curhost->adh_remote = NULL; 635*aa772005SRobert Watson curhost->adh_directory[0] = '\0'; 636*aa772005SRobert Watson curhost->adh_password[0] = '\0'; 637*aa772005SRobert Watson curhost->adh_fingerprint[0] = '\0'; 638*aa772005SRobert Watson curhost->adh_worker_pid = 0; 639*aa772005SRobert Watson curhost->adh_conn = NULL; 640*aa772005SRobert Watson } 641*aa772005SRobert Watson ; 642*aa772005SRobert Watson 643*aa772005SRobert Watson sender_host_entries: 644*aa772005SRobert Watson | 645*aa772005SRobert Watson sender_host_entries sender_host_entry 646*aa772005SRobert Watson ; 647*aa772005SRobert Watson 648*aa772005SRobert Watson sender_host_entry: 649*aa772005SRobert Watson source_statement 650*aa772005SRobert Watson | 651*aa772005SRobert Watson remote_statement 652*aa772005SRobert Watson | 653*aa772005SRobert Watson directory_statement 654*aa772005SRobert Watson | 655*aa772005SRobert Watson fingerprint_statement 656*aa772005SRobert Watson | 657*aa772005SRobert Watson password_statement 658*aa772005SRobert Watson /* 659*aa772005SRobert Watson | 660*aa772005SRobert Watson checksum_statement 661*aa772005SRobert Watson | 662*aa772005SRobert Watson compression_statement 663*aa772005SRobert Watson */ 664*aa772005SRobert Watson ; 665*aa772005SRobert Watson 666*aa772005SRobert Watson receiver_host_entries: 667*aa772005SRobert Watson | 668*aa772005SRobert Watson receiver_host_entries receiver_host_entry 669*aa772005SRobert Watson ; 670*aa772005SRobert Watson 671*aa772005SRobert Watson receiver_host_entry: 672*aa772005SRobert Watson remote_statement 673*aa772005SRobert Watson | 674*aa772005SRobert Watson directory_statement 675*aa772005SRobert Watson | 676*aa772005SRobert Watson password_statement 677*aa772005SRobert Watson ; 678*aa772005SRobert Watson 679*aa772005SRobert Watson remote_statement: REMOTE STR 680*aa772005SRobert Watson { 681*aa772005SRobert Watson PJDLOG_ASSERT(depth == 2); 682*aa772005SRobert Watson PJDLOG_ASSERT(cursection == SECTION_SENDER || 683*aa772005SRobert Watson cursection == SECTION_RECEIVER); 684*aa772005SRobert Watson 685*aa772005SRobert Watson if (strlcpy(curhost->adh_remoteaddr, $2, 686*aa772005SRobert Watson sizeof(curhost->adh_remoteaddr)) >= 687*aa772005SRobert Watson sizeof(curhost->adh_remoteaddr)) { 688*aa772005SRobert Watson pjdlog_error("Remote value is too long."); 689*aa772005SRobert Watson free($2); 690*aa772005SRobert Watson return (1); 691*aa772005SRobert Watson } 692*aa772005SRobert Watson free($2); 693*aa772005SRobert Watson } 694*aa772005SRobert Watson ; 695*aa772005SRobert Watson 696*aa772005SRobert Watson %% 697*aa772005SRobert Watson 698*aa772005SRobert Watson static bool 699*aa772005SRobert Watson family_supported(int family) 700*aa772005SRobert Watson { 701*aa772005SRobert Watson int sock; 702*aa772005SRobert Watson 703*aa772005SRobert Watson sock = socket(family, SOCK_STREAM, 0); 704*aa772005SRobert Watson if (sock == -1 && errno == EPROTONOSUPPORT) 705*aa772005SRobert Watson return (false); 706*aa772005SRobert Watson if (sock >= 0) 707*aa772005SRobert Watson (void)close(sock); 708*aa772005SRobert Watson return (true); 709*aa772005SRobert Watson } 710*aa772005SRobert Watson 711*aa772005SRobert Watson static bool 712*aa772005SRobert Watson adjust_directory(char *path) 713*aa772005SRobert Watson { 714*aa772005SRobert Watson size_t len; 715*aa772005SRobert Watson 716*aa772005SRobert Watson len = strlen(path); 717*aa772005SRobert Watson for (;;) { 718*aa772005SRobert Watson if (len == 0) { 719*aa772005SRobert Watson pjdlog_error("Directory path is empty."); 720*aa772005SRobert Watson return (false); 721*aa772005SRobert Watson } 722*aa772005SRobert Watson if (path[len - 1] != '/') 723*aa772005SRobert Watson break; 724*aa772005SRobert Watson len--; 725*aa772005SRobert Watson path[len] = '\0'; 726*aa772005SRobert Watson } 727*aa772005SRobert Watson if (path[0] != '/') { 728*aa772005SRobert Watson pjdlog_error("Directory path must be absolute."); 729*aa772005SRobert Watson return (false); 730*aa772005SRobert Watson } 731*aa772005SRobert Watson return (true); 732*aa772005SRobert Watson } 733*aa772005SRobert Watson 734*aa772005SRobert Watson static int 735*aa772005SRobert Watson my_name(char *name, size_t size) 736*aa772005SRobert Watson { 737*aa772005SRobert Watson char buf[MAXHOSTNAMELEN]; 738*aa772005SRobert Watson char *pos; 739*aa772005SRobert Watson 740*aa772005SRobert Watson if (gethostname(buf, sizeof(buf)) < 0) { 741*aa772005SRobert Watson pjdlog_errno(LOG_ERR, "gethostname() failed"); 742*aa772005SRobert Watson return (-1); 743*aa772005SRobert Watson } 744*aa772005SRobert Watson 745*aa772005SRobert Watson /* First component of the host name. */ 746*aa772005SRobert Watson pos = strchr(buf, '.'); 747*aa772005SRobert Watson if (pos == NULL) 748*aa772005SRobert Watson (void)strlcpy(name, buf, size); 749*aa772005SRobert Watson else 750*aa772005SRobert Watson (void)strlcpy(name, buf, MIN((size_t)(pos - buf + 1), size)); 751*aa772005SRobert Watson 752*aa772005SRobert Watson if (name[0] == '\0') { 753*aa772005SRobert Watson pjdlog_error("Empty host name."); 754*aa772005SRobert Watson return (-1); 755*aa772005SRobert Watson } 756*aa772005SRobert Watson 757*aa772005SRobert Watson return (0); 758*aa772005SRobert Watson } 759*aa772005SRobert Watson 760*aa772005SRobert Watson void 761*aa772005SRobert Watson yyerror(const char *str) 762*aa772005SRobert Watson { 763*aa772005SRobert Watson 764*aa772005SRobert Watson pjdlog_error("Unable to parse configuration file at line %d near '%s': %s", 765*aa772005SRobert Watson lineno, yytext, str); 766*aa772005SRobert Watson } 767*aa772005SRobert Watson 768*aa772005SRobert Watson struct adist_config * 769*aa772005SRobert Watson yy_config_parse(const char *config, bool exitonerror) 770*aa772005SRobert Watson { 771*aa772005SRobert Watson int ret; 772*aa772005SRobert Watson 773*aa772005SRobert Watson curhost = NULL; 774*aa772005SRobert Watson cursection = SECTION_GLOBAL; 775*aa772005SRobert Watson depth = 0; 776*aa772005SRobert Watson lineno = 0; 777*aa772005SRobert Watson 778*aa772005SRobert Watson lconfig = calloc(1, sizeof(*lconfig)); 779*aa772005SRobert Watson if (lconfig == NULL) { 780*aa772005SRobert Watson pjdlog_error("Unable to allocate memory for configuration."); 781*aa772005SRobert Watson if (exitonerror) 782*aa772005SRobert Watson exit(EX_TEMPFAIL); 783*aa772005SRobert Watson return (NULL); 784*aa772005SRobert Watson } 785*aa772005SRobert Watson TAILQ_INIT(&lconfig->adc_hosts); 786*aa772005SRobert Watson TAILQ_INIT(&lconfig->adc_listen); 787*aa772005SRobert Watson lconfig->adc_name[0] = '\0'; 788*aa772005SRobert Watson lconfig->adc_timeout = -1; 789*aa772005SRobert Watson lconfig->adc_pidfile[0] = '\0'; 790*aa772005SRobert Watson lconfig->adc_certfile[0] = '\0'; 791*aa772005SRobert Watson lconfig->adc_keyfile[0] = '\0'; 792*aa772005SRobert Watson 793*aa772005SRobert Watson yyin = fopen(config, "r"); 794*aa772005SRobert Watson if (yyin == NULL) { 795*aa772005SRobert Watson pjdlog_errno(LOG_ERR, "Unable to open configuration file %s", 796*aa772005SRobert Watson config); 797*aa772005SRobert Watson yy_config_free(lconfig); 798*aa772005SRobert Watson if (exitonerror) 799*aa772005SRobert Watson exit(EX_OSFILE); 800*aa772005SRobert Watson return (NULL); 801*aa772005SRobert Watson } 802*aa772005SRobert Watson yyrestart(yyin); 803*aa772005SRobert Watson ret = yyparse(); 804*aa772005SRobert Watson fclose(yyin); 805*aa772005SRobert Watson if (ret != 0) { 806*aa772005SRobert Watson yy_config_free(lconfig); 807*aa772005SRobert Watson if (exitonerror) 808*aa772005SRobert Watson exit(EX_CONFIG); 809*aa772005SRobert Watson return (NULL); 810*aa772005SRobert Watson } 811*aa772005SRobert Watson 812*aa772005SRobert Watson /* 813*aa772005SRobert Watson * Let's see if everything is set up. 814*aa772005SRobert Watson */ 815*aa772005SRobert Watson if (lconfig->adc_name[0] == '\0' && my_name(lconfig->adc_name, 816*aa772005SRobert Watson sizeof(lconfig->adc_name)) == -1) { 817*aa772005SRobert Watson yy_config_free(lconfig); 818*aa772005SRobert Watson if (exitonerror) 819*aa772005SRobert Watson exit(EX_CONFIG); 820*aa772005SRobert Watson return (NULL); 821*aa772005SRobert Watson } 822*aa772005SRobert Watson if (lconfig->adc_timeout == -1) 823*aa772005SRobert Watson lconfig->adc_timeout = ADIST_TIMEOUT; 824*aa772005SRobert Watson if (lconfig->adc_pidfile[0] == '\0') { 825*aa772005SRobert Watson (void)strlcpy(lconfig->adc_pidfile, ADIST_PIDFILE, 826*aa772005SRobert Watson sizeof(lconfig->adc_pidfile)); 827*aa772005SRobert Watson } 828*aa772005SRobert Watson if (lconfig->adc_certfile[0] == '\0') { 829*aa772005SRobert Watson (void)strlcpy(lconfig->adc_certfile, ADIST_CERTFILE, 830*aa772005SRobert Watson sizeof(lconfig->adc_certfile)); 831*aa772005SRobert Watson } 832*aa772005SRobert Watson if (lconfig->adc_keyfile[0] == '\0') { 833*aa772005SRobert Watson (void)strlcpy(lconfig->adc_keyfile, ADIST_KEYFILE, 834*aa772005SRobert Watson sizeof(lconfig->adc_keyfile)); 835*aa772005SRobert Watson } 836*aa772005SRobert Watson 837*aa772005SRobert Watson return (lconfig); 838*aa772005SRobert Watson } 839*aa772005SRobert Watson 840*aa772005SRobert Watson void 841*aa772005SRobert Watson yy_config_free(struct adist_config *config) 842*aa772005SRobert Watson { 843*aa772005SRobert Watson struct adist_host *adhost; 844*aa772005SRobert Watson struct adist_listen *lst; 845*aa772005SRobert Watson 846*aa772005SRobert Watson while ((lst = TAILQ_FIRST(&config->adc_listen)) != NULL) { 847*aa772005SRobert Watson TAILQ_REMOVE(&config->adc_listen, lst, adl_next); 848*aa772005SRobert Watson free(lst); 849*aa772005SRobert Watson } 850*aa772005SRobert Watson while ((adhost = TAILQ_FIRST(&config->adc_hosts)) != NULL) { 851*aa772005SRobert Watson TAILQ_REMOVE(&config->adc_hosts, adhost, adh_next); 852*aa772005SRobert Watson bzero(adhost, sizeof(*adhost)); 853*aa772005SRobert Watson free(adhost); 854*aa772005SRobert Watson } 855*aa772005SRobert Watson free(config); 856*aa772005SRobert Watson } 857