460b9539 |
1 | /* |
2 | * This file is part of DisOrder. |
3 | * Copyright (C) 2004, 2005, 2006 Richard Kettlewell |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, but |
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License |
16 | * along with this program; if not, write to the Free Software |
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
18 | * USA |
19 | */ |
20 | |
21 | #include <config.h> |
22 | #include "types.h" |
23 | |
24 | #include <sys/types.h> |
25 | #include <sys/socket.h> |
26 | #include <netinet/in.h> |
27 | #include <sys/un.h> |
28 | #include <string.h> |
29 | #include <stdio.h> |
30 | #include <unistd.h> |
31 | #include <errno.h> |
32 | #include <netdb.h> |
33 | #include <stdlib.h> |
34 | |
35 | #include "log.h" |
36 | #include "mem.h" |
37 | #include "configuration.h" |
38 | #include "queue.h" |
39 | #include "client.h" |
40 | #include "charset.h" |
41 | #include "hex.h" |
42 | #include "split.h" |
43 | #include "vector.h" |
44 | #include "inputline.h" |
45 | #include "kvp.h" |
46 | #include "syscalls.h" |
47 | #include "printf.h" |
48 | #include "sink.h" |
49 | #include "addr.h" |
50 | #include "authhash.h" |
51 | #include "client-common.h" |
52 | |
53 | struct disorder_client { |
54 | FILE *fpin, *fpout; |
55 | char *ident; |
56 | char *user; |
57 | int verbose; |
58 | }; |
59 | |
60 | disorder_client *disorder_new(int verbose) { |
61 | disorder_client *c = xmalloc(sizeof (struct disorder_client)); |
62 | |
63 | c->verbose = verbose; |
64 | return c; |
65 | } |
66 | |
67 | /* read a response line. |
68 | * If @rp@ is not a null pointer, returns the whole response through it. |
69 | * Return value is the response code, or -1 on error. */ |
70 | static int response(disorder_client *c, char **rp) { |
71 | char *r; |
72 | |
73 | if(inputline(c->ident, c->fpin, &r, '\n')) |
74 | return -1; |
75 | D(("response: %s", r)); |
76 | if(rp) |
77 | *rp = r; |
78 | if(r[0] >= '0' && r[0] <= '9' |
79 | && r[1] >= '0' && r[1] <= '9' |
80 | && r[2] >= '0' && r[2] <= '9' |
81 | && r[3] == ' ') |
82 | return (r[0] * 10 + r[1]) * 10 + r[2] - 111 * '0'; |
83 | else { |
84 | error(0, "invalid reply format from %s", c->ident); |
85 | return -1; |
86 | } |
87 | } |
88 | |
89 | /* Read a response. |
90 | * If @rp@ is not a null pointer then the response text (excluding |
91 | * the status code) is returned through it, UNLESS the response code |
92 | * is xx9. |
93 | * Return value is 0 for 2xx responses and -1 otherwise. |
94 | */ |
95 | static int check_response(disorder_client *c, char **rp) { |
96 | int rc; |
97 | char *r; |
98 | |
99 | if((rc = response(c, &r)) == -1) |
100 | return -1; |
101 | else if(rc / 100 == 2) { |
102 | if(rp) |
103 | *rp = (rc % 10 == 9) ? 0 : xstrdup(r + 4); |
104 | return 0; |
105 | } else { |
106 | if(c->verbose) |
107 | error(0, "from %s: %s", c->ident, utf82mb(r)); |
108 | return -1; |
109 | } |
110 | } |
111 | |
112 | static int disorder_simple_v(disorder_client *c, |
113 | char **rp, |
114 | const char *cmd, va_list ap) { |
115 | const char *arg; |
116 | struct dynstr d; |
117 | |
118 | if(cmd) { |
119 | dynstr_init(&d); |
120 | dynstr_append_string(&d, cmd); |
121 | while((arg = va_arg(ap, const char *))) { |
122 | dynstr_append(&d, ' '); |
123 | dynstr_append_string(&d, quoteutf8(arg)); |
124 | } |
125 | dynstr_append(&d, '\n'); |
126 | dynstr_terminate(&d); |
127 | D(("command: %s", d.vec)); |
128 | if(fputs(d.vec, c->fpout) < 0 || fflush(c->fpout)) { |
129 | error(errno, "error writing to %s", c->ident); |
130 | return -1; |
131 | } |
132 | } |
133 | return check_response(c, rp); |
134 | } |
135 | |
136 | /* Execute a simple command with any number of arguments. |
137 | * @rp@ and return value as for check_response(). |
138 | */ |
139 | static int disorder_simple(disorder_client *c, |
140 | char **rp, |
141 | const char *cmd, ...) { |
142 | va_list ap; |
143 | int ret; |
144 | |
145 | va_start(ap, cmd); |
146 | ret = disorder_simple_v(c, rp, cmd, ap); |
147 | va_end(ap); |
148 | return ret; |
149 | } |
150 | |
151 | static int connect_sock(void *vc, |
152 | const struct sockaddr *sa, |
153 | socklen_t len, |
154 | const char *ident) { |
155 | const char *username, *password; |
156 | disorder_client *c = vc; |
157 | int n; |
158 | |
159 | if(!(username = config->username)) { |
160 | error(0, "no username configured"); |
161 | return -1; |
162 | } |
163 | if(!(password = config->password)) { |
164 | for(n = 0; (n < config->allow.n |
165 | && strcmp(config->allow.s[n].s[0], username)); ++n) |
166 | ; |
167 | if(n < config->allow.n) |
168 | password = config->allow.s[n].s[1]; |
169 | else { |
170 | error(0, "no password configured"); |
171 | return -1; |
172 | } |
173 | } |
174 | return disorder_connect_sock(c, sa, len, username, password, ident); |
175 | } |
176 | |
177 | int disorder_connect(disorder_client *c) { |
178 | return with_sockaddr(c, connect_sock); |
179 | } |
180 | |
181 | static int check_running(void attribute((unused)) *c, |
182 | const struct sockaddr *sa, |
183 | socklen_t len, |
184 | const char attribute((unused)) *ident) { |
185 | int fd, ret; |
186 | |
187 | if((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) |
188 | fatal(errno, "error calling socket"); |
189 | if(connect(fd, sa, len) < 0) { |
190 | if(errno == ECONNREFUSED || errno == ENOENT) |
191 | ret = 0; |
192 | else |
193 | fatal(errno, "error calling connect"); |
194 | } else |
195 | ret = 1; |
196 | xclose(fd); |
197 | return ret; |
198 | } |
199 | |
200 | int disorder_running(disorder_client *c) { |
201 | return with_sockaddr(c, check_running); |
202 | } |
203 | |
204 | int disorder_connect_sock(disorder_client *c, |
205 | const struct sockaddr *sa, |
206 | socklen_t len, |
207 | const char *username, |
208 | const char *password, |
209 | const char *ident) { |
210 | int fd = -1, fd2 = -1; |
211 | unsigned char *nonce; |
212 | size_t nl; |
213 | const char *res; |
214 | char *r; |
215 | |
216 | if(!password) { |
217 | error(0, "no password found"); |
218 | return -1; |
219 | } |
220 | c->fpin = c->fpout = 0; |
221 | if((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) { |
222 | error(errno, "error calling socket"); |
223 | return -1; |
224 | } |
225 | if(connect(fd, sa, len) < 0) { |
226 | error(errno, "error calling connect"); |
227 | goto error; |
228 | } |
229 | if((fd2 = dup(fd)) < 0) { |
230 | error(errno, "error calling dup"); |
231 | goto error; |
232 | } |
233 | if(!(c->fpin = fdopen(fd, "rb"))) { |
234 | error(errno, "error calling fdopen"); |
235 | goto error; |
236 | } |
237 | fd = -1; |
238 | if(!(c->fpout = fdopen(fd2, "wb"))) { |
239 | error(errno, "error calling fdopen"); |
240 | goto error; |
241 | } |
242 | fd2 = -1; |
243 | c->ident = xstrdup(ident); |
244 | if(disorder_simple(c, &r, 0, (const char *)0)) |
245 | return -1; |
246 | if(!(nonce = unhex(r, &nl))) |
247 | return -1; |
248 | if(!(res = authhash(nonce, nl, password))) goto error; |
249 | if(disorder_simple(c, 0, "user", username, res, (char *)0)) |
250 | return -1; |
251 | c->user = xstrdup(username); |
252 | return 0; |
253 | error: |
254 | if(c->fpin) fclose(c->fpin); |
255 | if(c->fpout) fclose(c->fpout); |
256 | if(fd2 != -1) close(fd2); |
257 | if(fd != -1) close(fd); |
258 | return -1; |
259 | } |
260 | |
261 | int disorder_close(disorder_client *c) { |
262 | int ret = 0; |
263 | |
264 | if(c->fpin) { |
265 | if(fclose(c->fpin) < 0) { |
266 | error(errno, "error calling fclose"); |
267 | ret = -1; |
268 | } |
269 | c->fpin = 0; |
270 | } |
271 | if(c->fpout) { |
272 | if(fclose(c->fpout) < 0) { |
273 | error(errno, "error calling fclose"); |
274 | ret = -1; |
275 | } |
276 | c->fpout = 0; |
277 | } |
278 | return 0; |
279 | } |
280 | |
281 | int disorder_become(disorder_client *c, const char *user) { |
282 | if(disorder_simple(c, 0, "become", user, (char *)0)) return -1; |
283 | c->user = xstrdup(user); |
284 | return 0; |
285 | } |
286 | |
287 | int disorder_play(disorder_client *c, const char *track) { |
288 | return disorder_simple(c, 0, "play", track, (char *)0); |
289 | } |
290 | |
291 | int disorder_remove(disorder_client *c, const char *track) { |
292 | return disorder_simple(c, 0, "remove", track, (char *)0); |
293 | } |
294 | |
295 | int disorder_move(disorder_client *c, const char *track, int delta) { |
296 | char d[16]; |
297 | |
298 | byte_snprintf(d, sizeof d, "%d", delta); |
299 | return disorder_simple(c, 0, "move", track, d, (char *)0); |
300 | } |
301 | |
302 | int disorder_enable(disorder_client *c) { |
303 | return disorder_simple(c, 0, "enable", (char *)0); |
304 | } |
305 | |
306 | int disorder_disable(disorder_client *c) { |
307 | return disorder_simple(c, 0, "disable", (char *)0); |
308 | } |
309 | |
310 | int disorder_scratch(disorder_client *c, const char *id) { |
311 | return disorder_simple(c, 0, "scratch", id, (char *)0); |
312 | } |
313 | |
314 | int disorder_shutdown(disorder_client *c) { |
315 | return disorder_simple(c, 0, "shutdown", (char *)0); |
316 | } |
317 | |
318 | int disorder_reconfigure(disorder_client *c) { |
319 | return disorder_simple(c, 0, "reconfigure", (char *)0); |
320 | } |
321 | |
322 | int disorder_rescan(disorder_client *c) { |
323 | return disorder_simple(c, 0, "rescan", (char *)0); |
324 | } |
325 | |
326 | int disorder_version(disorder_client *c, char **rp) { |
327 | return disorder_simple(c, rp, "version", (char *)0); |
328 | } |
329 | |
330 | static void client_error(const char *msg, |
331 | void attribute((unused)) *u) { |
332 | error(0, "error parsing reply: %s", msg); |
333 | } |
334 | |
335 | int disorder_playing(disorder_client *c, struct queue_entry **qp) { |
336 | char *r; |
337 | struct queue_entry *q; |
338 | |
339 | if(disorder_simple(c, &r, "playing", (char *)0)) |
340 | return -1; |
341 | if(r) { |
342 | q = xmalloc(sizeof *q); |
343 | if(queue_unmarshall(q, r, client_error, 0)) |
344 | return -1; |
345 | *qp = q; |
346 | } else |
347 | *qp = 0; |
348 | return 0; |
349 | } |
350 | |
351 | static int disorder_somequeue(disorder_client *c, |
352 | const char *cmd, struct queue_entry **qp) { |
353 | struct queue_entry *qh, **qt = &qh, *q; |
354 | char *l; |
355 | |
356 | if(disorder_simple(c, 0, cmd, (char *)0)) |
357 | return -1; |
358 | while(inputline(c->ident, c->fpin, &l, '\n') >= 0) { |
359 | if(!strcmp(l, ".")) { |
360 | *qt = 0; |
361 | *qp = qh; |
362 | return 0; |
363 | } |
364 | q = xmalloc(sizeof *q); |
365 | if(!queue_unmarshall(q, l, client_error, 0)) { |
366 | *qt = q; |
367 | qt = &q->next; |
368 | } |
369 | } |
370 | if(ferror(c->fpin)) |
371 | error(errno, "error reading %s", c->ident); |
372 | else |
373 | error(0, "error reading %s: unexpected EOF", c->ident); |
374 | return -1; |
375 | } |
376 | |
377 | int disorder_recent(disorder_client *c, struct queue_entry **qp) { |
378 | return disorder_somequeue(c, "recent", qp); |
379 | } |
380 | |
381 | int disorder_queue(disorder_client *c, struct queue_entry **qp) { |
382 | return disorder_somequeue(c, "queue", qp); |
383 | } |
384 | |
385 | static int readlist(disorder_client *c, char ***vecp, int *nvecp) { |
386 | char *l; |
387 | struct vector v; |
388 | |
389 | vector_init(&v); |
390 | while(inputline(c->ident, c->fpin, &l, '\n') >= 0) { |
391 | if(!strcmp(l, ".")) { |
392 | vector_terminate(&v); |
393 | if(nvecp) |
394 | *nvecp = v.nvec; |
395 | *vecp = v.vec; |
396 | return 0; |
397 | } |
398 | vector_append(&v, l + (*l == '.')); |
399 | } |
400 | if(ferror(c->fpin)) |
401 | error(errno, "error reading %s", c->ident); |
402 | else |
403 | error(0, "error reading %s: unexpected EOF", c->ident); |
404 | return -1; |
405 | } |
406 | |
407 | static int disorder_simple_list(disorder_client *c, |
408 | char ***vecp, int *nvecp, |
409 | const char *cmd, ...) { |
410 | va_list ap; |
411 | int ret; |
412 | |
413 | va_start(ap, cmd); |
414 | ret = disorder_simple_v(c, 0, cmd, ap); |
415 | va_end(ap); |
416 | if(ret) return ret; |
417 | return readlist(c, vecp, nvecp); |
418 | } |
419 | |
420 | int disorder_directories(disorder_client *c, const char *dir, const char *re, |
421 | char ***vecp, int *nvecp) { |
422 | return disorder_simple_list(c, vecp, nvecp, "dirs", dir, re, (char *)0); |
423 | } |
424 | |
425 | int disorder_files(disorder_client *c, const char *dir, const char *re, |
426 | char ***vecp, int *nvecp) { |
427 | return disorder_simple_list(c, vecp, nvecp, "files", dir, re, (char *)0); |
428 | } |
429 | |
430 | int disorder_allfiles(disorder_client *c, const char *dir, const char *re, |
431 | char ***vecp, int *nvecp) { |
432 | return disorder_simple_list(c, vecp, nvecp, "allfiles", dir, re, (char *)0); |
433 | } |
434 | |
435 | char *disorder_user(disorder_client *c) { |
436 | return c->user; |
437 | } |
438 | |
439 | int disorder_set(disorder_client *c, const char *track, |
440 | const char *key, const char *value) { |
441 | return disorder_simple(c, 0, "set", track, key, value, (char *)0); |
442 | } |
443 | |
444 | int disorder_unset(disorder_client *c, const char *track, |
445 | const char *key) { |
446 | return disorder_simple(c, 0, "unset", track, key, (char *)0); |
447 | } |
448 | |
449 | int disorder_get(disorder_client *c, |
450 | const char *track, const char *key, char **valuep) { |
451 | return disorder_simple(c, valuep, "get", track, key, (char *)0); |
452 | } |
453 | |
454 | static void pref_error_handler(const char *msg, |
455 | void attribute((unused)) *u) { |
456 | error(0, "error handling 'prefs' reply: %s", msg); |
457 | } |
458 | |
459 | int disorder_prefs(disorder_client *c, const char *track, struct kvp **kp) { |
460 | char **vec, **pvec; |
461 | int nvec, npvec, n; |
462 | struct kvp *k; |
463 | |
464 | if(disorder_simple_list(c, &vec, &nvec, "prefs", track, (char *)0)) |
465 | return -1; |
466 | for(n = 0; n < nvec; ++n) { |
467 | if(!(pvec = split(vec[n], &npvec, SPLIT_QUOTES, pref_error_handler, 0))) |
468 | return -1; |
469 | if(npvec != 2) { |
470 | pref_error_handler("malformed response", 0); |
471 | return -1; |
472 | } |
473 | *kp = k = xmalloc(sizeof *k); |
474 | k->name = pvec[0]; |
475 | k->value = pvec[1]; |
476 | kp = &k->next; |
477 | } |
478 | *kp = 0; |
479 | return 0; |
480 | } |
481 | |
482 | static int boolean(const char *cmd, const char *value, |
483 | int *flagp) { |
484 | if(!strcmp(value, "yes")) *flagp = 1; |
485 | else if(!strcmp(value, "no")) *flagp = 0; |
486 | else { |
487 | error(0, "malformed response to '%s'", cmd); |
488 | return -1; |
489 | } |
490 | return 0; |
491 | } |
492 | |
493 | int disorder_exists(disorder_client *c, const char *track, int *existsp) { |
494 | char *v; |
495 | |
496 | if(disorder_simple(c, &v, "exists", track, (char *)0)) return -1; |
497 | return boolean("exists", v, existsp); |
498 | } |
499 | |
500 | int disorder_enabled(disorder_client *c, int *enabledp) { |
501 | char *v; |
502 | |
503 | if(disorder_simple(c, &v, "enabled", (char *)0)) return -1; |
504 | return boolean("enabled", v, enabledp); |
505 | } |
506 | |
507 | int disorder_length(disorder_client *c, const char *track, |
508 | long *valuep) { |
509 | char *value; |
510 | |
511 | if(disorder_simple(c, &value, "length", track, (char *)0)) return -1; |
512 | *valuep = atol(value); |
513 | return 0; |
514 | } |
515 | |
516 | int disorder_search(disorder_client *c, const char *terms, |
517 | char ***vecp, int *nvecp) { |
518 | return disorder_simple_list(c, vecp, nvecp, "search", terms, (char *)0); |
519 | } |
520 | |
521 | int disorder_random_enable(disorder_client *c) { |
522 | return disorder_simple(c, 0, "random-enable", (char *)0); |
523 | } |
524 | |
525 | int disorder_random_disable(disorder_client *c) { |
526 | return disorder_simple(c, 0, "random-disable", (char *)0); |
527 | } |
528 | |
529 | int disorder_random_enabled(disorder_client *c, int *enabledp) { |
530 | char *v; |
531 | |
532 | if(disorder_simple(c, &v, "random-enabled", (char *)0)) return -1; |
533 | return boolean("random-enabled", v, enabledp); |
534 | } |
535 | |
536 | int disorder_stats(disorder_client *c, |
537 | char ***vecp, int *nvecp) { |
538 | return disorder_simple_list(c, vecp, nvecp, "stats", (char *)0); |
539 | } |
540 | |
541 | int disorder_set_volume(disorder_client *c, int left, int right) { |
542 | char *ls, *rs; |
543 | |
544 | if(byte_asprintf(&ls, "%d", left) < 0 |
545 | || byte_asprintf(&rs, "%d", right) < 0) |
546 | return -1; |
547 | if(disorder_simple(c, 0, "volume", ls, rs, (char *)0)) return -1; |
548 | return 0; |
549 | } |
550 | |
551 | int disorder_get_volume(disorder_client *c, int *left, int *right) { |
552 | char *r; |
553 | |
554 | if(disorder_simple(c, &r, "volume", (char *)0)) return -1; |
555 | if(sscanf(r, "%d %d", left, right) != 2) { |
556 | error(0, "error parsing response to 'volume': '%s'", r); |
557 | return -1; |
558 | } |
559 | return 0; |
560 | } |
561 | |
562 | int disorder_log(disorder_client *c, struct sink *s) { |
563 | char *l; |
564 | |
565 | if(disorder_simple(c, 0, "log", (char *)0)) return -1; |
566 | while(inputline(c->ident, c->fpin, &l, '\n') >= 0 && strcmp(l, ".")) |
567 | if(sink_printf(s, "%s\n", l) < 0) return -1; |
568 | if(ferror(c->fpin) || feof(c->fpin)) return -1; |
569 | return 0; |
570 | } |
571 | |
572 | int disorder_part(disorder_client *c, char **partp, |
573 | const char *track, const char *context, const char *part) { |
574 | return disorder_simple(c, partp, "part", track, context, part, (char *)0); |
575 | } |
576 | |
577 | int disorder_resolve(disorder_client *c, char **trackp, const char *track) { |
578 | return disorder_simple(c, trackp, "resolve", track, (char *)0); |
579 | } |
580 | |
581 | int disorder_pause(disorder_client *c) { |
582 | return disorder_simple(c, 0, "pause", (char *)0); |
583 | } |
584 | |
585 | int disorder_resume(disorder_client *c) { |
586 | return disorder_simple(c, 0, "resume", (char *)0); |
587 | } |
588 | |
589 | int disorder_tags(disorder_client *c, |
590 | char ***vecp, int *nvecp) { |
591 | return disorder_simple_list(c, vecp, nvecp, "tags", (char *)0); |
592 | } |
593 | |
594 | int disorder_set_global(disorder_client *c, |
595 | const char *key, const char *value) { |
596 | return disorder_simple(c, 0, "set-global", key, value, (char *)0); |
597 | } |
598 | |
599 | int disorder_unset_global(disorder_client *c, const char *key) { |
600 | return disorder_simple(c, 0, "unset-global", key, (char *)0); |
601 | } |
602 | |
603 | int disorder_get_global(disorder_client *c, const char *key, char **valuep) { |
604 | return disorder_simple(c, valuep, "get-global", key, (char *)0); |
605 | } |
606 | |
607 | /* |
608 | Local Variables: |
609 | c-basic-offset:2 |
610 | comment-column:40 |
611 | End: |
612 | */ |