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#ifdef WIN32
18
19#include "apr.h"
20#include "arch/win32/apr_arch_file_io.h"
21#include "arch/win32/apr_arch_misc.h"
22#include "ap_regkey.h"
23
24struct ap_regkey_t {
25    apr_pool_t *pool;
26    HKEY        hkey;
27};
28
29
30AP_DECLARE(const ap_regkey_t *) ap_regkey_const(int i)
31{
32    static struct ap_regkey_t ap_regkey_consts[7] =
33    {
34        {NULL, HKEY_CLASSES_ROOT},
35        {NULL, HKEY_CURRENT_CONFIG},
36        {NULL, HKEY_CURRENT_USER},
37        {NULL, HKEY_LOCAL_MACHINE},
38        {NULL, HKEY_USERS},
39        {NULL, HKEY_PERFORMANCE_DATA},
40        {NULL, HKEY_DYN_DATA}
41    };
42    return ap_regkey_consts + i;
43}
44
45
46static apr_status_t regkey_cleanup(void *key)
47{
48    ap_regkey_t *regkey = key;
49
50    if (regkey->hkey && regkey->hkey != INVALID_HANDLE_VALUE) {
51        RegCloseKey(regkey->hkey);
52        regkey->hkey = INVALID_HANDLE_VALUE;
53    }
54    return APR_SUCCESS;
55}
56
57
58AP_DECLARE(apr_status_t) ap_regkey_open(ap_regkey_t **newkey,
59                                        const ap_regkey_t *parentkey,
60                                        const char *keyname,
61                                        apr_int32_t flags,
62                                        apr_pool_t *pool)
63{
64    DWORD access = KEY_QUERY_VALUE;
65    DWORD exists;
66    HKEY hkey;
67    LONG rc;
68
69    if (flags & APR_READ)
70        access |= KEY_READ;
71    if (flags & APR_WRITE)
72        access |= KEY_WRITE;
73
74#if APR_HAS_UNICODE_FS
75    IF_WIN_OS_IS_UNICODE
76    {
77        apr_size_t keylen = strlen(keyname) + 1;
78        apr_size_t wkeylen = 256;
79        apr_wchar_t wkeyname[256];
80        apr_status_t rv = apr_conv_utf8_to_ucs2(keyname, &keylen, wkeyname, &wkeylen);
81        if (rv != APR_SUCCESS)
82            return rv;
83        else if (keylen)
84            return APR_ENAMETOOLONG;
85
86        if (flags & APR_CREATE)
87            rc = RegCreateKeyExW(parentkey->hkey, wkeyname, 0, NULL, 0,
88                                 access, NULL, &hkey, &exists);
89        else
90            rc = RegOpenKeyExW(parentkey->hkey, wkeyname, 0, access, &hkey);
91    }
92#endif /* APR_HAS_UNICODE_FS */
93#if APR_HAS_ANSI_FS
94    ELSE_WIN_OS_IS_ANSI
95    {
96        if (flags & APR_CREATE)
97            rc = RegCreateKeyEx(parentkey->hkey, keyname, 0, NULL, 0,
98                                access, NULL, &hkey, &exists);
99        else
100            rc = RegOpenKeyEx(parentkey->hkey, keyname, 0, access, &hkey);
101    }
102#endif
103    if (rc != ERROR_SUCCESS) {
104        return APR_FROM_OS_ERROR(rc);
105    }
106    if ((flags & APR_EXCL) && (exists == REG_OPENED_EXISTING_KEY)) {
107        RegCloseKey(hkey);
108        return APR_EEXIST;
109    }
110
111    *newkey = apr_palloc(pool, sizeof(**newkey));
112    (*newkey)->pool = pool;
113    (*newkey)->hkey = hkey;
114    apr_pool_cleanup_register((*newkey)->pool, (void *)(*newkey),
115                              regkey_cleanup, apr_pool_cleanup_null);
116    return APR_SUCCESS;
117}
118
119
120AP_DECLARE(apr_status_t) ap_regkey_close(ap_regkey_t *regkey)
121{
122    apr_status_t stat;
123    if ((stat = regkey_cleanup(regkey)) == APR_SUCCESS) {
124        apr_pool_cleanup_kill(regkey->pool, regkey, regkey_cleanup);
125    }
126    return stat;
127}
128
129
130AP_DECLARE(apr_status_t) ap_regkey_remove(const ap_regkey_t *parent,
131                                          const char *keyname,
132                                          apr_pool_t *pool)
133{
134    LONG rc;
135
136#if APR_HAS_UNICODE_FS
137    IF_WIN_OS_IS_UNICODE
138    {
139        apr_size_t keylen = strlen(keyname) + 1;
140        apr_size_t wkeylen = 256;
141        apr_wchar_t wkeyname[256];
142        apr_status_t rv = apr_conv_utf8_to_ucs2(keyname, &keylen, wkeyname, &wkeylen);
143        if (rv != APR_SUCCESS)
144            return rv;
145        else if (keylen)
146            return APR_ENAMETOOLONG;
147        rc = RegDeleteKeyW(parent->hkey, wkeyname);
148    }
149#endif /* APR_HAS_UNICODE_FS */
150#if APR_HAS_ANSI_FS
151    ELSE_WIN_OS_IS_ANSI
152    {
153        /* We need to determine if subkeys exist on Win9x, to provide
154         * consistent behavior with NT, which returns access denied
155         * if subkeys exist when attempting to delete a key.
156         */
157        DWORD subkeys;
158        HKEY hkey;
159        rc = RegOpenKeyEx(parent->hkey, keyname, 0, KEY_READ, &hkey);
160        if (rc != ERROR_SUCCESS)
161            return APR_FROM_OS_ERROR(rc);
162        rc = RegQueryInfoKey(hkey, NULL, NULL, NULL, &subkeys, NULL, NULL,
163                             NULL, NULL, NULL, NULL, NULL);
164        RegCloseKey(hkey);
165        if (rc != ERROR_SUCCESS)
166            return APR_FROM_OS_ERROR(rc);
167        else if (subkeys)
168            return APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED);
169        rc = RegDeleteKey(parent->hkey, keyname);
170    }
171#endif
172    if (rc != ERROR_SUCCESS) {
173        return APR_FROM_OS_ERROR(rc);
174    }
175    return APR_SUCCESS;
176}
177
178
179AP_DECLARE(apr_status_t) ap_regkey_value_get(char **result,
180                                             ap_regkey_t *key,
181                                             const char *valuename,
182                                             apr_pool_t *pool)
183{
184    /* Retrieve a registry string value, and explode any envvars
185     * that the system has configured (e.g. %SystemRoot%/someapp.exe)
186     */
187    LONG rc;
188    DWORD type;
189    apr_size_t size = 0;
190
191#if APR_HAS_UNICODE_FS
192    IF_WIN_OS_IS_UNICODE
193    {
194        apr_size_t valuelen = strlen(valuename) + 1;
195        apr_size_t wvallen = 256;
196        apr_wchar_t wvalname[256];
197        apr_wchar_t *wvalue;
198        apr_status_t rv;
199        rv = apr_conv_utf8_to_ucs2(valuename, &valuelen, wvalname, &wvallen);
200        if (rv != APR_SUCCESS)
201            return rv;
202        else if (valuelen)
203            return APR_ENAMETOOLONG;
204        /* Read to NULL buffer to determine value size */
205        rc = RegQueryValueExW(key->hkey, wvalname, 0, &type, NULL, (DWORD *)&size);
206        if (rc != ERROR_SUCCESS) {
207            return APR_FROM_OS_ERROR(rc);
208        }
209        if ((size < 2) || (type != REG_SZ && type != REG_EXPAND_SZ)) {
210            return APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER);
211        }
212
213        wvalue = apr_palloc(pool, size);
214        /* Read value based on size query above */
215        rc = RegQueryValueExW(key->hkey, wvalname, 0, &type,
216                              (LPBYTE)wvalue, (DWORD *)&size);
217        if (rc != ERROR_SUCCESS) {
218            return APR_FROM_OS_ERROR(rc);
219        }
220        if (type == REG_EXPAND_SZ) {
221            apr_wchar_t zbuf[1];
222            size = ExpandEnvironmentStringsW(wvalue, zbuf, 0);
223            if (size) {
224                apr_wchar_t *tmp = wvalue;
225                /* The size returned by ExpandEnvironmentStringsW is wchars */
226                wvalue = apr_palloc(pool, size * 2);
227                size = ExpandEnvironmentStringsW(tmp, wvalue, (DWORD)size);
228            }
229        }
230        else {
231            /* count wchars from RegQueryValueExW, rather than bytes */
232            size /= 2;
233        }
234        /* ###: deliberately overallocate all but the trailing null.
235         * We could precalculate the exact buffer here instead, the question
236         * is a matter of storage v.s. cpu cycles.
237         */
238        valuelen = (size - 1) * 3 + 1;
239        *result = apr_palloc(pool, valuelen);
240        rv = apr_conv_ucs2_to_utf8(wvalue, &size, *result, &valuelen);
241        if (rv != APR_SUCCESS)
242            return rv;
243        else if (size)
244            return APR_ENAMETOOLONG;
245    }
246#endif /* APR_HAS_UNICODE_FS */
247#if APR_HAS_ANSI_FS
248    ELSE_WIN_OS_IS_ANSI
249    {
250        /* Read to NULL buffer to determine value size */
251        rc = RegQueryValueEx(key->hkey, valuename, 0, &type, NULL, (DWORD *)&size);
252        if (rc != ERROR_SUCCESS)
253            return APR_FROM_OS_ERROR(rc);
254
255        if ((size < 1) || (type != REG_SZ && type != REG_EXPAND_SZ)) {
256            return APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER);
257        }
258
259        *result = apr_palloc(pool, size);
260        /* Read value based on size query above */
261        rc = RegQueryValueEx(key->hkey, valuename, 0, &type, *result, (DWORD *)&size);
262        if (rc != ERROR_SUCCESS)
263            return APR_FROM_OS_ERROR(rc);
264
265        if (type == REG_EXPAND_SZ) {
266            /* Advise ExpandEnvironmentStrings that we have a zero char
267             * buffer to force computation of the required length.
268             */
269            char zbuf[1];
270            size = ExpandEnvironmentStrings(*result, zbuf, 0);
271            if (size) {
272                char *tmp = *result;
273                *result = apr_palloc(pool, size);
274                size = ExpandEnvironmentStrings(tmp, *result, (DWORD)size);
275            }
276        }
277    }
278#endif
279    return APR_SUCCESS;
280}
281
282
283AP_DECLARE(apr_status_t) ap_regkey_value_set(ap_regkey_t *key,
284                                             const char *valuename,
285                                             const char *value,
286                                             apr_int32_t flags,
287                                             apr_pool_t *pool)
288{
289    /* Retrieve a registry string value, and explode any envvars
290     * that the system has configured (e.g. %SystemRoot%/someapp.exe)
291     */
292    LONG rc;
293    apr_size_t size = strlen(value) + 1;
294    DWORD type = (flags & AP_REGKEY_EXPAND) ? REG_EXPAND_SZ : REG_SZ;
295
296#if APR_HAS_UNICODE_FS
297    IF_WIN_OS_IS_UNICODE
298    {
299        apr_size_t alloclen;
300        apr_size_t valuelen = strlen(valuename) + 1;
301        apr_size_t wvallen = 256;
302        apr_wchar_t wvalname[256];
303        apr_wchar_t *wvalue;
304        apr_status_t rv;
305        rv = apr_conv_utf8_to_ucs2(valuename, &valuelen, wvalname, &wvallen);
306        if (rv != APR_SUCCESS)
307            return rv;
308        else if (valuelen)
309            return APR_ENAMETOOLONG;
310
311        wvallen = alloclen = size;
312        wvalue = apr_palloc(pool, alloclen * 2);
313        rv = apr_conv_utf8_to_ucs2(value, &size, wvalue, &wvallen);
314        if (rv != APR_SUCCESS)
315            return rv;
316        else if (size)
317            return APR_ENAMETOOLONG;
318
319        /* The size is the number of wchars consumed by apr_conv_utf8_to_ucs2
320         * converted to bytes; the trailing L'\0' continues to be counted.
321         */
322        size = (alloclen - wvallen) * 2;
323        rc = RegSetValueExW(key->hkey, wvalname, 0, type,
324                            (LPBYTE)wvalue, (DWORD)size);
325        if (rc != ERROR_SUCCESS)
326            return APR_FROM_OS_ERROR(rc);
327    }
328#endif /* APR_HAS_UNICODE_FS */
329#if APR_HAS_ANSI_FS
330    ELSE_WIN_OS_IS_ANSI
331    {
332        rc = RegSetValueEx(key->hkey, valuename, 0, type, value, (DWORD)size);
333        if (rc != ERROR_SUCCESS)
334            return APR_FROM_OS_ERROR(rc);
335    }
336#endif
337    return APR_SUCCESS;
338}
339
340
341AP_DECLARE(apr_status_t) ap_regkey_value_raw_get(void **result,
342                                                 apr_size_t *resultsize,
343                                                 apr_int32_t *resulttype,
344                                                 ap_regkey_t *key,
345                                                 const char *valuename,
346                                                 apr_pool_t *pool)
347{
348    /* Retrieve a registry string value, and explode any envvars
349     * that the system has configured (e.g. %SystemRoot%/someapp.exe)
350     */
351    LONG rc;
352
353#if APR_HAS_UNICODE_FS
354    IF_WIN_OS_IS_UNICODE
355    {
356        apr_size_t valuelen = strlen(valuename) + 1;
357        apr_size_t wvallen = 256;
358        apr_wchar_t wvalname[256];
359        apr_status_t rv;
360        rv = apr_conv_utf8_to_ucs2(valuename, &valuelen, wvalname, &wvallen);
361        if (rv != APR_SUCCESS)
362            return rv;
363        else if (valuelen)
364            return APR_ENAMETOOLONG;
365        /* Read to NULL buffer to determine value size */
366        rc = RegQueryValueExW(key->hkey, wvalname, 0, (LPDWORD)resulttype,
367                              NULL, (LPDWORD)resultsize);
368        if (rc != ERROR_SUCCESS) {
369            return APR_FROM_OS_ERROR(rc);
370        }
371
372        /* Read value based on size query above */
373        *result = apr_palloc(pool, *resultsize);
374        rc = RegQueryValueExW(key->hkey, wvalname, 0, (LPDWORD)resulttype,
375                             (LPBYTE)*result, (LPDWORD)resultsize);
376    }
377#endif /* APR_HAS_UNICODE_FS */
378#if APR_HAS_ANSI_FS
379    ELSE_WIN_OS_IS_ANSI
380    {
381        /* Read to NULL buffer to determine value size */
382        rc = RegQueryValueEx(key->hkey, valuename, 0, (LPDWORD)resulttype,
383                             NULL, (LPDWORD)resultsize);
384        if (rc != ERROR_SUCCESS)
385            return APR_FROM_OS_ERROR(rc);
386
387        /* Read value based on size query above */
388        *result = apr_palloc(pool, *resultsize);
389        rc = RegQueryValueEx(key->hkey, valuename, 0, (LPDWORD)resulttype,
390                             (LPBYTE)*result, (LPDWORD)resultsize);
391        if (rc != ERROR_SUCCESS)
392            return APR_FROM_OS_ERROR(rc);
393    }
394#endif
395    if (rc != ERROR_SUCCESS) {
396        return APR_FROM_OS_ERROR(rc);
397    }
398
399    return APR_SUCCESS;
400}
401
402
403AP_DECLARE(apr_status_t) ap_regkey_value_raw_set(ap_regkey_t *key,
404                                                 const char *valuename,
405                                                 const void *value,
406                                                 apr_size_t valuesize,
407                                                 apr_int32_t valuetype,
408                                                 apr_pool_t *pool)
409{
410    LONG rc;
411
412#if APR_HAS_UNICODE_FS
413    IF_WIN_OS_IS_UNICODE
414    {
415        apr_size_t valuelen = strlen(valuename) + 1;
416        apr_size_t wvallen = 256;
417        apr_wchar_t wvalname[256];
418        apr_status_t rv;
419        rv = apr_conv_utf8_to_ucs2(valuename, &valuelen, wvalname, &wvallen);
420        if (rv != APR_SUCCESS)
421            return rv;
422        else if (valuelen)
423            return APR_ENAMETOOLONG;
424
425        rc = RegSetValueExW(key->hkey, wvalname, 0, valuetype,
426                            (LPBYTE)value, (DWORD)valuesize);
427    }
428#endif /* APR_HAS_UNICODE_FS */
429#if APR_HAS_ANSI_FS
430    ELSE_WIN_OS_IS_ANSI
431    {
432        rc = RegSetValueEx(key->hkey, valuename, 0, valuetype,
433                            (LPBYTE)value, (DWORD)valuesize);
434    }
435#endif
436    if (rc != ERROR_SUCCESS) {
437        return APR_FROM_OS_ERROR(rc);
438    }
439    return APR_SUCCESS;
440}
441
442
443AP_DECLARE(apr_status_t) ap_regkey_value_array_get(apr_array_header_t **result,
444                                                   ap_regkey_t *key,
445                                                   const char *valuename,
446                                                   apr_pool_t *pool)
447{
448    /* Retrieve a registry string value, and explode any envvars
449     * that the system has configured (e.g. %SystemRoot%/someapp.exe)
450     */
451    apr_status_t rv;
452    void *value;
453    char *buf;
454    char *tmp;
455    apr_int32_t type;
456    apr_size_t size = 0;
457
458    rv = ap_regkey_value_raw_get(&value, &size, &type, key, valuename, pool);
459    if (rv != APR_SUCCESS) {
460        return rv;
461    }
462    else if (type != REG_MULTI_SZ) {
463        return APR_EINVAL;
464    }
465
466#if APR_HAS_UNICODE_FS
467    IF_WIN_OS_IS_UNICODE
468    {
469        apr_size_t alloclen;
470        apr_size_t valuelen = strlen(valuename) + 1;
471
472        /* ###: deliberately overallocate plus two extra nulls.
473         * We could precalculate the exact buffer here instead, the question
474         * is a matter of storage v.s. cpu cycles.
475         */
476        size /= 2;
477        alloclen = valuelen = size * 3 + 2;
478        buf = apr_palloc(pool, valuelen);
479        rv = apr_conv_ucs2_to_utf8(value, &size, buf, &valuelen);
480        if (rv != APR_SUCCESS)
481            return rv;
482        else if (size)
483            return APR_ENAMETOOLONG;
484        buf[(alloclen - valuelen)] = '\0';
485        buf[(alloclen - valuelen) + 1] = '\0';
486    }
487#endif /* APR_HAS_UNICODE_FS */
488#if APR_HAS_ANSI_FS
489    ELSE_WIN_OS_IS_ANSI
490    {
491        /* Small possiblity the array is either unterminated
492         * or single NULL terminated.  Avert.
493         */
494        buf = (char *)value;
495        if (size < 2 || buf[size - 1] != '\0' || buf[size - 2] != '\0') {
496            buf = apr_palloc(pool, size + 2);
497            memcpy(buf, value, size);
498            buf[size + 1] = '\0';
499            buf[size] = '\0';
500        }
501    }
502#endif
503
504    size = 0;    /* Element Count */
505    for (tmp = buf; *tmp; ++tmp) {
506        ++size;
507        while (*tmp) {
508            ++tmp;
509        }
510    }
511
512    *result = apr_array_make(pool, (int)size, sizeof(char *));
513    for (tmp = buf; *tmp; ++tmp) {
514        char **newelem = (char **) apr_array_push(*result);
515        *newelem = tmp;
516        while (*tmp) {
517            ++tmp;
518        }
519    }
520
521   return APR_SUCCESS;
522}
523
524
525AP_DECLARE(apr_status_t) ap_regkey_value_array_set(ap_regkey_t *key,
526                                                   const char *valuename,
527                                                   int nelts,
528                                                   const char * const * elts,
529                                                   apr_pool_t *pool)
530{
531    /* Retrieve a registry string value, and explode any envvars
532     * that the system has configured (e.g. %SystemRoot%/someapp.exe)
533     */
534    int i;
535    const void *value;
536    apr_size_t bufsize;
537
538#if APR_HAS_UNICODE_FS
539    IF_WIN_OS_IS_UNICODE
540    {
541        apr_status_t rv;
542        apr_wchar_t *buf;
543        apr_wchar_t *tmp;
544        apr_size_t bufrem;
545
546        bufsize = 1; /* For trailing second null */
547        for (i = 0; i < nelts; ++i) {
548            bufsize += strlen(elts[i]) + 1;
549        }
550        if (!nelts) {
551            ++bufsize;
552        }
553
554        bufrem = bufsize;
555        buf = apr_palloc(pool, bufsize * 2);
556        tmp = buf;
557        for (i = 0; i < nelts; ++i) {
558            apr_size_t eltsize = strlen(elts[i]) + 1;
559            apr_size_t size = eltsize;
560            rv = apr_conv_utf8_to_ucs2(elts[i], &size, tmp, &bufrem);
561            if (rv != APR_SUCCESS)
562                return rv;
563            else if (size)
564                return APR_ENAMETOOLONG;
565            tmp += eltsize;
566        }
567        if (!nelts) {
568            --bufrem;
569            (*tmp++) = L'\0';
570        }
571        --bufrem;
572        *tmp = L'\0'; /* Trailing second null */
573
574        bufsize = (bufsize - bufrem) * 2;
575        value = (void*)buf;
576    }
577#endif /* APR_HAS_UNICODE_FS */
578#if APR_HAS_ANSI_FS
579    ELSE_WIN_OS_IS_ANSI
580    {
581        char *buf;
582        char *tmp;
583
584        bufsize = 1; /* For trailing second null */
585        for (i = 0; i < nelts; ++i) {
586            bufsize += strlen(elts[i]) + 1;
587        }
588        if (!nelts) {
589            ++bufsize;
590        }
591        buf = apr_palloc(pool, bufsize);
592        tmp = buf;
593        for (i = 0; i < nelts; ++i) {
594            apr_size_t len = strlen(elts[i]) + 1;
595            memcpy(tmp, elts[i], len);
596            tmp += len;
597        }
598        if (!nelts) {
599            (*tmp++) = '\0';
600        }
601        *tmp = '\0'; /* Trailing second null */
602        value = buf;
603    }
604#endif
605    return ap_regkey_value_raw_set(key, valuename, value,
606                                   bufsize, REG_MULTI_SZ, pool);
607}
608
609
610AP_DECLARE(apr_status_t) ap_regkey_value_remove(const ap_regkey_t *key,
611                                                const char *valuename,
612                                                apr_pool_t *pool)
613{
614    LONG rc;
615
616#if APR_HAS_UNICODE_FS
617    IF_WIN_OS_IS_UNICODE
618    {
619        apr_size_t valuelen = strlen(valuename) + 1;
620        apr_size_t wvallen = 256;
621        apr_wchar_t wvalname[256];
622        apr_status_t rv = apr_conv_utf8_to_ucs2(valuename, &valuelen, wvalname, &wvallen);
623        if (rv != APR_SUCCESS)
624            return rv;
625        else if (valuelen)
626            return APR_ENAMETOOLONG;
627        rc = RegDeleteValueW(key->hkey, wvalname);
628    }
629#endif /* APR_HAS_UNICODE_FS */
630#if APR_HAS_ANSI_FS
631    ELSE_WIN_OS_IS_ANSI
632    {
633        rc = RegDeleteValue(key->hkey, valuename);
634    }
635#endif
636    if (rc != ERROR_SUCCESS) {
637        return APR_FROM_OS_ERROR(rc);
638    }
639    return APR_SUCCESS;
640}
641
642#endif /* defined WIN32 */
643