res/res_pjsip_nat: Fix logic for REINVITES
[asterisk/asterisk.git] / res / stasis_recording / stored.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Stored file operations for Stasis
22  *
23  * \author David M. Lee, II <dlee@digium.com>
24  */
25
26 #include "asterisk.h"
27
28 #include "asterisk/astobj2.h"
29 #include "asterisk/paths.h"
30 #include "asterisk/stasis_app_recording.h"
31
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 #include <unistd.h>
35
36 struct stasis_app_stored_recording {
37         AST_DECLARE_STRING_FIELDS(
38                 AST_STRING_FIELD(name); /*!< Recording's name */
39                 AST_STRING_FIELD(file); /*!< Absolute filename, without extension; for use with streamfile */
40                 AST_STRING_FIELD(file_with_ext);        /*!< Absolute filename, with extension; for use with everything else */
41                 );
42
43         const char *format;     /*!< Format name (i.e. filename extension) */
44 };
45
46 static void stored_recording_dtor(void *obj)
47 {
48         struct stasis_app_stored_recording *recording = obj;
49
50         ast_string_field_free_memory(recording);
51 }
52
53 const char *stasis_app_stored_recording_get_file(
54         struct stasis_app_stored_recording *recording)
55 {
56         if (!recording) {
57                 return NULL;
58         }
59         return recording->file;
60 }
61
62 const char *stasis_app_stored_recording_get_filename(
63         struct stasis_app_stored_recording *recording)
64 {
65         if (!recording) {
66                 return NULL;
67         }
68         return recording->file_with_ext;
69 }
70
71 const char *stasis_app_stored_recording_get_extension(
72         struct stasis_app_stored_recording *recording)
73 {
74         if (!recording) {
75                 return NULL;
76         }
77         return recording->format;
78 }
79
80 /*!
81  * \brief Split a path into directory and file, resolving canonical directory.
82  *
83  * The path is resolved relative to the recording directory. Both dir and file
84  * are allocated strings, which you must ast_free().
85  *
86  * \param path Path to split.
87  * \param[out] dir Output parameter for directory portion.
88  * \param[out] fail Output parameter for the file portion.
89  * \return 0 on success.
90  * \return Non-zero on error.
91  */
92 static int split_path(const char *path, char **dir, char **file)
93 {
94         RAII_VAR(char *, relative_dir, NULL, ast_free);
95         RAII_VAR(char *, absolute_dir, NULL, ast_free);
96         RAII_VAR(char *, real_dir, NULL, ast_std_free);
97         char *last_slash;
98         const char *file_portion;
99
100         relative_dir = ast_strdup(path);
101         if (!relative_dir) {
102                 return -1;
103         }
104
105         last_slash = strrchr(relative_dir, '/');
106         if (last_slash) {
107                 *last_slash = '\0';
108                 file_portion = last_slash + 1;
109                 ast_asprintf(&absolute_dir, "%s/%s",
110                         ast_config_AST_RECORDING_DIR, relative_dir);
111         } else {
112                 /* There is no directory portion */
113                 file_portion = path;
114                 *relative_dir = '\0';
115                 absolute_dir = ast_strdup(ast_config_AST_RECORDING_DIR);
116         }
117         if (!absolute_dir) {
118                 return -1;
119         }
120
121         real_dir = realpath(absolute_dir, NULL);
122         if (!real_dir) {
123                 return -1;
124         }
125
126         *dir = ast_strdup(real_dir); /* Dupe so we can ast_free() */
127         *file = ast_strdup(file_portion);
128         return (*dir && *file) ? 0 : -1;
129 }
130
131 struct match_recording_data {
132         const char *file;
133         char *file_with_ext;
134 };
135
136 static int is_recording(const char *filename)
137 {
138         const char *ext = strrchr(filename, '.');
139
140         if (!ext) {
141                 /* No file extension; not us */
142                 return 0;
143         }
144         ++ext;
145
146         if (!ast_get_format_for_file_ext(ext)) {
147                 ast_debug(5, "Recording %s: unrecognized format %s\n",
148                         filename, ext);
149                 /* Keep looking */
150                 return 0;
151         }
152
153         /* Return the index to the .ext */
154         return ext - filename - 1;
155 }
156
157 static int handle_find_recording(const char *dir_name, const char *filename, void *obj)
158 {
159         struct match_recording_data *data = obj;
160         int num;
161
162         /* If not a recording or the names do not match the keep searching */
163         if (!(num = is_recording(filename)) || strncmp(data->file, filename, num)) {
164                 return 0;
165         }
166
167         if (ast_asprintf(&data->file_with_ext, "%s/%s", dir_name, filename) < 0) {
168                 return -1;
169         }
170
171         return 1;
172 }
173
174 /*!
175  * \brief Finds a recording in the given directory.
176  *
177  * This function searchs for a file with the given file name, with a registered
178  * format that matches its extension.
179  *
180  * \param dir_name Directory to search (absolute path).
181  * \param file File name, without extension.
182  * \return Absolute path of the recording file.
183  * \return \c NULL if recording is not found.
184  */
185 static char *find_recording(const char *dir_name, const char *file)
186 {
187         struct match_recording_data data = {
188                 .file = file,
189                 .file_with_ext = NULL
190         };
191
192         ast_file_read_dir(dir_name, handle_find_recording, &data);
193
194         /* Note, string potentially allocated in handle_file_recording */
195         return data.file_with_ext;
196 }
197
198 /*!
199  * \brief Allocate a recording object.
200  */
201 static struct stasis_app_stored_recording *recording_alloc(void)
202 {
203         RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
204                 ao2_cleanup);
205         int res;
206
207         recording = ao2_alloc(sizeof(*recording), stored_recording_dtor);
208         if (!recording) {
209                 return NULL;
210         }
211
212         res = ast_string_field_init(recording, 255);
213         if (res != 0) {
214                 return NULL;
215         }
216
217         ao2_ref(recording, +1);
218         return recording;
219 }
220
221 static int recording_sort(const void *obj_left, const void *obj_right, int flags)
222 {
223         const struct stasis_app_stored_recording *object_left = obj_left;
224         const struct stasis_app_stored_recording *object_right = obj_right;
225         const char *right_key = obj_right;
226         int cmp;
227
228         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
229         case OBJ_POINTER:
230                 right_key = object_right->name;
231                 /* Fall through */
232         case OBJ_KEY:
233                 cmp = strcmp(object_left->name, right_key);
234                 break;
235         case OBJ_PARTIAL_KEY:
236                 /*
237                  * We could also use a partial key struct containing a length
238                  * so strlen() does not get called for every comparison instead.
239                  */
240                 cmp = strncmp(object_left->name, right_key, strlen(right_key));
241                 break;
242         default:
243                 /* Sort can only work on something with a full or partial key. */
244                 ast_assert(0);
245                 cmp = 0;
246                 break;
247         }
248         return cmp;
249 }
250
251 static int handle_scan_file(const char *dir_name, const char *filename, void *obj)
252 {
253         struct ao2_container *recordings = obj;
254         struct stasis_app_stored_recording *recording;
255         char *dot, *filepath;
256
257         /* Skip if it is not a recording */
258         if (!is_recording(filename)) {
259                 return 0;
260         }
261
262         if (ast_asprintf(&filepath, "%s/%s", dir_name, filename) < 0) {
263                 return -1;
264         }
265
266         recording = recording_alloc();
267         if (!recording) {
268                 ast_free(filepath);
269                 return -1;
270         }
271
272         ast_string_field_set(recording, file_with_ext, filepath);
273         /* Build file and format from full path */
274         ast_string_field_set(recording, file, filepath);
275
276         ast_free(filepath);
277
278         dot = strrchr(recording->file, '.');
279         *dot = '\0';
280         recording->format = dot + 1;
281
282         /* Removed the recording dir from the file for the name. */
283         ast_string_field_set(recording, name,
284                 recording->file + strlen(ast_config_AST_RECORDING_DIR) + 1);
285
286         /* Add it to the recordings container */
287         ao2_link(recordings, recording);
288         ao2_ref(recording, -1);
289
290         return 0;
291 }
292
293 struct ao2_container *stasis_app_stored_recording_find_all(void)
294 {
295         struct ao2_container *recordings;
296         int res;
297
298         recordings = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK,
299                 AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, recording_sort, NULL);
300         if (!recordings) {
301                 return NULL;
302         }
303
304         res = ast_file_read_dirs(ast_config_AST_RECORDING_DIR,
305                                  handle_scan_file, recordings, -1);
306         if (res) {
307                 ao2_ref(recordings, -1);
308                 return NULL;
309         }
310
311         return recordings;
312 }
313
314 struct stasis_app_stored_recording *stasis_app_stored_recording_find_by_name(
315         const char *name)
316 {
317         RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
318                 ao2_cleanup);
319         RAII_VAR(char *, dir, NULL, ast_free);
320         RAII_VAR(char *, file, NULL, ast_free);
321         RAII_VAR(char *, file_with_ext, NULL, ast_free);
322         int res;
323         struct stat file_stat;
324         int prefix_len = strlen(ast_config_AST_RECORDING_DIR);
325
326         errno = 0;
327
328         if (!name) {
329                 errno = EINVAL;
330                 return NULL;
331         }
332
333         recording = recording_alloc();
334         if (!recording) {
335                 return NULL;
336         }
337
338         res = split_path(name, &dir, &file);
339         if (res != 0) {
340                 return NULL;
341         }
342         ast_string_field_build(recording, file, "%s/%s", dir, file);
343
344         if (!ast_begins_with(dir, ast_config_AST_RECORDING_DIR)) {
345                 /* It's possible that one or more component of the recording path is
346                  * a symbolic link, this would prevent dir from ever matching. */
347                 char *real_basedir = realpath(ast_config_AST_RECORDING_DIR, NULL);
348
349                 if (!real_basedir || !ast_begins_with(dir, real_basedir)) {
350                         /* Attempt to escape the recording directory */
351                         ast_log(LOG_WARNING, "Attempt to access invalid recording directory %s\n",
352                                 dir);
353                         ast_std_free(real_basedir);
354                         errno = EACCES;
355
356                         return NULL;
357                 }
358
359                 prefix_len = strlen(real_basedir);
360                 ast_std_free(real_basedir);
361         }
362
363         /* The actual name of the recording is file with the config dir
364          * prefix removed.
365          */
366         ast_string_field_set(recording, name, recording->file + prefix_len + 1);
367
368         file_with_ext = find_recording(dir, file);
369         if (!file_with_ext) {
370                 return NULL;
371         }
372         ast_string_field_set(recording, file_with_ext, file_with_ext);
373         recording->format = strrchr(recording->file_with_ext, '.');
374         if (!recording->format) {
375                 return NULL;
376         }
377         ++(recording->format);
378
379         res = stat(file_with_ext, &file_stat);
380         if (res != 0) {
381                 return NULL;
382         }
383
384         if (!S_ISREG(file_stat.st_mode)) {
385                 /* Let's not play if it's not a regular file */
386                 errno = EACCES;
387                 return NULL;
388         }
389
390         ao2_ref(recording, +1);
391         return recording;
392 }
393
394 int stasis_app_stored_recording_copy(struct stasis_app_stored_recording *src_recording, const char *dst,
395         struct stasis_app_stored_recording **dst_recording)
396 {
397         RAII_VAR(char *, full_path, NULL, ast_free);
398         char *dst_file = ast_strdupa(dst);
399         char *format;
400         char *last_slash;
401         int res;
402
403         /* Drop the extension if specified, core will do this for us */
404         format = strrchr(dst_file, '.');
405         if (format) {
406                 *format = '\0';
407         }
408
409         /* See if any intermediary directories need to be made */
410         last_slash = strrchr(dst_file, '/');
411         if (last_slash) {
412                 RAII_VAR(char *, tmp_path, NULL, ast_free);
413
414                 *last_slash = '\0';
415                 if (ast_asprintf(&tmp_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
416                         return -1;
417                 }
418                 if (ast_safe_mkdir(ast_config_AST_RECORDING_DIR,
419                                 tmp_path, 0777) != 0) {
420                         /* errno set by ast_mkdir */
421                         return -1;
422                 }
423                 *last_slash = '/';
424                 if (ast_asprintf(&full_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
425                         return -1;
426                 }
427         } else {
428                 /* There is no directory portion */
429                 if (ast_asprintf(&full_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
430                         return -1;
431                 }
432         }
433
434         ast_verb(4, "Copying recording %s to %s (format %s)\n", src_recording->file,
435                 full_path, src_recording->format);
436         res = ast_filecopy(src_recording->file, full_path, src_recording->format);
437         if (!res) {
438                 *dst_recording = stasis_app_stored_recording_find_by_name(dst_file);
439         }
440
441         return res;
442 }
443
444 int stasis_app_stored_recording_delete(
445         struct stasis_app_stored_recording *recording)
446 {
447         /* Path was validated when the recording object was created */
448         return unlink(recording->file_with_ext);
449 }
450
451 struct ast_json *stasis_app_stored_recording_to_json(
452         struct stasis_app_stored_recording *recording)
453 {
454         if (!recording) {
455                 return NULL;
456         }
457
458         return ast_json_pack("{ s: s, s: s }",
459                 "name", recording->name,
460                 "format", recording->format);
461 }