1 /*- 2 * Copyright (c) 2012-2017 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 30 #ifdef HAVE_CONFIG_H 31 # include "config.h" 32 #endif 33 34 #include <errno.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 38 #include <security/pam_appl.h> 39 40 #include "openpam_impl.h" 41 #include "openpam_ctype.h" 42 43 #define MIN_WORD_SIZE 32 44 45 /* 46 * OpenPAM extension 47 * 48 * Read a word from a file, respecting shell quoting rules. 49 */ 50 51 char * 52 openpam_readword(FILE *f, int *lineno, size_t *lenp) 53 { 54 char *word; 55 size_t size, len; 56 int ch, escape, quote; 57 int serrno; 58 59 errno = 0; 60 61 /* skip initial whitespace */ 62 escape = quote = 0; 63 while ((ch = getc(f)) != EOF) { 64 if (ch == '\n') { 65 /* either EOL or line continuation */ 66 if (!escape) 67 break; 68 if (lineno != NULL) 69 ++*lineno; 70 escape = 0; 71 } else if (escape) { 72 /* escaped something else */ 73 break; 74 } else if (ch == '#') { 75 /* comment: until EOL, no continuation */ 76 while ((ch = getc(f)) != EOF) 77 if (ch == '\n') 78 break; 79 break; 80 } else if (ch == '\\') { 81 escape = 1; 82 } else if (!is_ws(ch)) { 83 break; 84 } 85 } 86 if (ch == EOF) 87 return (NULL); 88 ungetc(ch, f); 89 if (ch == '\n') 90 return (NULL); 91 92 word = NULL; 93 size = len = 0; 94 while ((ch = fgetc(f)) != EOF && (!is_ws(ch) || quote || escape)) { 95 if (ch == '\\' && !escape && quote != '\'') { 96 /* escape next character */ 97 escape = ch; 98 } else if ((ch == '\'' || ch == '"') && !quote && !escape) { 99 /* begin quote */ 100 quote = ch; 101 /* edge case: empty quoted string */ 102 if (openpam_straddch(&word, &size, &len, 0) != 0) 103 return (NULL); 104 } else if (ch == quote && !escape) { 105 /* end quote */ 106 quote = 0; 107 } else if (ch == '\n' && escape) { 108 /* line continuation */ 109 escape = 0; 110 } else { 111 if (escape && quote && ch != '\\' && ch != quote && 112 openpam_straddch(&word, &size, &len, '\\') != 0) { 113 free(word); 114 errno = ENOMEM; 115 return (NULL); 116 } 117 if (openpam_straddch(&word, &size, &len, ch) != 0) { 118 free(word); 119 errno = ENOMEM; 120 return (NULL); 121 } 122 escape = 0; 123 } 124 if (lineno != NULL && ch == '\n') 125 ++*lineno; 126 } 127 if (ch == EOF && ferror(f)) { 128 serrno = errno; 129 free(word); 130 errno = serrno; 131 return (NULL); 132 } 133 if (ch == EOF && (escape || quote)) { 134 /* Missing escaped character or closing quote. */ 135 free(word); 136 errno = EINVAL; 137 return (NULL); 138 } 139 ungetc(ch, f); 140 if (lenp != NULL) 141 *lenp = len; 142 return (word); 143 } 144 145 /** 146 * The =openpam_readword function reads the next word from a file, and 147 * returns it in a NUL-terminated buffer allocated with =!malloc. 148 * 149 * A word is a sequence of non-whitespace characters. 150 * However, whitespace characters can be included in a word if quoted or 151 * escaped according to the following rules: 152 * 153 * - An unescaped single or double quote introduces a quoted string, 154 * which ends when the same quote character is encountered a second 155 * time. 156 * The quotes themselves are stripped. 157 * 158 * - Within a single- or double-quoted string, all whitespace characters, 159 * including the newline character, are preserved as-is. 160 * 161 * - Outside a quoted string, a backslash escapes the next character, 162 * which is preserved as-is, unless that character is a newline, in 163 * which case it is discarded and reading continues at the beginning of 164 * the next line as if the backslash and newline had not been there. 165 * In all cases, the backslash itself is discarded. 166 * 167 * - Within a single-quoted string, double quotes and backslashes are 168 * preserved as-is. 169 * 170 * - Within a double-quoted string, a single quote is preserved as-is, 171 * and a backslash is preserved as-is unless used to escape a double 172 * quote. 173 * 174 * In addition, if the first non-whitespace character on the line is a 175 * hash character (#), the rest of the line is discarded. 176 * If a hash character occurs within a word, however, it is preserved 177 * as-is. 178 * A backslash at the end of a comment does cause line continuation. 179 * 180 * If =lineno is not =NULL, the integer variable it points to is 181 * incremented every time a quoted or escaped newline character is read. 182 * 183 * If =lenp is not =NULL, the length of the word (after quotes and 184 * backslashes have been removed) is stored in the variable it points to. 185 * 186 * RETURN VALUES 187 * 188 * If successful, the =openpam_readword function returns a pointer to a 189 * dynamically allocated NUL-terminated string containing the first word 190 * encountered on the line. 191 * 192 * The caller is responsible for releasing the returned buffer by passing 193 * it to =!free. 194 * 195 * If =openpam_readword reaches the end of the line or file before any 196 * characters are copied to the word, it returns =NULL. In the former 197 * case, the newline is pushed back to the file. 198 * 199 * If =openpam_readword reaches the end of the file while a quote or 200 * backslash escape is in effect, it sets :errno to =EINVAL and returns 201 * =NULL. 202 * 203 * IMPLEMENTATION NOTES 204 * 205 * The parsing rules are intended to be equivalent to the normal POSIX 206 * shell quoting rules. 207 * Any discrepancy is a bug and should be reported to the author along 208 * with sample input that can be used to reproduce the error. 209 * 210 * >openpam_readline 211 * >openpam_readlinev 212 * 213 * AUTHOR DES 214 */ 215