xref: /lighttpd1.4/src/mod_authn_pam.c (revision 49e9f0ac)
1 #include "first.h"
2 
3 /* mod_authn_pam
4  *
5  * FUTURE POTENTIAL PERFORMANCE ENHANCEMENTS:
6  * - database response is not cached
7  *   TODO: db response caching (for limited time) to reduce load on db
8  *     (only cache successful logins to prevent cache bloat?)
9  *     (or limit number of entries (size) of cache)
10  *     (maybe have negative cache (limited size) of names not found in database)
11  * - database query is synchronous and blocks waiting for response
12  */
13 
14 #include <security/pam_appl.h>
15 
16 #include "base.h"
17 #include "http_auth.h"
18 #include "log.h"
19 #include "plugin.h"
20 
21 #include <stdlib.h>
22 #include <string.h>
23 
24 typedef struct {
25     array *opts;
26     const char *service;
27 } plugin_config;
28 
29 typedef struct {
30     PLUGIN_DATA;
31     plugin_config **config_storage;
32     plugin_config conf;
33 } plugin_data;
34 
35 static handler_t mod_authn_pam_basic(server *srv, connection *con, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw);
36 
37 INIT_FUNC(mod_authn_pam_init) {
38     static http_auth_backend_t http_auth_backend_pam =
39       { "pam", mod_authn_pam_basic, NULL, NULL };
40     plugin_data *p = calloc(1, sizeof(*p));
41 
42     /* register http_auth_backend_pam */
43     http_auth_backend_pam.p_d = p;
44     http_auth_backend_set(&http_auth_backend_pam);
45 
46     return p;
47 }
48 
49 FREE_FUNC(mod_authn_pam_free) {
50     plugin_data *p = p_d;
51     if (!p) return HANDLER_GO_ON;
52 
53     if (p->config_storage) {
54         for (size_t i = 0; i < srv->config_context->used; ++i) {
55             plugin_config *s = p->config_storage[i];
56             if (NULL == s) continue;
57             array_free(s->opts);
58             free(s);
59         }
60         free(p->config_storage);
61     }
62     free(p);
63     UNUSED(srv);
64     return HANDLER_GO_ON;
65 }
66 
67 SETDEFAULTS_FUNC(mod_authn_pam_set_defaults) {
68     plugin_data *p = p_d;
69     config_values_t cv[] = {
70         { "auth.backend.pam.opts",          NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
71         { NULL,                             NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
72     };
73 
74     p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
75 
76     for (size_t i = 0; i < srv->config_context->used; ++i) {
77         data_config const *config = (data_config const*)srv->config_context->data[i];
78         data_string *ds;
79         plugin_config *s = calloc(1, sizeof(plugin_config));
80         s->opts = array_init();
81 
82         cv[0].destination = s->opts;
83 
84         p->config_storage[i] = s;
85 
86         if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
87             return HANDLER_ERROR;
88         }
89 
90         if (0 == s->opts->used) continue;
91 
92         ds = (data_string *)
93           array_get_element_klen(s->opts, CONST_STR_LEN("service"));
94         s->service = (NULL != ds) ? ds->value->ptr : "http";
95     }
96 
97     if (p->config_storage[0]->service == NULL)
98         p->config_storage[0]->service = "http";
99 
100     return HANDLER_GO_ON;
101 }
102 
103 #define PATCH(x) \
104     p->conf.x = s->x;
105 static int mod_authn_pam_patch_connection(server *srv, connection *con, plugin_data *p) {
106     plugin_config *s = p->config_storage[0];
107     PATCH(service);
108 
109     /* skip the first, the global context */
110     for (size_t i = 1; i < srv->config_context->used; ++i) {
111         data_config *dc = (data_config *)srv->config_context->data[i];
112 
113         /* condition didn't match */
114         if (!config_check_cond(srv, con, dc)) continue;
115 
116         /* merge config */
117         s = p->config_storage[i];
118         for (size_t j = 0; j < dc->value->used; ++j) {
119             data_unset *du = dc->value->data[j];
120             if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.pam.opts"))) {
121                 PATCH(service);
122             }
123         }
124     }
125 
126     return 0;
127 }
128 #undef PATCH
129 
130 static int mod_authn_pam_fn_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)  {
131     const char * const pw = (char *)appdata_ptr;
132     struct pam_response * const pr = *resp =
133       (struct pam_response *)malloc(num_msg * sizeof(struct pam_response));
134     for (int i = 0; i < num_msg; ++i) {
135         const int style = msg[i]->msg_style;
136         pr[i].resp_retcode = 0;
137         pr[i].resp = (style==PAM_PROMPT_ECHO_OFF || style==PAM_PROMPT_ECHO_ON)
138           ? strdup(pw)
139           : NULL;
140     }
141     return PAM_SUCCESS;
142 }
143 
144 static handler_t mod_authn_pam_query(server *srv, connection *con, void *p_d, const buffer *username, const char *realm, const char *pw) {
145     plugin_data *p = (plugin_data *)p_d;
146     pam_handle_t *pamh = NULL;
147     struct pam_conv conv = { mod_authn_pam_fn_conv, NULL };
148     const int flags = PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK;
149     int rc;
150     UNUSED(realm);
151     *(const char **)&conv.appdata_ptr = pw; /*(cast away const)*/
152 
153     mod_authn_pam_patch_connection(srv, con, p);
154 
155     rc = pam_start(p->conf.service, username->ptr, &conv, &pamh);
156     if (PAM_SUCCESS != rc
157      || PAM_SUCCESS !=(rc = pam_set_item(pamh,PAM_RHOST,con->dst_addr_buf->ptr))
158      || PAM_SUCCESS !=(rc = pam_authenticate(pamh, flags))
159      || PAM_SUCCESS !=(rc = pam_acct_mgmt(pamh, flags)))
160         log_error_write(srv, __FILE__, __LINE__, "ss",
161                         "pam:", pam_strerror(pamh, rc));
162     pam_end(pamh, rc);
163     return (PAM_SUCCESS == rc) ? HANDLER_GO_ON : HANDLER_ERROR;
164 }
165 
166 static handler_t mod_authn_pam_basic(server *srv, connection *con, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw) {
167     char *realm = require->realm->ptr;
168     handler_t rc = mod_authn_pam_query(srv, con, p_d, username, realm, pw);
169     if (HANDLER_GO_ON != rc) return rc;
170     return http_auth_match_rules(require, username->ptr, NULL, NULL)
171       ? HANDLER_GO_ON  /* access granted */
172       : HANDLER_ERROR;
173 }
174 
175 int mod_authn_pam_plugin_init(plugin *p);
176 int mod_authn_pam_plugin_init(plugin *p) {
177     p->version     = LIGHTTPD_VERSION_ID;
178     p->name        = buffer_init_string("authn_pam");
179     p->data        = NULL;
180     p->init        = mod_authn_pam_init;
181     p->cleanup     = mod_authn_pam_free;
182     p->set_defaults= mod_authn_pam_set_defaults;
183 
184     return 0;
185 }
186