1 /*
2 * Logging functions for PAM modules.
3 *
4 * Logs errors and debugging messages from PAM modules. The debug versions
5 * only log anything if debugging was enabled; the crit and err versions
6 * always log.
7 *
8 * The canonical version of this file is maintained in the rra-c-util package,
9 * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
10 *
11 * Written by Russ Allbery <eagle@eyrie.org>
12 * Copyright 2015, 2018, 2020 Russ Allbery <eagle@eyrie.org>
13 * Copyright 2005-2007, 2009-2010, 2012-2013
14 * The Board of Trustees of the Leland Stanford Junior University
15 *
16 * Permission is hereby granted, free of charge, to any person obtaining a
17 * copy of this software and associated documentation files (the "Software"),
18 * to deal in the Software without restriction, including without limitation
19 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
20 * and/or sell copies of the Software, and to permit persons to whom the
21 * Software is furnished to do so, subject to the following conditions:
22 *
23 * The above copyright notice and this permission notice shall be included in
24 * all copies or substantial portions of the Software.
25 *
26 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
29 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
31 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
32 * DEALINGS IN THE SOFTWARE.
33 *
34 * SPDX-License-Identifier: MIT
35 */
36
37 #include <config.h>
38 #ifdef HAVE_KRB5
39 # include <portable/krb5.h>
40 #endif
41 #include <portable/pam.h>
42 #include <portable/system.h>
43
44 #include <syslog.h>
45
46 #include <pam-util/args.h>
47 #include <pam-util/logging.h>
48
49 #ifndef LOG_AUTHPRIV
50 # define LOG_AUTHPRIV LOG_AUTH
51 #endif
52
53 /* Used for iterating through arrays. */
54 #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
55
56 /*
57 * Mappings of PAM flags to symbolic names for logging when entering a PAM
58 * module function.
59 */
60 static const struct {
61 int flag;
62 const char *name;
63 } FLAGS[] = {
64 /* clang-format off */
65 {PAM_CHANGE_EXPIRED_AUTHTOK, "expired" },
66 {PAM_DELETE_CRED, "delete" },
67 {PAM_DISALLOW_NULL_AUTHTOK, "nonull" },
68 {PAM_ESTABLISH_CRED, "establish"},
69 {PAM_PRELIM_CHECK, "prelim" },
70 {PAM_REFRESH_CRED, "refresh" },
71 {PAM_REINITIALIZE_CRED, "reinit" },
72 {PAM_SILENT, "silent" },
73 {PAM_UPDATE_AUTHTOK, "update" },
74 /* clang-format on */
75 };
76
77
78 /*
79 * Utility function to format a message into newly allocated memory, reporting
80 * an error via syslog if vasprintf fails.
81 */
82 static char *__attribute__((__format__(printf, 1, 0)))
format(const char * fmt,va_list args)83 format(const char *fmt, va_list args)
84 {
85 char *msg;
86
87 if (vasprintf(&msg, fmt, args) < 0) {
88 syslog(LOG_CRIT | LOG_AUTHPRIV, "vasprintf failed: %m");
89 return NULL;
90 }
91 return msg;
92 }
93
94
95 /*
96 * Log wrapper function that adds the user. Log a message with the given
97 * priority, prefixed by (user <user>) with the account name being
98 * authenticated if known.
99 */
100 static void __attribute__((__format__(printf, 3, 0)))
log_vplain(struct pam_args * pargs,int priority,const char * fmt,va_list args)101 log_vplain(struct pam_args *pargs, int priority, const char *fmt, va_list args)
102 {
103 char *msg;
104
105 if (priority == LOG_DEBUG && (pargs == NULL || !pargs->debug))
106 return;
107 if (pargs != NULL && pargs->user != NULL) {
108 msg = format(fmt, args);
109 if (msg == NULL)
110 return;
111 pam_syslog(pargs->pamh, priority, "(user %s) %s", pargs->user, msg);
112 free(msg);
113 } else if (pargs != NULL) {
114 pam_vsyslog(pargs->pamh, priority, fmt, args);
115 } else {
116 msg = format(fmt, args);
117 if (msg == NULL)
118 return;
119 syslog(priority | LOG_AUTHPRIV, "%s", msg);
120 free(msg);
121 }
122 }
123
124
125 /*
126 * Wrapper around log_vplain with variadic arguments.
127 */
128 static void __attribute__((__format__(printf, 3, 4)))
log_plain(struct pam_args * pargs,int priority,const char * fmt,...)129 log_plain(struct pam_args *pargs, int priority, const char *fmt, ...)
130 {
131 va_list args;
132
133 va_start(args, fmt);
134 log_vplain(pargs, priority, fmt, args);
135 va_end(args);
136 }
137
138
139 /*
140 * Log wrapper function for reporting a PAM error. Log a message with the
141 * given priority, prefixed by (user <user>) with the account name being
142 * authenticated if known, followed by a colon and the formatted PAM error.
143 * However, do not include the colon and the PAM error if the PAM status is
144 * PAM_SUCCESS.
145 */
146 static void __attribute__((__format__(printf, 4, 0)))
log_pam(struct pam_args * pargs,int priority,int status,const char * fmt,va_list args)147 log_pam(struct pam_args *pargs, int priority, int status, const char *fmt,
148 va_list args)
149 {
150 char *msg;
151
152 if (priority == LOG_DEBUG && (pargs == NULL || !pargs->debug))
153 return;
154 msg = format(fmt, args);
155 if (msg == NULL)
156 return;
157 if (pargs == NULL)
158 log_plain(NULL, priority, "%s", msg);
159 else if (status == PAM_SUCCESS)
160 log_plain(pargs, priority, "%s", msg);
161 else
162 log_plain(pargs, priority, "%s: %s", msg,
163 pam_strerror(pargs->pamh, status));
164 free(msg);
165 }
166
167
168 /*
169 * The public interfaces. For each common log level (crit, err, and debug),
170 * generate a putil_<level> function and one for _pam. Do this with the
171 * preprocessor to save duplicate code.
172 */
173 /* clang-format off */
174 #define LOG_FUNCTION(level, priority) \
175 void __attribute__((__format__(printf, 2, 3))) \
176 putil_ ## level(struct pam_args *pargs, const char *fmt, ...) \
177 { \
178 va_list args; \
179 \
180 va_start(args, fmt); \
181 log_vplain(pargs, priority, fmt, args); \
182 va_end(args); \
183 } \
184 void __attribute__((__format__(printf, 3, 4))) \
185 putil_ ## level ## _pam(struct pam_args *pargs, int status, \
186 const char *fmt, ...) \
187 { \
188 va_list args; \
189 \
190 va_start(args, fmt); \
191 log_pam(pargs, priority, status, fmt, args); \
192 va_end(args); \
193 }
LOG_FUNCTION(crit,LOG_CRIT)194 LOG_FUNCTION(crit, LOG_CRIT)
195 LOG_FUNCTION(err, LOG_ERR)
196 LOG_FUNCTION(notice, LOG_NOTICE)
197 LOG_FUNCTION(debug, LOG_DEBUG)
198 /* clang-format on */
199
200
201 /*
202 * Report entry into a function. Takes the PAM arguments, the function name,
203 * and the flags and maps the flags to symbolic names.
204 */
205 void
206 putil_log_entry(struct pam_args *pargs, const char *func, int flags)
207 {
208 size_t i, length, offset;
209 char *out = NULL, *nout;
210
211 if (!pargs->debug)
212 return;
213 if (flags != 0)
214 for (i = 0; i < ARRAY_SIZE(FLAGS); i++) {
215 if (!(flags & FLAGS[i].flag))
216 continue;
217 if (out == NULL) {
218 out = strdup(FLAGS[i].name);
219 if (out == NULL)
220 break;
221 } else {
222 length = strlen(FLAGS[i].name);
223 nout = realloc(out, strlen(out) + length + 2);
224 if (nout == NULL) {
225 free(out);
226 out = NULL;
227 break;
228 }
229 out = nout;
230 offset = strlen(out);
231 out[offset] = '|';
232 memcpy(out + offset + 1, FLAGS[i].name, length);
233 out[offset + 1 + length] = '\0';
234 }
235 }
236 if (out == NULL)
237 pam_syslog(pargs->pamh, LOG_DEBUG, "%s: entry", func);
238 else {
239 pam_syslog(pargs->pamh, LOG_DEBUG, "%s: entry (%s)", func, out);
240 free(out);
241 }
242 }
243
244
245 /*
246 * Report an authentication failure. This is a separate function since we
247 * want to include various PAM metadata in the log message and put it in a
248 * standard format. The format here is modeled after the pam_unix
249 * authentication failure message from Linux PAM.
250 */
251 void __attribute__((__format__(printf, 2, 3)))
putil_log_failure(struct pam_args * pargs,const char * fmt,...)252 putil_log_failure(struct pam_args *pargs, const char *fmt, ...)
253 {
254 char *msg;
255 va_list args;
256 const char *ruser = NULL;
257 const char *rhost = NULL;
258 const char *tty = NULL;
259 const char *name = NULL;
260
261 if (pargs->user != NULL)
262 name = pargs->user;
263 va_start(args, fmt);
264 msg = format(fmt, args);
265 va_end(args);
266 if (msg == NULL)
267 return;
268 pam_get_item(pargs->pamh, PAM_RUSER, (PAM_CONST void **) &ruser);
269 pam_get_item(pargs->pamh, PAM_RHOST, (PAM_CONST void **) &rhost);
270 pam_get_item(pargs->pamh, PAM_TTY, (PAM_CONST void **) &tty);
271
272 /* clang-format off */
273 pam_syslog(pargs->pamh, LOG_NOTICE, "%s; logname=%s uid=%ld euid=%ld"
274 " tty=%s ruser=%s rhost=%s", msg,
275 (name != NULL) ? name : "",
276 (long) getuid(), (long) geteuid(),
277 (tty != NULL) ? tty : "",
278 (ruser != NULL) ? ruser : "",
279 (rhost != NULL) ? rhost : "");
280 /* clang-format on */
281
282 free(msg);
283 }
284
285
286 /*
287 * Below are the additional logging functions enabled if built with Kerberos
288 * support, used to report Kerberos errors.
289 */
290 #ifdef HAVE_KRB5
291
292
293 /*
294 * Log wrapper function for reporting a Kerberos error. Log a message with
295 * the given priority, prefixed by (user <user>) with the account name being
296 * authenticated if known, followed by a colon and the formatted Kerberos
297 * error.
298 */
299 __attribute__((__format__(printf, 4, 0))) static void
log_krb5(struct pam_args * pargs,int priority,int status,const char * fmt,va_list args)300 log_krb5(struct pam_args *pargs, int priority, int status, const char *fmt,
301 va_list args)
302 {
303 char *msg;
304 const char *k5_msg = NULL;
305
306 if (priority == LOG_DEBUG && (pargs == NULL || !pargs->debug))
307 return;
308 msg = format(fmt, args);
309 if (msg == NULL)
310 return;
311 if (pargs != NULL && pargs->ctx != NULL) {
312 k5_msg = krb5_get_error_message(pargs->ctx, status);
313 log_plain(pargs, priority, "%s: %s", msg, k5_msg);
314 } else {
315 log_plain(pargs, priority, "%s", msg);
316 }
317 free(msg);
318 if (k5_msg != NULL)
319 krb5_free_error_message(pargs->ctx, k5_msg);
320 }
321
322
323 /*
324 * The public interfaces. Do this with the preprocessor to save duplicate
325 * code.
326 */
327 /* clang-format off */
328 #define LOG_FUNCTION_KRB5(level, priority) \
329 void __attribute__((__format__(printf, 3, 4))) \
330 putil_ ## level ## _krb5(struct pam_args *pargs, int status, \
331 const char *fmt, ...) \
332 { \
333 va_list args; \
334 \
335 va_start(args, fmt); \
336 log_krb5(pargs, priority, status, fmt, args); \
337 va_end(args); \
338 }
339 LOG_FUNCTION_KRB5(crit, LOG_CRIT)
340 LOG_FUNCTION_KRB5(err, LOG_ERR)
341 LOG_FUNCTION_KRB5(notice, LOG_NOTICE)
342 LOG_FUNCTION_KRB5(debug, LOG_DEBUG)
343 /* clang-format on */
344
345 #endif /* HAVE_KRB5 */
346