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