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