1/* -------------------------------------------------------------------------- ** 2 * Microsoft Network Services for Unix, AKA., Andrew Tridgell's SAMBA. 3 * 4 * This module Copyright (C) 1990-1998 Karl Auer 5 * 6 * Rewritten almost completely by Christopher R. Hertel 7 * at the University of Minnesota, September, 1997. 8 * This module Copyright (C) 1997-1998 by the University of Minnesota 9 * -------------------------------------------------------------------------- ** 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation; either version 2 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program; if not, write to the Free Software 23 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 24 * 25 * -------------------------------------------------------------------------- ** 26 * 27 * Module name: params 28 * 29 * -------------------------------------------------------------------------- ** 30 * 31 * This module performs lexical analysis and initial parsing of a 32 * Windows-like parameter file. It recognizes and handles four token 33 * types: section-name, parameter-name, parameter-value, and 34 * end-of-file. Comments and line continuation are handled 35 * internally. 36 * 37 * The entry point to the module is function pm_process(). This 38 * function opens the source file, calls the Parse() function to parse 39 * the input, and then closes the file when either the EOF is reached 40 * or a fatal error is encountered. 41 * 42 * A sample parameter file might look like this: 43 * 44 * [section one] 45 * parameter one = value string 46 * parameter two = another value 47 * [section two] 48 * new parameter = some value or t'other 49 * 50 * The parameter file is divided into sections by section headers: 51 * section names enclosed in square brackets (eg. [section one]). 52 * Each section contains parameter lines, each of which consist of a 53 * parameter name and value delimited by an equal sign. Roughly, the 54 * syntax is: 55 * 56 * <file> :== { <section> } EOF 57 * 58 * <section> :== <section header> { <parameter line> } 59 * 60 * <section header> :== '[' NAME ']' 61 * 62 * <parameter line> :== NAME '=' VALUE '\n' 63 * 64 * Blank lines and comment lines are ignored. Comment lines are lines 65 * beginning with either a semicolon (';') or a pound sign ('#'). 66 * 67 * All whitespace in section names and parameter names is compressed 68 * to single spaces. Leading and trailing whitespace is stipped from 69 * both names and values. 70 * 71 * Only the first equals sign in a parameter line is significant. 72 * Parameter values may contain equals signs, square brackets and 73 * semicolons. Internal whitespace is retained in parameter values, 74 * with the exception of the '\r' character, which is stripped for 75 * historic reasons. Parameter names may not start with a left square 76 * bracket, an equal sign, a pound sign, or a semicolon, because these 77 * are used to identify other tokens. 78 * 79 * -------------------------------------------------------------------------- ** 80 */ 81 82#include "includes.h" 83 84/* -------------------------------------------------------------------------- ** 85 * Constants... 86 */ 87 88#define BUFR_INC 1024 89 90 91/* -------------------------------------------------------------------------- ** 92 * Variables... 93 * 94 * DEBUGLEVEL - The ubiquitous DEBUGLEVEL. This determines which DEBUG() 95 * messages will be produced. 96 * bufr - pointer to a global buffer. This is probably a kludge, 97 * but it was the nicest kludge I could think of (for now). 98 * bSize - The size of the global buffer <bufr>. 99 */ 100 101static char *bufr = NULL; 102static int bSize = 0; 103 104/* we can't use FILE* due to the 256 fd limit - use this cheap hack 105 instead */ 106typedef struct { 107 char *buf; 108 char *p; 109 size_t size; 110} myFILE; 111 112static int mygetc(myFILE *f) 113{ 114 if (f->p >= f->buf+f->size) return EOF; 115 /* be sure to return chars >127 as positive values */ 116 return (int)( *(f->p++) & 0x00FF ); 117} 118 119static void myfile_close(myFILE *f) 120{ 121 if (!f) return; 122 SAFE_FREE(f->buf); 123 SAFE_FREE(f); 124} 125 126/* -------------------------------------------------------------------------- ** 127 * Functions... 128 */ 129 130static int EatWhitespace( myFILE *InFile ) 131 /* ------------------------------------------------------------------------ ** 132 * Scan past whitespace (see ctype(3C)) and return the first non-whitespace 133 * character, or newline, or EOF. 134 * 135 * Input: InFile - Input source. 136 * 137 * Output: The next non-whitespace character in the input stream. 138 * 139 * Notes: Because the config files use a line-oriented grammar, we 140 * explicitly exclude the newline character from the list of 141 * whitespace characters. 142 * - Note that both EOF (-1) and the nul character ('\0') are 143 * considered end-of-file markers. 144 * 145 * ------------------------------------------------------------------------ ** 146 */ 147 { 148 int c; 149 150 for( c = mygetc( InFile ); isspace( c ) && ('\n' != c); c = mygetc( InFile ) ) 151 ; 152 return( c ); 153 } /* EatWhitespace */ 154 155static int EatComment( myFILE *InFile ) 156 /* ------------------------------------------------------------------------ ** 157 * Scan to the end of a comment. 158 * 159 * Input: InFile - Input source. 160 * 161 * Output: The character that marks the end of the comment. Normally, 162 * this will be a newline, but it *might* be an EOF. 163 * 164 * Notes: Because the config files use a line-oriented grammar, we 165 * explicitly exclude the newline character from the list of 166 * whitespace characters. 167 * - Note that both EOF (-1) and the nul character ('\0') are 168 * considered end-of-file markers. 169 * 170 * ------------------------------------------------------------------------ ** 171 */ 172 { 173 int c; 174 175 for( c = mygetc( InFile ); ('\n'!=c) && (EOF!=c) && (c>0); c = mygetc( InFile ) ) 176 ; 177 return( c ); 178 } /* EatComment */ 179 180/***************************************************************************** 181 * Scan backards within a string to discover if the last non-whitespace 182 * character is a line-continuation character ('\\'). 183 * 184 * Input: line - A pointer to a buffer containing the string to be 185 * scanned. 186 * pos - This is taken to be the offset of the end of the 187 * string. This position is *not* scanned. 188 * 189 * Output: The offset of the '\\' character if it was found, or -1 to 190 * indicate that it was not. 191 * 192 *****************************************************************************/ 193 194static int Continuation(char *line, int pos ) 195{ 196 pos--; 197 while( (pos >= 0) && isspace((int)line[pos])) 198 pos--; 199 200 return (((pos >= 0) && ('\\' == line[pos])) ? pos : -1 ); 201} 202 203 204static BOOL Section( myFILE *InFile, BOOL (*sfunc)(const char *) ) 205 /* ------------------------------------------------------------------------ ** 206 * Scan a section name, and pass the name to function sfunc(). 207 * 208 * Input: InFile - Input source. 209 * sfunc - Pointer to the function to be called if the section 210 * name is successfully read. 211 * 212 * Output: True if the section name was read and True was returned from 213 * <sfunc>. False if <sfunc> failed or if a lexical error was 214 * encountered. 215 * 216 * ------------------------------------------------------------------------ ** 217 */ 218 { 219 int c; 220 int i; 221 int end; 222 const char *func = "params.c:Section() -"; 223 224 i = 0; /* <i> is the offset of the next free byte in bufr[] and */ 225 end = 0; /* <end> is the current "end of string" offset. In most */ 226 /* cases these will be the same, but if the last */ 227 /* character written to bufr[] is a space, then <end> */ 228 /* will be one less than <i>. */ 229 230 c = EatWhitespace( InFile ); /* We've already got the '['. Scan */ 231 /* past initial white space. */ 232 233 while( (EOF != c) && (c > 0) ) 234 { 235 236 /* Check that the buffer is big enough for the next character. */ 237 if( i > (bSize - 2) ) 238 { 239 char *tb; 240 241 tb = Realloc( bufr, bSize +BUFR_INC ); 242 if( NULL == tb ) 243 { 244 DEBUG(0, ("%s Memory re-allocation failure.", func) ); 245 return( False ); 246 } 247 bufr = tb; 248 bSize += BUFR_INC; 249 } 250 251 /* Handle a single character. */ 252 switch( c ) 253 { 254 case ']': /* Found the closing bracket. */ 255 bufr[end] = '\0'; 256 if( 0 == end ) /* Don't allow an empty name. */ 257 { 258 DEBUG(0, ("%s Empty section name in configuration file.\n", func )); 259 return( False ); 260 } 261 if( !sfunc(bufr) ) /* Got a valid name. Deal with it. */ 262 return( False ); 263 (void)EatComment( InFile ); /* Finish off the line. */ 264 return( True ); 265 266 case '\n': /* Got newline before closing ']'. */ 267 i = Continuation( bufr, i ); /* Check for line continuation. */ 268 if( i < 0 ) 269 { 270 bufr[end] = '\0'; 271 DEBUG(0, ("%s Badly formed line in configuration file: %s\n", 272 func, bufr )); 273 return( False ); 274 } 275 end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i); 276 c = mygetc( InFile ); /* Continue with next line. */ 277 break; 278 279 default: /* All else are a valid name chars. */ 280 if( isspace( c ) ) /* One space per whitespace region. */ 281 { 282 bufr[end] = ' '; 283 i = end + 1; 284 c = EatWhitespace( InFile ); 285 } 286 else /* All others copy verbatim. */ 287 { 288 bufr[i++] = c; 289 end = i; 290 c = mygetc( InFile ); 291 } 292 } 293 } 294 295 /* We arrive here if we've met the EOF before the closing bracket. */ 296 DEBUG(0, ("%s Unexpected EOF in the configuration file: %s\n", func, bufr )); 297 return( False ); 298 } /* Section */ 299 300static BOOL Parameter( myFILE *InFile, BOOL (*pfunc)(const char *, const char *), int c ) 301 /* ------------------------------------------------------------------------ ** 302 * Scan a parameter name and value, and pass these two fields to pfunc(). 303 * 304 * Input: InFile - The input source. 305 * pfunc - A pointer to the function that will be called to 306 * process the parameter, once it has been scanned. 307 * c - The first character of the parameter name, which 308 * would have been read by Parse(). Unlike a comment 309 * line or a section header, there is no lead-in 310 * character that can be discarded. 311 * 312 * Output: True if the parameter name and value were scanned and processed 313 * successfully, else False. 314 * 315 * Notes: This function is in two parts. The first loop scans the 316 * parameter name. Internal whitespace is compressed, and an 317 * equal sign (=) terminates the token. Leading and trailing 318 * whitespace is discarded. The second loop scans the parameter 319 * value. When both have been successfully identified, they are 320 * passed to pfunc() for processing. 321 * 322 * ------------------------------------------------------------------------ ** 323 */ 324 { 325 int i = 0; /* Position within bufr. */ 326 int end = 0; /* bufr[end] is current end-of-string. */ 327 int vstart = 0; /* Starting position of the parameter value. */ 328 const char *func = "params.c:Parameter() -"; 329 330 /* Read the parameter name. */ 331 while( 0 == vstart ) /* Loop until we've found the start of the value. */ 332 { 333 334 if( i > (bSize - 2) ) /* Ensure there's space for next char. */ 335 { 336 char *tb; 337 338 tb = Realloc( bufr, bSize + BUFR_INC ); 339 if( NULL == tb ) 340 { 341 DEBUG(0, ("%s Memory re-allocation failure.", func) ); 342 return( False ); 343 } 344 bufr = tb; 345 bSize += BUFR_INC; 346 } 347 348 switch( c ) 349 { 350 case '=': /* Equal sign marks end of param name. */ 351 if( 0 == end ) /* Don't allow an empty name. */ 352 { 353 DEBUG(0, ("%s Invalid parameter name in config. file.\n", func )); 354 return( False ); 355 } 356 bufr[end++] = '\0'; /* Mark end of string & advance. */ 357 i = end; /* New string starts here. */ 358 vstart = end; /* New string is parameter value. */ 359 bufr[i] = '\0'; /* New string is nul, for now. */ 360 break; 361 362 case '\n': /* Find continuation char, else error. */ 363 i = Continuation( bufr, i ); 364 if( i < 0 ) 365 { 366 bufr[end] = '\0'; 367 DEBUG(1,("%s Ignoring badly formed line in configuration file: %s\n", 368 func, bufr )); 369 return( True ); 370 } 371 end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i); 372 c = mygetc( InFile ); /* Read past eoln. */ 373 break; 374 375 case '\0': /* Shouldn't have EOF within param name. */ 376 case EOF: 377 bufr[i] = '\0'; 378 DEBUG(1,("%s Unexpected end-of-file at: %s\n", func, bufr )); 379 return( True ); 380 381 default: 382 if( isspace( c ) ) /* One ' ' per whitespace region. */ 383 { 384 bufr[end] = ' '; 385 i = end + 1; 386 c = EatWhitespace( InFile ); 387 } 388 else /* All others verbatim. */ 389 { 390 bufr[i++] = c; 391 end = i; 392 c = mygetc( InFile ); 393 } 394 } 395 } 396 397 /* Now parse the value. */ 398 c = EatWhitespace( InFile ); /* Again, trim leading whitespace. */ 399 while( (EOF !=c) && (c > 0) ) 400 { 401 402 if( i > (bSize - 2) ) /* Make sure there's enough room. */ 403 { 404 char *tb; 405 406 tb = Realloc( bufr, bSize + BUFR_INC ); 407 if( NULL == tb ) 408 { 409 DEBUG(0, ("%s Memory re-allocation failure.", func) ); 410 return( False ); 411 } 412 bufr = tb; 413 bSize += BUFR_INC; 414 } 415 416 switch( c ) 417 { 418 case '\r': /* Explicitly remove '\r' because the older */ 419 c = mygetc( InFile ); /* version called fgets_slash() which also */ 420 break; /* removes them. */ 421 422 case '\n': /* Marks end of value unless there's a '\'. */ 423 i = Continuation( bufr, i ); 424 if( i < 0 ) 425 c = 0; 426 else 427 { 428 for( end = i; (end >= 0) && isspace((int)bufr[end]); end-- ) 429 ; 430 c = mygetc( InFile ); 431 } 432 break; 433 434 default: /* All others verbatim. Note that spaces do */ 435 bufr[i++] = c; /* not advance <end>. This allows trimming */ 436 if( !isspace( c ) ) /* of whitespace at the end of the line. */ 437 end = i; 438 c = mygetc( InFile ); 439 break; 440 } 441 } 442 bufr[end] = '\0'; /* End of value. */ 443 444 return( pfunc( bufr, &bufr[vstart] ) ); /* Pass name & value to pfunc(). */ 445 } /* Parameter */ 446 447static BOOL Parse( myFILE *InFile, 448 BOOL (*sfunc)(const char *), 449 BOOL (*pfunc)(const char *, const char *) ) 450 /* ------------------------------------------------------------------------ ** 451 * Scan & parse the input. 452 * 453 * Input: InFile - Input source. 454 * sfunc - Function to be called when a section name is scanned. 455 * See Section(). 456 * pfunc - Function to be called when a parameter is scanned. 457 * See Parameter(). 458 * 459 * Output: True if the file was successfully scanned, else False. 460 * 461 * Notes: The input can be viewed in terms of 'lines'. There are four 462 * types of lines: 463 * Blank - May contain whitespace, otherwise empty. 464 * Comment - First non-whitespace character is a ';' or '#'. 465 * The remainder of the line is ignored. 466 * Section - First non-whitespace character is a '['. 467 * Parameter - The default case. 468 * 469 * ------------------------------------------------------------------------ ** 470 */ 471 { 472 int c; 473 474 c = EatWhitespace( InFile ); 475 while( (EOF != c) && (c > 0) ) 476 { 477 switch( c ) 478 { 479 case '\n': /* Blank line. */ 480 c = EatWhitespace( InFile ); 481 break; 482 483 case ';': /* Comment line. */ 484 case '#': 485 c = EatComment( InFile ); 486 break; 487 488 case '[': /* Section Header. */ 489 if( !Section( InFile, sfunc ) ) 490 return( False ); 491 c = EatWhitespace( InFile ); 492 break; 493 494 case '\\': /* Bogus backslash. */ 495 c = EatWhitespace( InFile ); 496 break; 497 498 default: /* Parameter line. */ 499 if( !Parameter( InFile, pfunc, c ) ) 500 return( False ); 501 c = EatWhitespace( InFile ); 502 break; 503 } 504 } 505 return( True ); 506 } /* Parse */ 507 508static myFILE *OpenConfFile( const char *FileName ) 509 /* ------------------------------------------------------------------------ ** 510 * Open a configuration file. 511 * 512 * Input: FileName - The pathname of the config file to be opened. 513 * 514 * Output: A pointer of type (char **) to the lines of the file 515 * 516 * ------------------------------------------------------------------------ ** 517 */ 518 { 519 const char *func = "params.c:OpenConfFile() -"; 520 extern BOOL in_client; 521 int lvl = in_client?1:0; 522 myFILE *ret; 523 524 ret = (myFILE *)malloc(sizeof(*ret)); 525 if (!ret) return NULL; 526 527 ret->buf = file_load(FileName, &ret->size); 528 if( NULL == ret->buf ) 529 { 530 DEBUG( lvl, 531 ("%s Unable to open configuration file \"%s\":\n\t%s\n", 532 func, FileName, strerror(errno)) ); 533 SAFE_FREE(ret); 534 return NULL; 535 } 536 537 ret->p = ret->buf; 538 return( ret ); 539 } /* OpenConfFile */ 540 541BOOL pm_process( const char *FileName, 542 BOOL (*sfunc)(const char *), 543 BOOL (*pfunc)(const char *, const char *) ) 544 /* ------------------------------------------------------------------------ ** 545 * Process the named parameter file. 546 * 547 * Input: FileName - The pathname of the parameter file to be opened. 548 * sfunc - A pointer to a function that will be called when 549 * a section name is discovered. 550 * pfunc - A pointer to a function that will be called when 551 * a parameter name and value are discovered. 552 * 553 * Output: TRUE if the file was successfully parsed, else FALSE. 554 * 555 * ------------------------------------------------------------------------ ** 556 */ 557 { 558 int result; 559 myFILE *InFile; 560 const char *func = "params.c:pm_process() -"; 561 562 InFile = OpenConfFile( FileName ); /* Open the config file. */ 563 if( NULL == InFile ) 564 return( False ); 565 566 DEBUG( 3, ("%s Processing configuration file \"%s\"\n", func, FileName) ); 567 568 if( NULL != bufr ) /* If we already have a buffer */ 569 result = Parse( InFile, sfunc, pfunc ); /* (recursive call), then just */ 570 /* use it. */ 571 572 else /* If we don't have a buffer */ 573 { /* allocate one, then parse, */ 574 bSize = BUFR_INC; /* then free. */ 575 bufr = (char *)malloc( bSize ); 576 if( NULL == bufr ) 577 { 578 DEBUG(0,("%s memory allocation failure.\n", func)); 579 myfile_close(InFile); 580 return( False ); 581 } 582 result = Parse( InFile, sfunc, pfunc ); 583 SAFE_FREE( bufr ); 584 bufr = NULL; 585 bSize = 0; 586 } 587 588 myfile_close(InFile); 589 590 if( !result ) /* Generic failure. */ 591 { 592 DEBUG(0,("%s Failed. Error returned from params.c:parse().\n", func)); 593 return( False ); 594 } 595 596 return( True ); /* Generic success. */ 597 } /* pm_process */ 598 599/* -------------------------------------------------------------------------- */ 600