b04f77a76b4daf517832b25863e74b46c6dd79b9
[asterisk/asterisk.git] / muted.c
1 /*
2  * Mute Daemon
3  *
4  * Specially written for Malcolm Davenport, but I think I'll use it too
5  *
6  * Copyright (C)  2004 - 2005, Digium Inc.
7  *
8  * Mark Spencer <markster@digium.com>
9  *
10  * Distributed under the terms of the GNU General Public License version 2.0 
11  *
12  */
13
14 #include <linux/soundcard.h>
15 #include <stdio.h>
16 #include <errno.h>
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <fcntl.h>
20 #include <string.h>
21 #include <netdb.h>
22 #include <sys/socket.h>
23 #include <sys/ioctl.h>
24 #include <netinet/in.h>
25 #include <arpa/inet.h>
26
27 static char *config = "/etc/muted.conf";
28
29 static char host[256] = "";
30 static char user[256] = "";
31 static char pass[256] = "";
32 static int smoothfade = 0;
33 static int mutelevel = 20;
34 static int muted = 0;
35 static int needfork = 1;
36 static int debug = 0;
37 static int stepsize = 3;
38 static int mixchan = SOUND_MIXER_VOLUME;
39
40 struct subchannel {
41         char *name;
42         struct subchannel *next;
43 };
44
45 static struct channel {
46         char *tech;
47         char *location;
48         struct channel *next;
49         struct subchannel *subs;
50 } *channels;
51
52 static void add_channel(char *tech, char *location)
53 {
54         struct channel *chan;
55         chan = malloc(sizeof(struct channel));
56         if (chan) {
57                 memset(chan, 0, sizeof(struct channel));
58                 chan->tech = strdup(tech);
59                 chan->location = strdup(location);
60                 chan->next = channels;
61                 channels = chan;
62         }
63         
64 }
65
66 static int load_config(void)
67 {
68         FILE *f;
69         char buf[1024];
70         char *val;
71         char *val2;
72         int lineno=0;
73         int x;
74         f = fopen(config, "r");
75         if (!f) {
76                 fprintf(stderr, "Unable to open config file '%s': %s\n", config, strerror(errno));
77                 return -1;
78         }
79         while(!feof(f)) {
80                 fgets(buf, sizeof(buf), f);
81                 if (!feof(f)) {
82                         lineno++;
83                         val = strchr(buf, '#');
84                         if (val) *val = '\0';
85                         while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
86                                 buf[strlen(buf) - 1] = '\0';
87                         if (!strlen(buf))
88                                 continue;
89                         val = buf;
90                         while(*val) {
91                                 if (*val < 33)
92                                         break;
93                                 val++;
94                         }
95                         if (*val) {
96                                 *val = '\0';
97                                 val++;
98                                 while(*val && (*val < 33)) val++;
99                         }
100                         if (!strcasecmp(buf, "host")) {
101                                 if (val && strlen(val))
102                                         strncpy(host, val, sizeof(host) - 1);
103                                 else
104                                         fprintf(stderr, "host needs an argument (the host) at line %d\n", lineno);
105                         } else if (!strcasecmp(buf, "user")) {
106                                 if (val && strlen(val))
107                                         strncpy(user, val, sizeof(user) - 1);
108                                 else
109                                         fprintf(stderr, "user needs an argument (the user) at line %d\n", lineno);
110                         } else if (!strcasecmp(buf, "pass")) {
111                                 if (val && strlen(val))
112                                         strncpy(pass, val, sizeof(pass) - 1);
113                                 else
114                                         fprintf(stderr, "pass needs an argument (the password) at line %d\n", lineno);
115                         } else if (!strcasecmp(buf, "smoothfade")) {
116                                 smoothfade = 1;
117                         } else if (!strcasecmp(buf, "mutelevel")) {
118                                 if (val && (sscanf(val, "%d", &x) == 1) && (x > -1) && (x < 101)) {
119                                         mutelevel = x;
120                                 } else 
121                                         fprintf(stderr, "mutelevel must be a number from 0 (most muted) to 100 (no mute) at line %d\n", lineno);
122                         } else if (!strcasecmp(buf, "channel")) {
123                                 if (val && strlen(val)) {
124                                         val2 = strchr(val, '/');
125                                         if (val2) {
126                                                 *val2 = '\0';
127                                                 val2++;
128                                                 add_channel(val, val2);
129                                         } else
130                                                 fprintf(stderr, "channel needs to be of the format Tech/Location at line %d\n", lineno);
131                                 } else
132                                         fprintf(stderr, "channel needs an argument (the channel) at line %d\n", lineno);
133                         } else {
134                                 fprintf(stderr, "ignoring unknown keyword '%s'\n", buf);
135                         }
136                 }
137         }
138         fclose(f);
139         if (!strlen(host))
140                 fprintf(stderr, "no 'host' specification in config file\n");
141         else if (!strlen(user))
142                 fprintf(stderr, "no 'user' specification in config file\n");
143         else if (!channels) 
144                 fprintf(stderr, "no 'channel' specifications in config file\n");
145         else
146                 return 0;
147         return -1;
148 }
149
150 static FILE *astf;
151
152 static int mixfd;
153
154 static int open_mixer(void)
155 {
156         mixfd = open("/dev/mixer", O_RDWR);
157         if (mixfd < 0) {
158                 fprintf(stderr, "Unable to open /dev/mixer: %s\n", strerror(errno));
159                 return -1;
160         }
161         return 0;
162 }
163
164 static int connect_asterisk(void)
165 {
166         int sock;
167         struct hostent *hp;
168         char *ports;
169         int port = 5038;
170         struct sockaddr_in sin;
171         ports = strchr(host, ':');
172         if (ports) {
173                 *ports = '\0';
174                 ports++;
175                 if ((sscanf(ports, "%d", &port) != 1) || (port < 1) || (port > 65535)) {
176                         fprintf(stderr, "'%s' is not a valid port number in the hostname\n", ports);
177                         return -1;
178                 }
179         }
180         hp = gethostbyname(host);
181         if (!hp) {
182                 fprintf(stderr, "Can't find host '%s'\n", host);
183                 return -1;
184         }
185         sock = socket(AF_INET, SOCK_STREAM, 0);
186         if (sock < 0) {
187                 fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
188                 return -1;
189         }
190         sin.sin_family = AF_INET;
191         sin.sin_port = htons(port);
192         memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
193         if (connect(sock, &sin, sizeof(sin))) {
194                 fprintf(stderr, "Failed to connect to '%s' port '%d': %s\n", host, port, strerror(errno));
195                 close(sock);
196                 return -1;
197         }
198         astf = fdopen(sock, "r+");
199         if (!astf) {
200                 fprintf(stderr, "fdopen failed: %s\n", strerror(errno));
201                 close(sock);
202                 return -1;
203         }
204         return 0;
205 }
206
207 static char *get_line(void)
208 {
209         static char buf[1024];
210         if (fgets(buf, sizeof(buf), astf)) {
211                 while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
212                         buf[strlen(buf) - 1] = '\0';
213                 return buf;
214         } else
215                 return NULL;
216 }
217
218 static int login_asterisk(void)
219 {
220         char *welcome;
221         char *resp;
222         if (!(welcome = get_line())) {
223                 fprintf(stderr, "disconnected (1)\n");
224                 return -1;
225         }
226         fprintf(astf, 
227                 "Action: Login\r\n"
228                 "Username: %s\r\n"
229                 "Secret: %s\r\n\r\n", user, pass);
230         if (!(welcome = get_line())) {
231                 fprintf(stderr, "disconnected (2)\n");
232                 return -1;
233         }
234         if (strcasecmp(welcome, "Response: Success")) {
235                 fprintf(stderr, "login failed ('%s')\n", welcome);
236                 return -1;
237         }
238         /* Eat the rest of the event */
239         while((resp = get_line()) && strlen(resp));
240         if (!resp) {
241                 fprintf(stderr, "disconnected (3)\n");
242                 return -1;
243         }
244         fprintf(astf, 
245                 "Action: Status\r\n\r\n");
246         if (!(welcome = get_line())) {
247                 fprintf(stderr, "disconnected (4)\n");
248                 return -1;
249         }
250         if (strcasecmp(welcome, "Response: Success")) {
251                 fprintf(stderr, "status failed ('%s')\n", welcome);
252                 return -1;
253         }
254         /* Eat the rest of the event */
255         while((resp = get_line()) && strlen(resp));
256         if (!resp) {
257                 fprintf(stderr, "disconnected (5)\n");
258                 return -1;
259         }
260         return 0;
261 }
262
263 static struct channel *find_channel(char *channel)
264 {
265         char tmp[256] = "";
266         char *s, *t;
267         struct channel *chan;
268         strncpy(tmp, channel, sizeof(tmp) - 1);
269         s = strchr(tmp, '/');
270         if (s) {
271                 *s = '\0';
272                 s++;
273                 t = strrchr(s, '-');
274                 if (t) {
275                         *t = '\0';
276                 }
277                 if (debug)
278                         printf("Searching for '%s' tech, '%s' location\n", tmp, s);
279                 chan = channels;
280                 while(chan) {
281                         if (!strcasecmp(chan->tech, tmp) && !strcasecmp(chan->location, s)) {
282                                 if (debug)
283                                         printf("Found '%s'/'%s'\n", chan->tech, chan->location);
284                                 break;
285                         }
286                         chan = chan->next;
287                 }
288         } else
289                 chan = NULL;
290         return chan;
291 }
292
293 static int getvol(void)
294 {
295         int vol;
296         if (ioctl(mixfd, MIXER_READ(mixchan), &vol)) {
297                 fprintf(stderr, "Unable to read mixer volume: %s\n", strerror(errno));
298                 return -1;
299         }
300         return vol;
301 }
302
303 static int setvol(int vol)
304 {
305         if (ioctl(mixfd, MIXER_WRITE(mixchan), &vol)) {
306                 fprintf(stderr, "Unable to write mixer volume: %s\n", strerror(errno));
307                 return -1;
308         }
309         return 0;
310 }
311
312 static int oldvol = 0;
313 static int mutevol = 0;
314
315 static int mutedlevel(int orig, int mutelevel)
316 {
317         int l = orig >> 8;
318         int r = orig & 0xff;
319         l = (float)(mutelevel) * (float)(l) / 100.0;
320         r = (float)(mutelevel) * (float)(r) / 100.0;
321         return (l << 8) | r;
322 }
323
324 static void mute(void)
325 {
326         int vol;
327         int start;
328         int x;
329         vol = getvol();
330         oldvol = vol;
331         if (smoothfade) 
332                 start = 100;
333         else
334                 start = mutelevel;
335         for (x=start;x>=mutelevel;x-=stepsize) {
336                 mutevol = mutedlevel(vol, x);
337                 setvol(mutevol);
338                 /* Wait 0.01 sec */
339                 usleep(10000);
340         }
341         mutevol = mutedlevel(vol, mutelevel);
342         setvol(mutevol);
343         if (debug)
344                 printf("Mute from '%04x' to '%04x'!\n", oldvol, mutevol);
345         muted = 1;
346 }
347
348 static void unmute(void)
349 {
350         int vol;
351         int start;
352         int x;
353         vol = getvol();
354         if (debug)
355                 printf("Unmute from '%04x' (should be '%04x') to '%04x'!\n", vol, mutevol, oldvol);
356         if (vol == mutevol) {
357                 if (smoothfade)
358                         start = mutelevel;
359                 else
360                         start = 100;
361                 for (x=start;x<100;x+=stepsize) {
362                         mutevol = mutedlevel(oldvol, x);
363                         setvol(mutevol);
364                         /* Wait 0.01 sec */
365                         usleep(10000);
366                 }
367                 setvol(oldvol);
368         } else
369                 printf("Whoops, it's already been changed!\n");
370         muted = 0;
371 }
372
373 static void check_mute(void)
374 {
375         int offhook = 0;
376         struct channel *chan;
377         chan = channels;
378         while(chan) {
379                 if (chan->subs) {
380                         offhook++;
381                         break;
382                 }
383                 chan = chan->next;
384         }
385         if (offhook && !muted)
386                 mute();
387         else if (!offhook && muted)
388                 unmute();
389 }
390
391 static void delete_sub(struct channel *chan, char *name)
392 {
393         struct subchannel *sub, *prev;
394         prev = NULL;
395         sub = chan->subs;
396         while(sub) {
397                 if (!strcasecmp(sub->name, name)) {
398                         if (prev)
399                                 prev->next = sub->next;
400                         else
401                                 chan->subs = sub->next;
402                         free(sub->name);
403                         free(sub);
404                         return;
405                 }
406                 prev = sub;
407                 sub = sub->next;
408         }
409 }
410
411 static void append_sub(struct channel *chan, char *name)
412 {
413         struct subchannel *sub;
414         sub = chan->subs;
415         while(sub) {
416                 if (!strcasecmp(sub->name, name)) 
417                         return;
418                 sub = sub->next;
419         }
420         sub = malloc(sizeof(struct subchannel));
421         if (sub) {
422                 memset(sub, 0, sizeof(struct subchannel));
423                 sub->name = strdup(name);
424                 sub->next = chan->subs;
425                 chan->subs = sub;
426         }
427 }
428
429 static void hangup_chan(char *channel)
430 {
431         struct channel *chan;
432         if (debug)
433                 printf("Hangup '%s'\n", channel);
434         chan = find_channel(channel);
435         if (chan)
436                 delete_sub(chan, channel);
437         check_mute();
438 }
439
440 static void offhook_chan(char *channel)
441 {
442         struct channel *chan;
443         if (debug)
444                 printf("Offhook '%s'\n", channel);
445         chan = find_channel(channel);
446         if (chan)
447                 append_sub(chan, channel);
448         check_mute();
449 }
450
451 static int wait_event(void)
452 {
453         char *resp;
454         char event[120]="";
455         char channel[120]="";
456         char oldname[120]="";
457         char newname[120]="";
458         resp = get_line();
459         if (!resp) {
460                 fprintf(stderr, "disconnected (6)\n");
461                 return -1;
462         }
463         if (!strncasecmp(resp, "Event: ", strlen("Event: "))) {
464                 strncpy(event, resp + strlen("Event: "), sizeof(event) - 1);
465                 /* Consume the rest of the non-event */
466                 while((resp = get_line()) && strlen(resp)) {
467                         if (!strncasecmp(resp, "Channel: ", strlen("Channel: ")))
468                                 strncpy(channel, resp + strlen("Channel: "), sizeof(channel) - 1);
469                         if (!strncasecmp(resp, "Newname: ", strlen("Newname: ")))
470                                 strncpy(newname, resp + strlen("Newname: "), sizeof(newname) - 1);
471                         if (!strncasecmp(resp, "Oldname: ", strlen("Oldname: ")))
472                                 strncpy(oldname, resp + strlen("Oldname: "), sizeof(oldname) - 1);
473                 }
474                 if (strlen(channel)) {
475                         if (!strcasecmp(event, "Hangup")) 
476                                 hangup_chan(channel);
477                         else
478                                 offhook_chan(channel);
479                 }
480                 if (strlen(newname) && strlen(oldname)) {
481                         if (!strcasecmp(event, "Rename")) {
482                                 hangup_chan(oldname);
483                                 offhook_chan(newname);
484                         }
485                 }
486         } else {
487                 /* Consume the rest of the non-event */
488                 while((resp = get_line()) && strlen(resp));
489         }
490         if (!resp) {
491                 fprintf(stderr, "disconnected (7)\n");
492                 return -1;
493         }
494         return 0;
495 }
496
497 static void usage(void)
498 {
499         printf("Usage: muted [-f] [-d]\n"
500                "        -f : Do not fork\n"
501                    "        -d : Debug (implies -f)\n");
502 }
503
504 int main(int argc, char *argv[])
505 {
506         int x;
507         while((x = getopt(argc, argv, "fhd")) > 0) {
508                 switch(x) {
509                 case 'd':
510                         debug = 1;
511                         needfork = 0;
512                         break;
513                 case 'f':
514                         needfork = 0;
515                         break;
516                 case 'h':
517                         /* Fall through */
518                 default:
519                         usage();
520                         exit(1);
521                 }
522         }
523         if (load_config())
524                 exit(1);
525         if (open_mixer())
526                 exit(1);
527         if (connect_asterisk()) {
528                 close(mixfd);
529                 exit(1);
530         }
531         if (login_asterisk()) {
532                 close(mixfd);
533                 fclose(astf);
534                 exit(1);
535         }
536         if (needfork)
537                 daemon(0,0);
538         for(;;) {
539                 if (wait_event()) {
540                         fclose(astf);
541                         while(connect_asterisk()) {
542                                 sleep(5);
543                         }
544                         if (login_asterisk()) {
545                                 fclose(astf);
546                                 exit(1);
547                         }
548                 }
549         }
550         exit(0);
551 }