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