1 /*- 2 * Copyright (c) 2012 Dag-Erling Smørgrav 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote 14 * products derived from this software without specific prior written 15 * permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 * $Id: openpam_readword.c 648 2013-03-05 17:54:27Z des $ 30 */ 31 32 #ifdef HAVE_CONFIG_H 33 # include "config.h" 34 #endif 35 36 #include <errno.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 40 #include <security/pam_appl.h> 41 42 #include "openpam_impl.h" 43 #include "openpam_ctype.h" 44 45 #define MIN_WORD_SIZE 32 46 47 /* 48 * OpenPAM extension 49 * 50 * Read a word from a file, respecting shell quoting rules. 51 */ 52 53 char * 54 openpam_readword(FILE *f, int *lineno, size_t *lenp) 55 { 56 char *word; 57 size_t size, len; 58 int ch, escape, quote; 59 int serrno; 60 61 errno = 0; 62 63 /* skip initial whitespace */ 64 escape = quote = 0; 65 while ((ch = getc(f)) != EOF) { 66 if (ch == '\n') { 67 /* either EOL or line continuation */ 68 if (!escape) 69 break; 70 if (lineno != NULL) 71 ++*lineno; 72 escape = 0; 73 } else if (escape) { 74 /* escaped something else */ 75 break; 76 } else if (ch == '#') { 77 /* comment: until EOL, no continuation */ 78 while ((ch = getc(f)) != EOF) 79 if (ch == '\n') 80 break; 81 break; 82 } else if (ch == '\\') { 83 escape = 1; 84 } else if (!is_ws(ch)) { 85 break; 86 } 87 } 88 if (ch == EOF) 89 return (NULL); 90 ungetc(ch, f); 91 if (ch == '\n') 92 return (NULL); 93 94 word = NULL; 95 size = len = 0; 96 while ((ch = fgetc(f)) != EOF && (!is_ws(ch) || quote || escape)) { 97 if (ch == '\\' && !escape && quote != '\'') { 98 /* escape next character */ 99 escape = ch; 100 } else if ((ch == '\'' || ch == '"') && !quote && !escape) { 101 /* begin quote */ 102 quote = ch; 103 /* edge case: empty quoted string */ 104 if (openpam_straddch(&word, &size, &len, 0) != 0) 105 return (NULL); 106 } else if (ch == quote && !escape) { 107 /* end quote */ 108 quote = 0; 109 } else if (ch == '\n' && escape) { 110 /* line continuation */ 111 escape = 0; 112 } else { 113 if (escape && quote && ch != '\\' && ch != quote && 114 openpam_straddch(&word, &size, &len, '\\') != 0) { 115 free(word); 116 errno = ENOMEM; 117 return (NULL); 118 } 119 if (openpam_straddch(&word, &size, &len, ch) != 0) { 120 free(word); 121 errno = ENOMEM; 122 return (NULL); 123 } 124 escape = 0; 125 } 126 if (lineno != NULL && ch == '\n') 127 ++*lineno; 128 } 129 if (ch == EOF && ferror(f)) { 130 serrno = errno; 131 free(word); 132 errno = serrno; 133 return (NULL); 134 } 135 if (ch == EOF && (escape || quote)) { 136 /* Missing escaped character or closing quote. */ 137 openpam_log(PAM_LOG_ERROR, "unexpected end of file"); 138 free(word); 139 errno = EINVAL; 140 return (NULL); 141 } 142 ungetc(ch, f); 143 if (lenp != NULL) 144 *lenp = len; 145 return (word); 146 } 147 148 /** 149 * The =openpam_readword function reads the next word from a file, and 150 * returns it in a NUL-terminated buffer allocated with =!malloc. 151 * 152 * A word is a sequence of non-whitespace characters. 153 * However, whitespace characters can be included in a word if quoted or 154 * escaped according to the following rules: 155 * 156 * - An unescaped single or double quote introduces a quoted string, 157 * which ends when the same quote character is encountered a second 158 * time. 159 * The quotes themselves are stripped. 160 * 161 * - Within a single- or double-quoted string, all whitespace characters, 162 * including the newline character, are preserved as-is. 163 * 164 * - Outside a quoted string, a backslash escapes the next character, 165 * which is preserved as-is, unless that character is a newline, in 166 * which case it is discarded and reading continues at the beginning of 167 * the next line as if the backslash and newline had not been there. 168 * In all cases, the backslash itself is discarded. 169 * 170 * - Within a single-quoted string, double quotes and backslashes are 171 * preserved as-is. 172 * 173 * - Within a double-quoted string, a single quote is preserved as-is, 174 * and a backslash is preserved as-is unless used to escape a double 175 * quote. 176 * 177 * In addition, if the first non-whitespace character on the line is a 178 * hash character (#), the rest of the line is discarded. 179 * If a hash character occurs within a word, however, it is preserved 180 * as-is. 181 * A backslash at the end of a comment does cause line continuation. 182 * 183 * If =lineno is not =NULL, the integer variable it points to is 184 * incremented every time a quoted or escaped newline character is read. 185 * 186 * If =lenp is not =NULL, the length of the word (after quotes and 187 * backslashes have been removed) is stored in the variable it points to. 188 * 189 * RETURN VALUES 190 * 191 * If successful, the =openpam_readword function returns a pointer to a 192 * dynamically allocated NUL-terminated string containing the first word 193 * encountered on the line. 194 * 195 * The caller is responsible for releasing the returned buffer by passing 196 * it to =!free. 197 * 198 * If =openpam_readword reaches the end of the line or file before any 199 * characters are copied to the word, it returns =NULL. In the former 200 * case, the newline is pushed back to the file. 201 * 202 * If =openpam_readword reaches the end of the file while a quote or 203 * backslash escape is in effect, it sets :errno to =EINVAL and returns 204 * =NULL. 205 * 206 * IMPLEMENTATION NOTES 207 * 208 * The parsing rules are intended to be equivalent to the normal POSIX 209 * shell quoting rules. 210 * Any discrepancy is a bug and should be reported to the author along 211 * with sample input that can be used to reproduce the error. 212 * 213 * >openpam_readline 214 * >openpam_readlinev 215 * 216 * AUTHOR DES 217 */ 218