1/***
2  This file is part of PulseAudio.
3
4  Copyright 2004-2006 Lennart Poettering
5  Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
6
7  PulseAudio is free software; you can redistribute it and/or modify
8  it under the terms of the GNU Lesser General Public License as
9  published by the Free Software Foundation; either version 2.1 of the
10  License, or (at your option) any later version.
11
12  PulseAudio is distributed in the hope that it will be useful, but
13  WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  Lesser General Public License for more details.
16
17  You should have received a copy of the GNU Lesser General Public
18  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19***/
20
21#ifdef HAVE_CONFIG_H
22#include <config.h>
23#endif
24
25#include <unistd.h>
26#include <fcntl.h>
27#include <string.h>
28#include <errno.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <sys/stat.h>
32
33#include <pulse/util.h>
34#include <pulse/xmalloc.h>
35#include <pulsecore/core-error.h>
36#include <pulsecore/core-util.h>
37#include <pulsecore/log.h>
38#include <pulsecore/random.h>
39#include <pulsecore/macro.h>
40
41#include "authkey.h"
42
43/* Generate a new authentication key, store it in file fd and return it in *data  */
44static int generate(int fd, void *ret_data, size_t length) {
45    ssize_t r;
46
47    pa_assert(fd >= 0);
48    pa_assert(ret_data);
49    pa_assert(length > 0);
50
51    pa_random(ret_data, length);
52
53    lseek(fd, (off_t) 0, SEEK_SET);
54    if (ftruncate(fd, (off_t) 0) < 0) {
55        pa_log("Failed to truncate cookie file: %s", pa_cstrerror(errno));
56        return -1;
57    }
58
59    if ((r = pa_loop_write(fd, ret_data, length, NULL)) < 0 || (size_t) r != length) {
60        pa_log("Failed to write cookie file: %s", pa_cstrerror(errno));
61        return -1;
62    }
63
64    return 0;
65}
66
67#ifndef O_BINARY
68#define O_BINARY 0
69#endif
70
71/* Load an authentication cookie from file fn and store it in data. If
72 * the cookie file doesn't exist, create it */
73static int load(const char *fn, bool create, void *data, size_t length) {
74    int fd = -1;
75    int writable = 1;
76    int unlock = 0, ret = -1;
77    ssize_t r;
78
79    pa_assert(fn);
80    pa_assert(data);
81    pa_assert(length > 0);
82
83    if (create)
84        pa_make_secure_parent_dir(fn, pa_in_system_mode() ? 0755U : 0700U, -1, -1, false);
85
86    if ((fd = pa_open_cloexec(fn, (create ? O_RDWR|O_CREAT : O_RDONLY)|O_BINARY, S_IRUSR|S_IWUSR)) < 0) {
87
88        if (!create || errno != EACCES || (fd = open(fn, O_RDONLY|O_BINARY)) < 0) {
89            pa_log_warn("Failed to open cookie file '%s': %s", fn, pa_cstrerror(errno));
90            goto finish;
91        } else
92            writable = 0;
93    }
94
95    unlock = pa_lock_fd(fd, 1) >= 0;
96
97    if ((r = pa_loop_read(fd, data, length, NULL)) < 0) {
98        pa_log("Failed to read cookie file '%s': %s", fn, pa_cstrerror(errno));
99        goto finish;
100    }
101
102    if ((size_t) r != length) {
103        pa_log_debug("Got %d bytes from cookie file '%s', expected %d", (int) r, fn, (int) length);
104
105        if (!writable) {
106            pa_log_warn("Unable to write cookie to read-only file");
107            goto finish;
108        }
109
110        if (generate(fd, data, length) < 0)
111            goto finish;
112    }
113
114    ret = 0;
115
116finish:
117
118    if (fd >= 0) {
119
120        if (unlock)
121            pa_lock_fd(fd, 0);
122
123        if (pa_close(fd) < 0) {
124            pa_log_warn("Failed to close cookie file: %s", pa_cstrerror(errno));
125            ret = -1;
126        }
127    }
128
129    return ret;
130}
131
132/* If the specified file path starts with / return it, otherwise
133 * return path prepended with the config home directory. */
134static int normalize_path(const char *fn, char **_r) {
135    pa_assert(fn);
136    pa_assert(_r);
137
138    if (!pa_is_path_absolute(fn))
139        return pa_append_to_config_home_dir(fn, _r);
140
141    *_r = pa_xstrdup(fn);
142    return 0;
143}
144
145int pa_authkey_load(const char *fn, bool create, void *data, size_t length) {
146    char *p;
147    int ret;
148
149    pa_assert(fn);
150    pa_assert(data);
151    pa_assert(length > 0);
152
153    if ((ret = normalize_path(fn, &p)) < 0)
154        return ret;
155
156    if ((ret = load(p, create, data, length)) < 0)
157        pa_log_warn("Failed to load authentication key '%s': %s", p, (ret < 0) ? pa_cstrerror(errno) : "File corrupt");
158
159    pa_xfree(p);
160
161    return ret;
162}
163
164/* Store the specified cookie in the specified cookie file */
165int pa_authkey_save(const char *fn, const void *data, size_t length) {
166    int fd = -1;
167    int unlock = 0, ret;
168    ssize_t r;
169    char *p;
170
171    pa_assert(fn);
172    pa_assert(data);
173    pa_assert(length > 0);
174
175    if ((ret = normalize_path(fn, &p)) < 0)
176        return ret;
177
178    if ((fd = pa_open_cloexec(p, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
179        pa_log_warn("Failed to open cookie file '%s': %s", fn, pa_cstrerror(errno));
180        ret = -1;
181        goto finish;
182    }
183
184    unlock = pa_lock_fd(fd, 1) >= 0;
185
186    if ((r = pa_loop_write(fd, data, length, NULL)) < 0 || (size_t) r != length) {
187        pa_log("Failed to read cookie file '%s': %s", fn, pa_cstrerror(errno));
188        ret = -1;
189        goto finish;
190    }
191
192finish:
193
194    if (fd >= 0) {
195
196        if (unlock)
197            pa_lock_fd(fd, 0);
198
199        if (pa_close(fd) < 0) {
200            pa_log_warn("Failed to close cookie file: %s", pa_cstrerror(errno));
201            ret = -1;
202        }
203    }
204
205    pa_xfree(p);
206
207    return ret;
208}
209