00a9d9ebbebd8e5ac2dbdd53c5fda84fcd0617c6
[asterisk/asterisk.git] / channels / iax2 / firmware.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Mark Spencer <markster@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 IAX Firmware Support
22  *
23  * \author Mark Spencer <markster@digium.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <dirent.h>
37 #include <sys/mman.h>
38 #include <arpa/inet.h>
39
40 #include "asterisk/linkedlists.h"
41 #include "asterisk/md5.h"
42 #include "asterisk/paths.h"
43 #include "asterisk/utils.h"
44
45 #include "include/firmware.h"
46
47 struct iax_firmware {
48         AST_LIST_ENTRY(iax_firmware) list;
49         int fd;
50         int mmaplen;
51         int dead;
52         struct ast_iax2_firmware_header *fwh;
53         unsigned char *buf;
54 };
55
56 static AST_LIST_HEAD_STATIC(firmwares, iax_firmware);
57
58 static int try_firmware(char *s)
59 {
60         struct stat stbuf;
61         struct iax_firmware *cur = NULL;
62         int ifd, fd, res, len, chunk;
63         struct ast_iax2_firmware_header *fwh, fwh2;
64         struct MD5Context md5;
65         unsigned char sum[16], buf[1024];
66         char *s2, *last;
67
68         s2 = ast_alloca(strlen(s) + 100);
69
70         last = strrchr(s, '/');
71         if (last)
72                 last++;
73         else
74                 last = s;
75
76         snprintf(s2, strlen(s) + 100, "/var/tmp/%s-%ld", last, ast_random());
77
78         if (stat(s, &stbuf) < 0) {
79                 ast_log(LOG_WARNING, "Failed to stat '%s': %s\n", s, strerror(errno));
80                 return -1;
81         }
82
83         /* Make sure it's not a directory */
84         if (S_ISDIR(stbuf.st_mode))
85                 return -1;
86         ifd = open(s, O_RDONLY);
87         if (ifd < 0) {
88                 ast_log(LOG_WARNING, "Cannot open '%s': %s\n", s, strerror(errno));
89                 return -1;
90         }
91         fd = open(s2, O_RDWR | O_CREAT | O_EXCL, AST_FILE_MODE);
92         if (fd < 0) {
93                 ast_log(LOG_WARNING, "Cannot open '%s' for writing: %s\n", s2, strerror(errno));
94                 close(ifd);
95                 return -1;
96         }
97         /* Unlink our newly created file */
98         unlink(s2);
99
100         /* Now copy the firmware into it */
101         len = stbuf.st_size;
102         while(len) {
103                 chunk = len;
104                 if (chunk > sizeof(buf))
105                         chunk = sizeof(buf);
106                 res = read(ifd, buf, chunk);
107                 if (res != chunk) {
108                         ast_log(LOG_WARNING, "Only read %d of %d bytes of data :(: %s\n", res, chunk, strerror(errno));
109                         close(ifd);
110                         close(fd);
111                         return -1;
112                 }
113                 res = write(fd, buf, chunk);
114                 if (res != chunk) {
115                         ast_log(LOG_WARNING, "Only write %d of %d bytes of data :(: %s\n", res, chunk, strerror(errno));
116                         close(ifd);
117                         close(fd);
118                         return -1;
119                 }
120                 len -= chunk;
121         }
122         close(ifd);
123         /* Return to the beginning */
124         lseek(fd, 0, SEEK_SET);
125         if ((res = read(fd, &fwh2, sizeof(fwh2))) != sizeof(fwh2)) {
126                 ast_log(LOG_WARNING, "Unable to read firmware header in '%s'\n", s);
127                 close(fd);
128                 return -1;
129         }
130         if (ntohl(fwh2.magic) != IAX_FIRMWARE_MAGIC) {
131                 ast_log(LOG_WARNING, "'%s' is not a valid firmware file\n", s);
132                 close(fd);
133                 return -1;
134         }
135         if (ntohl(fwh2.datalen) != (stbuf.st_size - sizeof(fwh2))) {
136                 ast_log(LOG_WARNING, "Invalid data length in firmware '%s'\n", s);
137                 close(fd);
138                 return -1;
139         }
140         if (fwh2.devname[sizeof(fwh2.devname) - 1] || ast_strlen_zero((char *)fwh2.devname)) {
141                 ast_log(LOG_WARNING, "No or invalid device type specified for '%s'\n", s);
142                 close(fd);
143                 return -1;
144         }
145         fwh = (struct ast_iax2_firmware_header*)mmap(NULL, stbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
146         if (fwh == MAP_FAILED) {
147                 ast_log(LOG_WARNING, "mmap failed: %s\n", strerror(errno));
148                 close(fd);
149                 return -1;
150         }
151         MD5Init(&md5);
152         MD5Update(&md5, fwh->data, ntohl(fwh->datalen));
153         MD5Final(sum, &md5);
154         if (memcmp(sum, fwh->chksum, sizeof(sum))) {
155                 ast_log(LOG_WARNING, "Firmware file '%s' fails checksum\n", s);
156                 munmap((void*)fwh, stbuf.st_size);
157                 close(fd);
158                 return -1;
159         }
160
161         AST_LIST_TRAVERSE(&firmwares, cur, list) {
162                 if (!strcmp((const char *) cur->fwh->devname, (const char *) fwh->devname)) {
163                         /* Found a candidate */
164                         if (cur->dead || (ntohs(cur->fwh->version) < ntohs(fwh->version)))
165                                 /* The version we have on loaded is older, load this one instead */
166                                 break;
167                         /* This version is no newer than what we have.  Don't worry about it.
168                            We'll consider it a proper load anyhow though */
169                         munmap((void*)fwh, stbuf.st_size);
170                         close(fd);
171                         return 0;
172                 }
173         }
174
175         if (!cur && ((cur = ast_calloc(1, sizeof(*cur))))) {
176                 cur->fd = -1;
177                 AST_LIST_INSERT_TAIL(&firmwares, cur, list);
178         }
179
180         if (cur) {
181                 if (cur->fwh)
182                         munmap((void*)cur->fwh, cur->mmaplen);
183                 if (cur->fd > -1)
184                         close(cur->fd);
185                 cur->fwh = fwh;
186                 cur->fd = fd;
187                 cur->mmaplen = stbuf.st_size;
188                 cur->dead = 0;
189         }
190
191         return 0;
192 }
193
194 static void destroy_firmware(struct iax_firmware *cur)
195 {
196         /* Close firmware */
197         if (cur->fwh) {
198                 munmap((void*)cur->fwh, ntohl(cur->fwh->datalen) + sizeof(*(cur->fwh)));
199         }
200         close(cur->fd);
201         ast_free(cur);
202 }
203
204 void iax_firmware_reload(void)
205 {
206         struct iax_firmware *cur = NULL;
207         DIR *fwd;
208         struct dirent *de;
209         char dir[256], fn[256];
210
211         AST_LIST_LOCK(&firmwares);
212
213         /* Mark all as dead */
214         AST_LIST_TRAVERSE(&firmwares, cur, list) {
215                 cur->dead = 1;
216         }
217
218         /* Now that we have marked them dead... load new ones */
219         snprintf(dir, sizeof(dir), "%s/firmware/iax", ast_config_AST_DATA_DIR);
220         fwd = opendir(dir);
221         if (fwd) {
222                 while((de = readdir(fwd))) {
223                         if (de->d_name[0] != '.') {
224                                 snprintf(fn, sizeof(fn), "%s/%s", dir, de->d_name);
225                                 if (!try_firmware(fn)) {
226                                         ast_verb(2, "Loaded firmware '%s'\n", de->d_name);
227                                 }
228                         }
229                 }
230                 closedir(fwd);
231         } else {
232                 ast_log(LOG_WARNING, "Error opening firmware directory '%s': %s\n", dir, strerror(errno));
233         }
234
235         /* Clean up leftovers */
236         AST_LIST_TRAVERSE_SAFE_BEGIN(&firmwares, cur, list) {
237                 if (!cur->dead)
238                         continue;
239                 AST_LIST_REMOVE_CURRENT(list);
240                 destroy_firmware(cur);
241         }
242         AST_LIST_TRAVERSE_SAFE_END;
243
244         AST_LIST_UNLOCK(&firmwares);
245 }
246
247 void iax_firmware_unload(void)
248 {
249         struct iax_firmware *cur = NULL;
250
251         AST_LIST_LOCK(&firmwares);
252         AST_LIST_TRAVERSE_SAFE_BEGIN(&firmwares, cur, list) {
253                 AST_LIST_REMOVE_CURRENT(list);
254                 destroy_firmware(cur);
255         }
256         AST_LIST_TRAVERSE_SAFE_END;
257         AST_LIST_UNLOCK(&firmwares);
258 }
259
260 int iax_firmware_get_version(const char *dev, uint16_t *version)
261 {
262         struct iax_firmware *cur = NULL;
263
264         if (ast_strlen_zero(dev))
265                 return 0;
266
267         AST_LIST_LOCK(&firmwares);
268         AST_LIST_TRAVERSE(&firmwares, cur, list) {
269                 if (!strcmp(dev, (const char *) cur->fwh->devname)) {
270                         *version = ntohs(cur->fwh->version);
271                         AST_LIST_UNLOCK(&firmwares);
272                         return 1;
273                 }
274         }
275         AST_LIST_UNLOCK(&firmwares);
276
277         return 0;
278 }
279
280 int iax_firmware_append(struct iax_ie_data *ied, const char *dev, unsigned int desc)
281 {
282         int res = -1;
283         unsigned int bs = desc & 0xff;
284         unsigned int start = (desc >> 8) & 0xffffff;
285         unsigned int bytes;
286         struct iax_firmware *cur;
287
288         if (ast_strlen_zero((char *)dev) || !bs)
289                 return -1;
290
291         start *= bs;
292
293         AST_LIST_LOCK(&firmwares);
294         AST_LIST_TRAVERSE(&firmwares, cur, list) {
295                 if (strcmp(dev, (const char *) cur->fwh->devname))
296                         continue;
297                 iax_ie_append_int(ied, IAX_IE_FWBLOCKDESC, desc);
298                 if (start < ntohl(cur->fwh->datalen)) {
299                         bytes = ntohl(cur->fwh->datalen) - start;
300                         if (bytes > bs)
301                                 bytes = bs;
302                         iax_ie_append_raw(ied, IAX_IE_FWBLOCKDATA, cur->fwh->data + start, bytes);
303                 } else {
304                         bytes = 0;
305                         iax_ie_append(ied, IAX_IE_FWBLOCKDATA);
306                 }
307                 if (bytes == bs)
308                         res = 0;
309                 else
310                         res = 1;
311                 break;
312         }
313         AST_LIST_UNLOCK(&firmwares);
314
315         return res;
316 }
317
318 void iax_firmware_traverse(
319         const char *filter,
320         int (*callback)(struct ast_iax2_firmware_header *header, void *data),
321         void *data)
322 {
323         struct iax_firmware *cur = NULL;
324
325         if (!callback) {
326                 return;
327         }
328
329         AST_LIST_LOCK(&firmwares);
330         AST_LIST_TRAVERSE(&firmwares, cur, list) {
331                 if (!filter || !strcasecmp(filter, (const char *) cur->fwh->devname)) {
332                         if (callback(cur->fwh, data)) {
333                                 break;
334                         }
335                 }
336         }
337         AST_LIST_UNLOCK(&firmwares);
338 }