1/* Licensed to the Apache Software Foundation (ASF) under one or more
2* contributor license agreements.  See the NOTICE file distributed with
3* this work for additional information regarding copyright ownership.
4* The ASF licenses this file to You under the Apache License, Version 2.0
5* (the "License"); you may not use this file except in compliance with
6* the License.  You may obtain a copy of the License at
7*
8*     http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16
17
18#include "httpd.h"
19#include "http_config.h"
20
21#include "apr.h"
22#include "apu_version.h"
23
24/* apr_memcache support requires >= 1.3 */
25#if APU_MAJOR_VERSION > 1 || \
26    (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION > 2)
27#define HAVE_APU_MEMCACHE 1
28#endif
29
30#ifdef HAVE_APU_MEMCACHE
31
32#include "ap_socache.h"
33#include "ap_mpm.h"
34#include "http_log.h"
35#include "apr_memcache.h"
36
37/* The underlying apr_memcache system is thread safe.. */
38#define MC_KEY_LEN 254
39
40#ifndef MC_DEFAULT_SERVER_PORT
41#define MC_DEFAULT_SERVER_PORT 11211
42#endif
43
44
45#ifndef MC_DEFAULT_SERVER_MIN
46#define MC_DEFAULT_SERVER_MIN 0
47#endif
48
49#ifndef MC_DEFAULT_SERVER_SMAX
50#define MC_DEFAULT_SERVER_SMAX 1
51#endif
52
53#ifndef MC_DEFAULT_SERVER_TTL
54#define MC_DEFAULT_SERVER_TTL 600
55#endif
56
57struct ap_socache_instance_t {
58    const char *servers;
59    apr_memcache_t *mc;
60    const char *tag;
61    apr_size_t taglen; /* strlen(tag) + 1 */
62};
63
64static const char *socache_mc_create(ap_socache_instance_t **context,
65                                     const char *arg,
66                                     apr_pool_t *tmp, apr_pool_t *p)
67{
68    ap_socache_instance_t *ctx;
69
70    *context = ctx = apr_palloc(p, sizeof *ctx);
71
72    if (!arg || !*arg) {
73        return "List of server names required to create memcache socache.";
74    }
75
76    ctx->servers = apr_pstrdup(p, arg);
77
78    return NULL;
79}
80
81static apr_status_t socache_mc_init(ap_socache_instance_t *ctx,
82                                    const char *namespace,
83                                    const struct ap_socache_hints *hints,
84                                    server_rec *s, apr_pool_t *p)
85{
86    apr_status_t rv;
87    int thread_limit = 0;
88    apr_uint16_t nservers = 0;
89    char *cache_config;
90    char *split;
91    char *tok;
92
93    ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
94
95    /* Find all the servers in the first run to get a total count */
96    cache_config = apr_pstrdup(p, ctx->servers);
97    split = apr_strtok(cache_config, ",", &tok);
98    while (split) {
99        nservers++;
100        split = apr_strtok(NULL,",", &tok);
101    }
102
103    rv = apr_memcache_create(p, nservers, 0, &ctx->mc);
104    if (rv != APR_SUCCESS) {
105        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00785)
106                     "Failed to create Memcache Object of '%d' size.",
107                     nservers);
108        return rv;
109    }
110
111    /* Now add each server to the memcache */
112    cache_config = apr_pstrdup(p, ctx->servers);
113    split = apr_strtok(cache_config, ",", &tok);
114    while (split) {
115        apr_memcache_server_t *st;
116        char *host_str;
117        char *scope_id;
118        apr_port_t port;
119
120        rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p);
121        if (rv != APR_SUCCESS) {
122            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00786)
123                         "Failed to Parse memcache Server: '%s'", split);
124            return rv;
125        }
126
127        if (host_str == NULL) {
128            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00787)
129                         "Failed to Parse Server, "
130                         "no hostname specified: '%s'", split);
131            return APR_EINVAL;
132        }
133
134        if (port == 0) {
135            port = MC_DEFAULT_SERVER_PORT;
136        }
137
138        rv = apr_memcache_server_create(p,
139                                        host_str, port,
140                                        MC_DEFAULT_SERVER_MIN,
141                                        MC_DEFAULT_SERVER_SMAX,
142                                        thread_limit,
143                                        MC_DEFAULT_SERVER_TTL,
144                                        &st);
145        if (rv != APR_SUCCESS) {
146            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00788)
147                         "Failed to Create memcache Server: %s:%d",
148                         host_str, port);
149            return rv;
150        }
151
152        rv = apr_memcache_add_server(ctx->mc, st);
153        if (rv != APR_SUCCESS) {
154            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00789)
155                         "Failed to Add memcache Server: %s:%d",
156                         host_str, port);
157            return rv;
158        }
159
160        split = apr_strtok(NULL,",", &tok);
161    }
162
163    ctx->tag = apr_pstrcat(p, namespace, ":", NULL);
164    ctx->taglen = strlen(ctx->tag) + 1;
165
166    /* socache API constraint: */
167    AP_DEBUG_ASSERT(ctx->taglen <= 16);
168
169    return APR_SUCCESS;
170}
171
172static void socache_mc_destroy(ap_socache_instance_t *context, server_rec *s)
173{
174    /* noop. */
175}
176
177/* Converts (binary) id into a key prefixed by the predetermined
178 * namespace tag; writes output to key buffer.  Returns non-zero if
179 * the id won't fit in the key buffer. */
180static int socache_mc_id2key(ap_socache_instance_t *ctx,
181                             const unsigned char *id, unsigned int idlen,
182                             char *key, apr_size_t keylen)
183{
184    char *cp;
185
186    if (idlen * 2 + ctx->taglen >= keylen)
187        return 1;
188
189    cp = apr_cpystrn(key, ctx->tag, ctx->taglen);
190    ap_bin2hex(id, idlen, cp);
191
192    return 0;
193}
194
195static apr_status_t socache_mc_store(ap_socache_instance_t *ctx, server_rec *s,
196                                     const unsigned char *id, unsigned int idlen,
197                                     apr_time_t expiry,
198                                     unsigned char *ucaData, unsigned int nData,
199                                     apr_pool_t *p)
200{
201    char buf[MC_KEY_LEN];
202    apr_status_t rv;
203
204    if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
205        return APR_EINVAL;
206    }
207
208    /* In APR-util - unclear what 'timeout' is, as it was not implemented */
209    rv = apr_memcache_set(ctx->mc, buf, (char*)ucaData, nData, 0, 0);
210
211    if (rv != APR_SUCCESS) {
212        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00790)
213                     "scache_mc: error setting key '%s' "
214                     "with %d bytes of data", buf, nData);
215        return rv;
216    }
217
218    return APR_SUCCESS;
219}
220
221static apr_status_t socache_mc_retrieve(ap_socache_instance_t *ctx, server_rec *s,
222                                        const unsigned char *id, unsigned int idlen,
223                                        unsigned char *dest, unsigned int *destlen,
224                                        apr_pool_t *p)
225{
226    apr_size_t data_len;
227    char buf[MC_KEY_LEN], *data;
228    apr_status_t rv;
229
230    if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
231        return APR_EINVAL;
232    }
233
234    /* ### this could do with a subpool, but _getp looks like it will
235     * eat memory like it's going out of fashion anyway. */
236
237    rv = apr_memcache_getp(ctx->mc, p, buf, &data, &data_len, NULL);
238    if (rv) {
239        if (rv != APR_NOTFOUND) {
240            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00791)
241                         "scache_mc: 'retrieve' FAIL");
242        }
243        return rv;
244    }
245    else if (data_len > *destlen) {
246        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00792)
247                     "scache_mc: 'retrieve' OVERFLOW");
248        return APR_ENOMEM;
249    }
250
251    memcpy(dest, data, data_len);
252    *destlen = data_len;
253
254    return APR_SUCCESS;
255}
256
257static apr_status_t socache_mc_remove(ap_socache_instance_t *ctx, server_rec *s,
258                                      const unsigned char *id,
259                                      unsigned int idlen, apr_pool_t *p)
260{
261    char buf[MC_KEY_LEN];
262    apr_status_t rv;
263
264    if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
265        return APR_EINVAL;
266    }
267
268    rv = apr_memcache_delete(ctx->mc, buf, 0);
269
270    if (rv != APR_SUCCESS) {
271        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00793)
272                     "scache_mc: error deleting key '%s' ",
273                     buf);
274    }
275
276    return rv;
277}
278
279static void socache_mc_status(ap_socache_instance_t *ctx, request_rec *r, int flags)
280{
281    /* TODO: Make a mod_status handler. meh. */
282}
283
284static apr_status_t socache_mc_iterate(ap_socache_instance_t *instance,
285                                       server_rec *s, void *userctx,
286                                       ap_socache_iterator_t *iterator,
287                                       apr_pool_t *pool)
288{
289    return APR_ENOTIMPL;
290}
291
292static const ap_socache_provider_t socache_mc = {
293    "memcache",
294    0,
295    socache_mc_create,
296    socache_mc_init,
297    socache_mc_destroy,
298    socache_mc_store,
299    socache_mc_retrieve,
300    socache_mc_remove,
301    socache_mc_status,
302    socache_mc_iterate
303};
304
305#endif /* HAVE_APU_MEMCACHE */
306
307static void register_hooks(apr_pool_t *p)
308{
309#ifdef HAVE_APU_MEMCACHE
310    ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "memcache",
311                         AP_SOCACHE_PROVIDER_VERSION,
312                         &socache_mc);
313#endif
314}
315
316AP_DECLARE_MODULE(socache_memcache) = {
317    STANDARD20_MODULE_STUFF,
318    NULL, NULL, NULL, NULL, NULL,
319    register_hooks
320};
321