1/* 2 * $Id: webserver.c,v 1.1 2009-06-30 02:31:09 steven Exp $ 3 * Webserver library 4 * 5 * Copyright (C) 2003 Ron Pedde (ron@pedde.com) 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 */ 21 22#ifdef HAVE_CONFIG_H 23# include "config.h" 24#endif 25 26#include <ctype.h> 27#include <errno.h> 28#include <fcntl.h> 29#include <limits.h> 30#include <pthread.h> 31#include <regex.h> 32#include <restart.h> 33#include <stdarg.h> 34#include <stdio.h> 35#include <stdlib.h> 36#include <string.h> 37#include <time.h> 38#include <uici.h> 39 40#include <sys/param.h> 41#include <sys/types.h> 42#include <sys/socket.h> 43 44#include "err.h" 45#include "webserver.h" 46 47/* 48 * Defines 49 */ 50 51#define MAX_HOSTNAME 256 52#define MAX_LINEBUFFER 4096 53 54/* 55 * Local (private) typedefs 56 */ 57 58typedef struct tag_ws_handler { 59 regex_t regex; 60 void (*req_handler)(WS_CONNINFO*); 61 int(*auth_handler)(char *, char *); 62 int addheaders; 63 struct tag_ws_handler *next; 64} WS_HANDLER; 65 66typedef struct tag_ws_connlist { 67 WS_CONNINFO *pwsc; 68 char *status; 69 struct tag_ws_connlist *next; 70} WS_CONNLIST; 71 72typedef struct tag_ws_private { 73 WSCONFIG wsconfig; 74 WS_HANDLER handlers; 75 WS_CONNLIST connlist; 76 int server_fd; 77 int stop; 78 int running; 79 int threadno; 80 int dispatch_threads; 81 pthread_t server_tid; 82 pthread_cond_t exit_cond; 83 pthread_mutex_t exit_mutex; 84} WS_PRIVATE; 85 86 87/* 88 * Forwards 89 */ 90void *ws_mainthread(void*); 91void *ws_dispatcher(void*); 92int ws_lock_unsafe(void); 93int ws_unlock_unsafe(void); 94void ws_defaulthandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc); 95int ws_addarg(ARGLIST *root, char *key, char *fmt, ...); 96void ws_freearglist(ARGLIST *root); 97char *ws_urldecode(char *string, int space_as_plus); 98int ws_getheaders(WS_CONNINFO *pwsc); 99int ws_getpostvars(WS_CONNINFO *pwsc); 100int ws_getgetvars(WS_CONNINFO *pwsc, char *string); 101char *ws_getarg(ARGLIST *root, char *key); 102int ws_testarg(ARGLIST *root, char *key, char *value); 103int ws_findhandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc, 104 void(**preq)(WS_CONNINFO*), 105 int(**pauth)(char *, char *), 106 int *addheaders); 107int ws_registerhandler(WSHANDLE ws, char *regex, 108 void(*handler)(WS_CONNINFO*), 109 int(*auth)(char *, char *), 110 int addheaders); 111int ws_decodepassword(char *header, char **username, char **password); 112int ws_testrequestheader(WS_CONNINFO *pwsc, char *header, char *value); 113char *ws_getrequestheader(WS_CONNINFO *pwsc, char *header); 114static void ws_add_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc); 115static void ws_remove_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc); 116static int ws_encoding_hack(WS_CONNINFO *pwsc); 117 118/* 119 * Globals 120 */ 121pthread_mutex_t ws_unsafe=PTHREAD_MUTEX_INITIALIZER; 122 123char *ws_dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; 124char *ws_moy[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", 125 "Aug", "Sep", "Oct", "Nov", "Dec" }; 126 127/* 128 * ws_lock_unsafe 129 * 130 * Lock non-thread-safe functions 131 * 132 * returns 0 on success, 133 * returns -1 on failure, with errno set 134 */ 135int ws_lock_unsafe(void) { 136 int err; 137 int retval=0; 138 139 DPRINTF(E_SPAM,L_WS,"Entering ws_lock_unsafe\n"); 140 141 if((err=pthread_mutex_lock(&ws_unsafe))) { 142 errno=err; 143 retval=-1; 144 } 145 146 DPRINTF(E_SPAM,L_WS,"Exiting ws_lock_unsafe with retval of %d\n",retval); 147 return retval; 148} 149 150/* 151 * ws_unlock_unsafe 152 * 153 * Lock non-thread-safe functions 154 * 155 * returns 0 on success, 156 * returns -1 on failure, with errno set 157 */ 158int ws_unlock_unsafe(void) { 159 int err; 160 int retval=0; 161 162 DPRINTF(E_SPAM,L_WS,"Entering ws_unlock_unsafe\n"); 163 164 if((err=pthread_mutex_unlock(&ws_unsafe))) { 165 errno=err; 166 retval=-1; 167 } 168 169 DPRINTF(E_SPAM,L_WS,"Exiting ws_unlock_unsafe with a retval of %d\n",retval); 170 return retval; 171} 172 173/* 174 * ws_start 175 * 176 * Start the main webserver thread. Should really 177 * bind and listen to the port before spawning the thread, 178 * since it will be hard to detect and return the error unless 179 * we listen first 180 * 181 * RETURNS 182 * Success: WSHANDLE 183 * Failure: NULL, with errno set 184 * 185 */ 186WSHANDLE ws_start(WSCONFIG *config) { 187 int err; 188 WS_PRIVATE *pwsp; 189 190 DPRINTF(E_SPAM,L_WS,"Entering ws_start\n"); 191 192 if((pwsp=(WS_PRIVATE*)malloc(sizeof(WS_PRIVATE))) == NULL) { 193 DPRINTF(E_SPAM,L_WS,"Malloc error: %s\n",strerror(errno)); 194 return NULL; 195 } 196 197 memcpy(&pwsp->wsconfig,config,sizeof(WS_PRIVATE)); 198 pwsp->connlist.next=NULL; 199 pwsp->running=0; 200 pwsp->threadno=0; 201 pwsp->stop=0; 202 pwsp->dispatch_threads=0; 203 pwsp->handlers.next=NULL; 204 205 if((err=pthread_cond_init(&pwsp->exit_cond, NULL))) { 206 errno=err; 207 DPRINTF(E_LOG,L_WS,"Error in pthread_cond_init: %s\n",strerror(errno)); 208 return NULL; 209 } 210 211 if((err=pthread_mutex_init(&pwsp->exit_mutex,NULL))) { 212 errno=err; 213 DPRINTF(E_LOG,L_WS,"Error in pthread_mutex_init: %s\n",strerror(errno)); 214 return NULL; 215 } 216 217 DPRINTF(E_INF,L_WS,"Preparing to listen on port %d\n",pwsp->wsconfig.port); 218 219 if((pwsp->server_fd = u_open(pwsp->wsconfig.port)) == -1) { 220 err=errno; 221 DPRINTF(E_LOG,L_WS,"Could not open port: %s\n",strerror(errno)); 222 errno=err; 223 return NULL; 224 } 225 226 DPRINTF(E_INF,L_WS,"Starting server thread\n"); 227 if((err=pthread_create(&pwsp->server_tid,NULL,ws_mainthread,(void*)pwsp))) { 228 DPRINTF(E_LOG,L_WS,"Could not spawn thread: %s\n",strerror(err)); 229 r_close(pwsp->server_fd); 230 errno=err; 231 return NULL; 232 } 233 234 /* we're really running */ 235 pwsp->running=1; 236 237 DPRINTF(E_SPAM,L_WS,"Exiting ws_start\n"); 238 return (WSHANDLE)pwsp; 239} 240 241 242/* 243 * ws_remove_dispatch_thread 244 * 245 * remove a dispatch thread from the thread list 246 */ 247void ws_remove_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc) { 248 WS_CONNLIST *pHead, *pTail; 249 250 DPRINTF(E_SPAM,L_WS,"Entering ws_remove_dispatch_thread\n"); 251 252 if(pthread_mutex_lock(&pwsp->exit_mutex)) 253 DPRINTF(E_FATAL,L_WS,"Cannot lock condition mutex\n"); 254 255 pTail=&(pwsp->connlist); 256 pHead=pwsp->connlist.next; 257 258 while((pHead) && (pHead->pwsc != pwsc)) { 259 pTail=pHead; 260 pHead=pHead->next; 261 } 262 263 if(pHead) { 264 pwsp->dispatch_threads--; 265 DPRINTF(E_DBG,L_WS,"With thread %d exiting, %d are still running\n", 266 pwsc->threadno,pwsp->dispatch_threads); 267 268 pTail->next = pHead->next; 269 270 if(pHead->status) 271 free(pHead->status); 272 free(pHead); 273 274 /* signal condition in case something is waiting */ 275 pthread_cond_signal(&pwsp->exit_cond); 276 } 277 278 pthread_mutex_unlock(&pwsp->exit_mutex); 279 DPRINTF(E_SPAM,L_WS,"Exiting ws_remote_dispatch_thread\n"); 280} 281 282 283/* 284 * ws_add_dispatch_thread 285 * 286 * Add a thread to the dispatch thread list 287 */ 288void ws_add_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc) { 289 WS_CONNLIST *pNew; 290 291 DPRINTF(E_SPAM,L_WS,"Entering ws_add_dispatch_thread\n"); 292 293 pNew=(WS_CONNLIST*)malloc(sizeof(WS_CONNLIST)); 294 pNew->next=NULL; 295 pNew->pwsc=pwsc; 296 pNew->status=strdup("Initializing"); 297 298 if(!pNew) 299 DPRINTF(E_FATAL,L_WS,"Malloc: %s\n",strerror(errno)); 300 301 if(pthread_mutex_lock(&pwsp->exit_mutex)) 302 DPRINTF(E_FATAL,L_WS,"Cannot lock condition mutex\n"); 303 304 /* list is locked... */ 305 pwsp->dispatch_threads++; 306 pNew->next = pwsp->connlist.next; 307 pwsp->connlist.next = pNew; 308 309 pthread_mutex_unlock(&pwsp->exit_mutex); 310 DPRINTF(E_SPAM,L_WS,"Exiting ws_add_dispatch_thread\n"); 311} 312 313/* 314 * ws_stop 315 * 316 * Stop the web server and all the child threads 317 */ 318extern int ws_stop(WSHANDLE ws) { 319 WS_PRIVATE *pwsp = (WS_PRIVATE*)ws; 320 WS_HANDLER *current; 321 WS_CONNLIST *pcl; 322 void *result; 323 324 DPRINTF(E_DBG,L_WS,"Entering ws_stop: %d threads\n",pwsp->dispatch_threads); 325 326 /* free the ws_handlers */ 327 while(pwsp->handlers.next) { 328 current=pwsp->handlers.next; 329 pwsp->handlers.next=current->next; 330 regfree(¤t->regex); 331 free(current); 332 } 333 334 pwsp->stop=1; 335 pwsp->running=0; 336 337 DPRINTF(E_DBG,L_WS,"ws_stop: closing the server fd\n"); 338 shutdown(pwsp->server_fd,SHUT_RDWR); 339 r_close(pwsp->server_fd); /* this should tick off the listener */ 340 341 /* wait for the server thread to terminate. SHould be quick! */ 342 pthread_join(pwsp->server_tid,&result); 343 344 /* Give the threads an extra push */ 345 if(pthread_mutex_lock(&pwsp->exit_mutex)) 346 DPRINTF(E_FATAL,L_WS,"Cannot lock condition mutex\n"); 347 348 pcl=pwsp->connlist.next; 349 350 /* Closing the client sockets out from under the dispatch threads 351 * should cause the dispatch threads to exit out with an error. 352 */ 353 while(pcl) { 354 if(pcl->pwsc->fd) { 355 shutdown(pcl->pwsc->fd,SHUT_RDWR); 356 r_close(pcl->pwsc->fd); 357 } 358 pcl=pcl->next; 359 } 360 361 /* wait for the threads to be done */ 362 while(pwsp->dispatch_threads) { 363 DPRINTF(E_DBG,L_WS,"ws_stop: I still see %d threads\n",pwsp->dispatch_threads); 364 pthread_cond_wait(&pwsp->exit_cond, &pwsp->exit_mutex); 365 } 366 367 pthread_mutex_unlock(&pwsp->exit_mutex); 368 369 free(pwsp); 370 371 DPRINTF(E_SPAM,L_WS,"Exiting ws_stop\n"); 372 return 0; 373} 374 375/* 376 * ws_mainthread 377 * 378 * Main thread for webserver - this accepts connections 379 * and spawns a handler thread for each incoming connection. 380 * 381 * For a persistant connection, these threads will be 382 * long-lived, otherwise, they will terminate as soon as 383 * the request has been honored. 384 * 385 * These client threads will, of course, be detached 386 */ 387void *ws_mainthread(void *arg) { 388 int fd; 389 int err; 390 WS_PRIVATE *pwsp = (WS_PRIVATE*)arg; 391 WS_CONNINFO *pwsc; 392 pthread_t tid; 393 char hostname[MAX_HOSTNAME]; 394 395 DPRINTF(E_SPAM,L_WS,"Entering ws_mainthread\n"); 396 397 while(1) { 398 pwsc=(WS_CONNINFO*)malloc(sizeof(WS_CONNINFO)); 399 if(!pwsc) { 400 /* can't very well service any more threads! */ 401 DPRINTF(E_FATAL,L_WS,"Error: %s\n",strerror(errno)); 402 pwsp->running=0; 403 return NULL; 404 } 405 406 memset(pwsc,0,sizeof(WS_CONNINFO)); 407 408 if((fd=u_accept(pwsp->server_fd,hostname,MAX_HOSTNAME)) == -1) { 409 DPRINTF(E_LOG,L_WS,"Dispatcher: accept failed: %s\n",strerror(errno)); 410 shutdown(pwsp->server_fd,SHUT_RDWR); 411 r_close(pwsp->server_fd); 412 pwsp->running=0; 413 free(pwsc); 414 415 DPRINTF(E_FATAL,L_WS,"Dispatcher: Aborting\n"); 416 return NULL; 417 } 418 419 pwsc->hostname=strdup(hostname); 420 pwsc->fd=fd; 421 pwsc->pwsp = pwsp; 422 423 /* Spawn off a dispatcher to decide what to do with 424 * the request 425 */ 426 427 /* don't really care if it locks or not */ 428 ws_lock_unsafe(); 429 pwsc->threadno=pwsp->threadno; 430 pwsp->threadno++; 431 ws_unlock_unsafe(); 432 433 /* now, throw off a dispatch thread */ 434 if((err=pthread_create(&tid,NULL,ws_dispatcher,(void*)pwsc))) { 435 pwsc->error=err; 436 DPRINTF(E_FATAL,L_WS,"Could not spawn thread: %s\n",strerror(err)); 437 ws_close(pwsc); 438 } else { 439 ws_add_dispatch_thread(pwsp,pwsc); 440 pthread_detach(tid); 441 } 442 } 443 444 DPRINTF(E_SPAM,L_WS,"Exiting ws_mainthred\n"); 445} 446 447 448/* 449 * ws_close 450 * 451 * Close the connection. This might be called when things 452 * are already in bad shape, so we'll ignore errors and let 453 * them be detected back in either the dispatch 454 * thread or the main server thread 455 * 456 * Mainly, we just want to make sure that any 457 * allocated memory has been freed 458 */ 459void ws_close(WS_CONNINFO *pwsc) { 460 WS_PRIVATE *pwsp = (WS_PRIVATE *)pwsc->pwsp; 461 462 463 DPRINTF(E_SPAM,L_WS,"Entering ws_close\n"); 464 465 /* DWB: update the status so it doesn't fill up with no longer 466 relevant entries */ 467 config_set_status(pwsc, 0, NULL); 468 469 DPRINTF(E_DBG,L_WS,"Thread %d: Terminating\n",pwsc->threadno); 470 DPRINTF(E_DBG,L_WS,"Thread %d: Freeing request headers\n",pwsc->threadno); 471 ws_freearglist(&pwsc->request_headers); 472 DPRINTF(E_DBG,L_WS,"Thread %d: Freeing response headers\n",pwsc->threadno); 473 ws_freearglist(&pwsc->response_headers); 474 DPRINTF(E_DBG,L_WS,"Thread %d: Freeing request vars\n",pwsc->threadno); 475 ws_freearglist(&pwsc->request_vars); 476 if(pwsc->uri) { 477 free(pwsc->uri); 478 pwsc->uri=NULL; 479 } 480 481 if((pwsc->close)||(pwsc->error)) { 482 DPRINTF(E_DBG,L_WS,"Thread %d: Closing fd\n",pwsc->threadno); 483 shutdown(pwsc->fd,SHUT_RDWR); 484 r_close(pwsc->fd); 485 free(pwsc->hostname); 486 487 /* this thread is done */ 488 ws_remove_dispatch_thread(pwsp, pwsc); 489 490 free(pwsc); 491 DPRINTF(E_SPAM,L_WS,"Exiting ws_close (thread terminating)\n"); 492 pthread_exit(NULL); 493 } 494 DPRINTF(E_SPAM,L_WS,"Exiting ws_close (thread continuing)\n"); 495} 496 497/* 498 * ws_freearglist 499 * 500 * Walks through an arg list freeing as it goes 501 */ 502void ws_freearglist(ARGLIST *root) { 503 ARGLIST *current; 504 505 DPRINTF(E_SPAM,L_WS,"Entering ws_freearglist\n"); 506 507 while(root->next) { 508 free(root->next->key); 509 free(root->next->value); 510 current=root->next; 511 root->next=current->next; 512 free(current); 513 } 514 515 DPRINTF(E_SPAM,L_WS,"Exiting ws_freearglist\n"); 516} 517 518 519void ws_emitheaders(WS_CONNINFO *pwsc) { 520 ARGLIST *pcurrent=pwsc->response_headers.next; 521 522 DPRINTF(E_SPAM,L_WS,"Entering ws_emitheaders\n"); 523 while(pcurrent) { 524 DPRINTF(E_DBG,L_WS,"Emitting reponse header %s: %s\n",pcurrent->key, 525 pcurrent->value); 526 ws_writefd(pwsc,"%s: %s\r\n",pcurrent->key,pcurrent->value); 527 pcurrent=pcurrent->next; 528 } 529 530 ws_writefd(pwsc,"\r\n"); 531 DPRINTF(E_SPAM,L_WS,"Exitin ws_emitheaders\n"); 532} 533 534 535/* 536 * ws_getpostvars 537 * 538 * Receive and parse headers. These will trump 539 * get headers 540 */ 541int ws_getpostvars(WS_CONNINFO *pwsc) { 542 char *content_length; 543 int length; 544 char *buffer; 545 546 DPRINTF(E_SPAM,L_WS,"Entering ws_getpostvars\n"); 547 548 content_length = ws_getarg(&pwsc->request_headers,"Content-Length"); 549 if(!content_length) { 550 pwsc->error = EINVAL; 551 return -1; 552 } 553 554 length=strtol(content_length, NULL, 10); 555 if(EINVAL == errno || UINT_MAX -1 <= length) { 556 DPRINTF(E_WARN,L_WS, "Thread %d: Suspicious Content-Length value, ignoring request\n",pwsc->threadno); 557 return -1; 558 } 559 560 DPRINTF(E_DBG,L_WS,"Thread %d: Post var length: %d\n", 561 pwsc->threadno,length); 562 563 buffer=(char*)malloc(length+1); 564 565 if(!buffer) { 566 pwsc->error = errno; 567 DPRINTF(E_INF,L_WS,"Thread %d: Could not malloc %d bytes\n", 568 pwsc->threadno, length); 569 return -1; 570 } 571 572 // make the read time out 30 minutes like we said in the 573 // /server-info response 574 if((readtimed(pwsc->fd, buffer, length, 1800.0)) == -1) { 575 DPRINTF(E_INF,L_WS,"Thread %d: Timeout reading post vars\n", 576 pwsc->threadno); 577 pwsc->error=errno; 578 return -1; 579 } 580 581 DPRINTF(E_DBG,L_WS,"Thread %d: Read post vars: %s\n",pwsc->threadno,buffer); 582 583 pwsc->error=ws_getgetvars(pwsc,buffer); 584 585 free(buffer); 586 587 DPRINTF(E_SPAM,L_WS,"Exiting ws_getpostvars\n"); 588 return pwsc->error; 589} 590 591 592/* 593 * ws_getheaders 594 * 595 * Receive and parse headers. This is called from 596 * ws_dispatcher 597 */ 598int ws_getheaders(WS_CONNINFO *pwsc) { 599 char *first, *last; 600 int done; 601 char buffer[MAX_LINEBUFFER]; 602 603 DPRINTF(E_SPAM,L_WS,"Entering ws_getheaders\n"); 604 605 /* Break down the headers into some kind of header list */ 606 done=0; 607 while(!done) { 608 if(readline(pwsc->fd,buffer,sizeof(buffer)) == -1) { 609 pwsc->error=errno; 610 DPRINTF(E_INF,L_WS,"Thread %d: Unexpected close\n",pwsc->threadno); 611 return -1; 612 } 613 614 DPRINTF(E_DBG,L_WS,"Thread %d: Read: %s",pwsc->threadno,buffer); 615 616 first=buffer; 617 if(buffer[0] == '\r') 618 first=&buffer[1]; 619 620 /* trim the trailing \n */ 621 if(first[strlen(first)-1] == '\n') 622 first[strlen(first)-1] = '\0'; 623 624 if(strlen(first) == 0) { 625 DPRINTF(E_DBG,L_WS,"Thread %d: Headers parsed!\n",pwsc->threadno); 626 done=1; 627 } else { 628 /* we have a header! */ 629 last=first; 630 strsep(&last,":"); 631 632 if(!last) { 633 DPRINTF(E_WARN,L_WS,"Thread %d: Invalid header: %s\n", 634 pwsc->threadno,first); 635 } else { 636 while(*last==' ') 637 last++; 638 639 while(last[strlen(last)-1] == '\r') 640 last[strlen(last)-1] = '\0'; 641 642 DPRINTF(E_DBG,L_WS,"Thread %d: Adding header *%s=%s*\n", 643 pwsc->threadno,first,last); 644 645 if(ws_addarg(&pwsc->request_headers,first,"%s",last)) { 646 DPRINTF(E_FATAL,L_WS,"Thread %d: Out of memory\n", 647 pwsc->threadno); 648 pwsc->error=ENOMEM; 649 return -1; 650 } 651 } 652 } 653 } 654 655 DPRINTF(E_SPAM,L_WS,"Exiting ws_getheaders\n"); 656 return 0; 657} 658 659 660/* 661 * ws_encoding_hack 662 */ 663int ws_encoding_hack(WS_CONNINFO *pwsc) { 664 char *user_agent; 665 int space_as_plus=1; 666 667 user_agent=ws_getrequestheader(pwsc, "user-agent"); 668 if(user_agent) { 669 if(strncasecmp(user_agent,"Roku",4) == 0) 670 space_as_plus=0; 671 if(strncasecmp(user_agent,"iTunes",6) == 0) 672 space_as_plus=0; 673 } 674 return space_as_plus; 675} 676 677 678/* 679 * ws_getgetvars 680 * 681 * parse a GET string of variables (or POST) 682 * 683 */ 684int ws_getgetvars(WS_CONNINFO *pwsc, char *string) { 685 char *new_string; 686 char *first, *last, *middle; 687 char *key, *value; 688 int done; 689 690 int space_as_plus; 691 692 DPRINTF(E_DBG,L_WS,"Thread %d: Entering ws_getgetvars (%s)\n", 693 pwsc->threadno,string); 694 695 space_as_plus=ws_encoding_hack(pwsc); 696 697 done=0; 698 699 first=string; 700 701 while((!done) && (first)) { 702 last=middle=first; 703 strsep(&last,"&"); 704 strsep(&middle,"="); 705 706 if(!middle) { 707 DPRINTF(E_WARN,L_WS,"Thread %d: Bad arg: %s\n", 708 pwsc->threadno,first); 709 } else { 710 key=ws_urldecode(first,space_as_plus); 711 value=ws_urldecode(middle,space_as_plus); 712 713 DPRINTF(E_DBG,L_WS,"Thread %d: Adding arg %s = %s\n", 714 pwsc->threadno,key,value); 715 ws_addarg(&pwsc->request_vars,key,"%s",value); 716 717 free(key); 718 free(value); 719 } 720 721 if(!last) { 722 DPRINTF(E_DBG,L_WS,"Thread %d: Done parsing GET/POST args!\n", 723 pwsc->threadno); 724 done=1; 725 } else { 726 first=last; 727 } 728 } 729 730 DPRINTF(E_SPAM,L_WS,"Exiting ws_getgetvars\n"); 731 return 0; 732} 733 734 735/* 736 * ws_dispatcher 737 * 738 * Main dispatch thread. This gets the request, reads the 739 * headers, decodes the GET'd or POST'd variables, 740 * then decides what function should service the request 741 */ 742void *ws_dispatcher(void *arg) { 743 WS_CONNINFO *pwsc=(WS_CONNINFO*)arg; 744 WS_PRIVATE *pwsp=pwsc->pwsp; 745 char buffer[MAX_LINEBUFFER]; 746 char *first,*last; 747 int connection_done=0; 748 int can_dispatch; 749 char *auth, *username, *password; 750 int hdrs,handler; 751 time_t now; 752 struct tm now_tm; 753 void (*req_handler)(WS_CONNINFO*); 754 int(*auth_handler)(char *, char *); 755 756 DPRINTF(E_DBG,L_WS,"Thread %d: Entering ws_dispatcher (Connection from %s)\n", 757 pwsc->threadno, pwsc->hostname); 758 759 while(!connection_done) { 760 /* Now, get the request from the other end 761 * and decide where to dispatch it 762 */ 763 764 /* DWB: set timeout to 30 minutes as advertised in the 765 server-info response. */ 766 if((readlinetimed(pwsc->fd,buffer,sizeof(buffer),1800.0)) < 1) { 767 pwsc->error=errno; 768 pwsc->close=1; 769 DPRINTF(E_WARN,L_WS,"Thread %d: could not read: %s\n", 770 pwsc->threadno,strerror(errno)); 771 ws_close(pwsc); 772 return NULL; 773 } 774 775 DPRINTF(E_DBG,L_WS,"Thread %d: got request\n",pwsc->threadno); 776 DPRINTF(E_DBG - 1,L_WS, "Request: %s", buffer); 777 778 first=last=buffer; 779 strsep(&last," "); 780 if(!last) { 781 pwsc->close=1; 782 ws_returnerror(pwsc,400,"Bad request\n"); 783 ws_close(pwsc); 784 DPRINTF(E_SPAM,L_WS,"Error: bad request. Exiting ws_dispatcher\n"); 785 return NULL; 786 } 787 788 if(!strcasecmp(first,"get")) { 789 pwsc->request_type = RT_GET; 790 } else if(!strcasecmp(first,"post")) { 791 pwsc->request_type = RT_POST; 792 } else { 793 /* return a 501 not implemented */ 794 pwsc->error=EINVAL; 795 pwsc->close=1; 796 ws_returnerror(pwsc,501,"Not implemented"); 797 ws_close(pwsc); 798 DPRINTF(E_SPAM,L_WS,"Error: not get or post. Exiting ws_dispatcher\n"); 799 return NULL; 800 } 801 802 first=last; 803 strsep(&last," "); 804 pwsc->uri=strdup(first); 805 806 /* Get headers */ 807 if((ws_getheaders(pwsc)) || (!last)) { /* didn't provide a HTTP/1.x */ 808 /* error already set */ 809 DPRINTF(E_LOG,L_WS,"Thread %d: Couldn't parse headers - aborting\n", 810 pwsc->threadno); 811 pwsc->close=1; 812 ws_close(pwsc); 813 return NULL; 814 } 815 816 817 /* Now that we have the headers, we can 818 * decide whether or not this is a persistant 819 * connection */ 820 if(strncasecmp(last,"HTTP/1.0",8)==0) { /* defaults to non-persistant */ 821 pwsc->close=!ws_testarg(&pwsc->request_headers,"connection","keep-alive"); 822 } else { /* default to persistant for HTTP/1.1 and above */ 823 pwsc->close=ws_testarg(&pwsc->request_headers,"connection","close"); 824 } 825 826 DPRINTF(E_DBG,L_WS,"Thread %d: Connection type %s: Connection: %s\n", 827 pwsc->threadno, last, pwsc->close ? "non-persist" : "persist"); 828 829 if(!pwsc->uri) { 830 pwsc->error=ENOMEM; 831 pwsc->close=1; /* force a full close */ 832 DPRINTF(E_LOG,L_WS,"Thread %d: Error allocation URI\n", 833 pwsc->threadno); 834 ws_returnerror(pwsc,500,"Internal server error"); 835 ws_close(pwsc); 836 return NULL; 837 } 838 839 /* trim the URI */ 840 first=pwsc->uri; 841 strsep(&first,"?"); 842 843 if(first) { /* got some GET args */ 844 DPRINTF(E_DBG,L_WS,"Thread %d: parsing GET args\n",pwsc->threadno); 845 ws_getgetvars(pwsc,first); 846 } 847 848 /* fix the URI by un urldecoding it */ 849 850 DPRINTF(E_DBG,L_WS,"Thread %d: Original URI: %s\n", 851 pwsc->threadno,pwsc->uri); 852 853 first=ws_urldecode(pwsc->uri,ws_encoding_hack(pwsc)); 854 free(pwsc->uri); 855 pwsc->uri=first; 856 857 /* Strip out the proxy stuff - iTunes 4.5 */ 858 first=strstr(pwsc->uri,"://"); 859 if(first) { 860 first += 3; 861 first=strchr(first,'/'); 862 if(first) { 863 first=strdup(first); 864 free(pwsc->uri); 865 pwsc->uri=first; 866 } 867 } 868 869 870 DPRINTF(E_DBG,L_WS,"Thread %d: Translated URI: %s\n",pwsc->threadno, 871 pwsc->uri); 872 873 /* now, parse POST args */ 874 if((pwsc->request_type == RT_POST) && (ws_getpostvars(pwsc) == -1)) { 875 DPRINTF(E_LOG,L_WS,"Couldn't process post vars. Aborting connection\n"); 876 pwsc->error=0; 877 pwsc->close=1; /* force a full close */ 878 ws_returnerror(pwsc,500,"Internal server error"); 879 ws_close(pwsc); 880 return NULL; 881 } 882 883 hdrs=1; 884 885 handler=ws_findhandler(pwsp,pwsc,&req_handler,&auth_handler,&hdrs); 886 887 time(&now); 888 DPRINTF(E_DBG,L_WS,"Thread %d: Time is %d seconds after epoch\n", 889 pwsc->threadno,now); 890 gmtime_r(&now,&now_tm); 891 DPRINTF(E_DBG,L_WS,"Thread %d: Setting time header\n",pwsc->threadno); 892 ws_addarg(&pwsc->response_headers,"Date", 893 "%s, %d %s %d %02d:%02d:%02d GMT", 894 ws_dow[now_tm.tm_wday],now_tm.tm_mday, 895 ws_moy[now_tm.tm_mon],now_tm.tm_year + 1900, 896 now_tm.tm_hour,now_tm.tm_min,now_tm.tm_sec); 897 898 if(hdrs) { 899 ws_addarg(&pwsc->response_headers,"Connection", 900 pwsc->close ? "close" : "keep-alive"); 901 902 ws_addarg(&pwsc->response_headers,"Server", 903 "mt-daapd/" VERSION); 904 905 ws_addarg(&pwsc->response_headers,"Content-Type","text/html"); 906 ws_addarg(&pwsc->response_headers,"Content-Language","en_us"); 907 } 908 909 /* Find the appropriate handler and dispatch it */ 910 if(handler == -1) { 911 DPRINTF(E_DBG,L_WS,"Thread %d: Using default handler.\n", 912 pwsc->threadno); 913 ws_defaulthandler(pwsp,pwsc); 914 } else { 915 DPRINTF(E_DBG,L_WS,"Thread %d: Using non-default handler\n", 916 pwsc->threadno); 917 918 can_dispatch=0; 919 /* If an auth handler is registered, but it accepts a 920 * username and password of NULL, then don't bother 921 * authing. 922 */ 923 if((auth_handler) && (auth_handler(NULL,NULL)==0)) { 924 /* do the auth thing */ 925 auth=ws_getarg(&pwsc->request_headers,"Authorization"); 926 if((auth) && (ws_decodepassword(auth,&username,&password) != -1)) { 927 if(auth_handler(username,password)) 928 can_dispatch=1; 929 ws_addarg(&pwsc->request_vars,"HTTP_USER","%s",username); 930 ws_addarg(&pwsc->request_vars,"HTTP_PASSWD","%s",password); 931 free(username); /* this frees password too */ 932 } 933 934 if(!can_dispatch) { /* auth failed, or need auth */ 935 //ws_addarg(&pwsc->response_headers,"Connection","close"); 936 ws_addarg(&pwsc->response_headers,"WWW-Authenticate", 937 "Basic realm=\"webserver\""); 938 ws_returnerror(pwsc,401,"Unauthorized"); 939 pwsc->error=0; 940 } 941 } else { 942 can_dispatch=1; 943 } 944 945 if(can_dispatch) { 946 if(req_handler) 947 req_handler(pwsc); 948 else 949 ws_defaulthandler(pwsp,pwsc); 950 } 951 } 952 953 if((pwsc->close) || (pwsc->error) || (pwsp->stop)) { 954 pwsc->close=1; 955 connection_done=1; 956 } 957 ws_close(pwsc); 958 } 959 DPRINTF(E_SPAM,L_WS,"Exiting ws_dispatcher\n"); 960 return NULL; 961} 962 963 964/* 965 * ws_writefd 966 * 967 * Write a printf-style output to a connfd 968 */ 969int ws_writefd(WS_CONNINFO *pwsc, char *fmt, ...) { 970 char buffer[1024]; 971 va_list ap; 972 973 DPRINTF(E_SPAM,L_WS,"Entering ws_writefd\n"); 974 975 va_start(ap, fmt); 976 vsnprintf(buffer, 1024, fmt, ap); 977 va_end(ap); 978 979 DPRINTF(E_SPAM,L_WS,"Exiting ws_writefd\n"); 980 return r_write(pwsc->fd,buffer,strlen(buffer)); 981} 982 983 984/* 985 * ws_returnerror 986 * 987 * return a particular error code to the requesting 988 * agent 989 * 990 * This will always succeed. If it cannot, it will 991 * just close the connection with prejudice. 992 */ 993int ws_returnerror(WS_CONNINFO *pwsc,int error, char *description) { 994 char *useragent; 995 996 DPRINTF(E_WARN,L_WS,"Thread %d: Entering ws_returnerror (%d: %s)\n", 997 pwsc->threadno,error,description); 998 ws_writefd(pwsc,"HTTP/1.1 %d %s\r\n",error,description); 999 1000 /* we'll force a close here unless the user agent is 1001 iTunes, which seems to get pissy about it */ 1002 useragent = ws_getarg(&pwsc->request_headers,"User-Agent"); 1003 if(useragent && (strncmp(useragent,"iTunes",6))) { 1004 pwsc->close=1; 1005 ws_addarg(&pwsc->response_headers,"Connection","close"); 1006 } 1007 1008 ws_emitheaders(pwsc); 1009 1010 ws_writefd(pwsc,"<HTML>\r\n<TITLE>"); 1011 ws_writefd(pwsc,"%d %s</TITLE>\r\n<BODY>",error,description); 1012 ws_writefd(pwsc,"\r\n<H1>%s</H1>\r\n",description); 1013 ws_writefd(pwsc,"Error %d\r\n<hr>\r\n",error); 1014 ws_writefd(pwsc,"<i>mt-daapd: %s\r\n<br>",VERSION); 1015 if(errno) 1016 ws_writefd(pwsc,"Error: %s\r\n",strerror(errno)); 1017 1018 ws_writefd(pwsc,"</i></BODY>\r\n</HTML>\r\n"); 1019 1020 DPRINTF(E_SPAM,L_WS,"Exiting ws_returnerror\n"); 1021 return 0; 1022} 1023 1024/* 1025 * ws_defaulthandler 1026 * 1027 * default URI handler. This simply finds the file 1028 * and serves it up 1029 */ 1030void ws_defaulthandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc) { 1031 char path[MAXPATHLEN]; 1032 char resolved_path[MAXPATHLEN]; 1033 int file_fd; 1034 off_t len; 1035 1036 DPRINTF(E_SPAM,L_WS,"Entering ws_defaulthandler\n"); 1037 1038 snprintf(path,MAXPATHLEN,"%s/%s",pwsp->wsconfig.web_root,pwsc->uri); 1039 if(!realpath(path,resolved_path)) { 1040 pwsc->error=errno; 1041 DPRINTF(E_WARN,L_WS,"Exiting ws_defaulthandler: Cannot resolve %s\n",path); 1042 ws_returnerror(pwsc,404,"Not found"); 1043 ws_close(pwsc); 1044 return; 1045 } 1046 1047 DPRINTF(E_DBG,L_WS,"Thread %d: Preparing to serve %s\n", 1048 pwsc->threadno, resolved_path); 1049 1050 if(strncmp(resolved_path,pwsp->wsconfig.web_root, 1051 strlen(pwsp->wsconfig.web_root))) { 1052 pwsc->error=EINVAL; 1053 DPRINTF(E_WARN,L_WS,"Exiting ws_defaulthandler: Thread %d: " 1054 "Requested file %s out of root\n", 1055 pwsc->threadno,resolved_path); 1056 ws_returnerror(pwsc,403,"Forbidden"); 1057 ws_close(pwsc); 1058 return; 1059 } 1060 1061 file_fd=open(resolved_path,O_RDONLY); 1062 if(file_fd == -1) { 1063 pwsc->error=errno; 1064 DPRINTF(E_WARN,L_WS,"Exiting ws_defaulthandler: Thread %d: " 1065 "Error opening %s: %s\n", 1066 pwsc->threadno,resolved_path,strerror(errno)); 1067 ws_returnerror(pwsc,404,"Not found"); 1068 ws_close(pwsc); 1069 return; 1070 } 1071 1072 /* set the Content-Length response header */ 1073 len=lseek(file_fd,0,SEEK_END); 1074 1075 /* FIXME: assumes off_t == long */ 1076 if(len != -1) { 1077 /* we have a real length */ 1078 DPRINTF(E_DBG,L_WS,"Length of file is %ld\n",(long)len); 1079 ws_addarg(&pwsc->response_headers,"Content-Length","%ld",(long)len); 1080 lseek(file_fd,0,SEEK_SET); 1081 } 1082 1083 1084 ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); 1085 ws_emitheaders(pwsc); 1086 1087 /* now throw out the file */ 1088 copyfile(file_fd,pwsc->fd); 1089 1090 r_close(file_fd); 1091 DPRINTF(E_DBG,L_WS,"Exiting ws_defaulthandler: " 1092 "Thread %d: Served successfully\n", 1093 pwsc->threadno); 1094 return; 1095} 1096 1097 1098/* 1099 * ws_testrequestheader 1100 * 1101 * Check to see if a request header is a particular value 1102 * 1103 * Example: 1104 * 1105 */ 1106int ws_testrequestheader(WS_CONNINFO *pwsc, char *header, char *value) { 1107 return ws_testarg(&pwsc->request_headers,header,value); 1108} 1109 1110/* 1111 * ws_testarg 1112 * 1113 * Check an arg for a particular value 1114 * 1115 * Example: 1116 * 1117 * pwsc->close=ws_queryarg(&pwsc->request_headers,"Connection","close"); 1118 * 1119 */ 1120int ws_testarg(ARGLIST *root, char *key, char *value) { 1121 char *retval; 1122 1123 DPRINTF(E_DBG,L_WS,"Checking to see if %s matches %s\n",key,value); 1124 1125 retval=ws_getarg(root,key); 1126 if(!retval) 1127 return 0; 1128 1129 return !strcasecmp(value,retval); 1130} 1131 1132/* 1133 * ws_getarg 1134 * 1135 * Find an argument in an argument list 1136 * 1137 * returns a pointer to the value if successful, 1138 * NULL otherwise. 1139 * 1140 * This should be passed the pointer to the 1141 * stub in the pwsc. 1142 * 1143 * Ex: ws_getarg(pwsc->request_headers,"Connection"); 1144 */ 1145char *ws_getarg(ARGLIST *root, char *key) { 1146 ARGLIST *pcurrent=root->next; 1147 1148 while((pcurrent)&&(strcasecmp(pcurrent->key,key))) 1149 pcurrent=pcurrent->next; 1150 1151 if(pcurrent) 1152 return pcurrent->value; 1153 1154 return NULL; 1155} 1156 1157 1158/* 1159 * ws_addarg 1160 * 1161 * Add an argument to an arg list 1162 * 1163 * This will strdup the passed key and value. 1164 * The arglist will then have to be freed. This should 1165 * be done in ws_close, and handler functions will 1166 * have to remember to ws_close them. 1167 * 1168 * RETURNS 1169 * -1 on failure, with errno set (ENOMEM) 1170 * 0 on success 1171 */ 1172int ws_addarg(ARGLIST *root, char *key, char *fmt, ...) { 1173 char *newkey; 1174 char *newvalue; 1175 ARGLIST *pnew; 1176 ARGLIST *current; 1177 va_list ap; 1178 char value[MAX_LINEBUFFER]; 1179 1180 va_start(ap,fmt); 1181 vsnprintf(value,sizeof(value),fmt,ap); 1182 va_end(ap); 1183 1184 newkey=strdup(key); 1185 newvalue=strdup(value); 1186 pnew=(ARGLIST*)malloc(sizeof(ARGLIST)); 1187 1188 if((!pnew)||(!newkey)||(!newvalue)) 1189 return -1; 1190 1191 pnew->key=newkey; 1192 pnew->value=newvalue; 1193 1194 /* first, see if the key exists... if it does, simply 1195 * replace it rather than adding a duplicate key 1196 */ 1197 1198 current=root->next; 1199 while(current) { 1200 if(!strcmp(current->key,key)) { 1201 /* got a match! */ 1202 DPRINTF(E_DBG,L_WS,"Updating %s from %s to %s\n", 1203 key,current->value,value); 1204 free(current->value); 1205 current->value = newvalue; 1206 free(newkey); 1207 free(pnew); 1208 return 0; 1209 } 1210 current=current->next; 1211 } 1212 1213 1214 pnew->next=root->next; 1215 DPRINTF(E_DBG,L_WS,"Added *%s=%s*\n",newkey,newvalue); 1216 root->next=pnew; 1217 1218 return 0; 1219} 1220 1221/* 1222 * ws_urldecode 1223 * 1224 * decode a urlencoded string 1225 * 1226 * the returned char will be malloced -- it must be 1227 * freed by the caller 1228 * 1229 * returns NULL on error (ENOMEM) 1230 */ 1231char *ws_urldecode(char *string, int space_as_plus) { 1232 char *pnew; 1233 char *src,*dst; 1234 int val=0; 1235 1236 pnew=(char*)malloc(strlen(string)+1); 1237 if(!pnew) 1238 return NULL; 1239 1240 src=string; 1241 dst=pnew; 1242 1243 while(*src) { 1244 switch(*src) { 1245 /* DWB - space gets converted to %20, not +, this definitely breaks compatibility with iTunes */ 1246 /* But the browsers encode space as plus, so when using the web interface, 1247 * anything with a plus is broken. This will end up having to be sniffed 1248 * by remote agent */ 1249 case '+': 1250 if(space_as_plus) { 1251 *dst++=' '; 1252 } else { 1253 *dst++=*src; 1254 } 1255 src++; 1256 break; 1257 case '%': 1258 /* this is hideous */ 1259 src++; 1260 if(*src) { 1261 if((*src <= '9') && (*src >='0')) 1262 val=(*src - '0'); 1263 else if((tolower(*src) <= 'f')&&(tolower(*src) >= 'a')) 1264 val=10+(tolower(*src) - 'a'); 1265 src++; 1266 } 1267 if(*src) { 1268 val *= 16; 1269 if((*src <= '9') && (*src >='0')) 1270 val+=(*src - '0'); 1271 else if((tolower(*src) <= 'f')&&(tolower(*src) >= 'a')) 1272 val+=(10+(tolower(*src) - 'a')); 1273 src++; 1274 } 1275 *dst++=val; 1276 break; 1277 default: 1278 *dst++=*src++; 1279 break; 1280 } 1281 } 1282 1283 *dst='\0'; 1284 return pnew; 1285} 1286 1287 1288/* 1289 * ws_registerhandler 1290 * 1291 * Register a page and auth handler. Returns 0 on success, 1292 * returns -1 on failure. 1293 * 1294 * If the regex is not well-formed, it returns -1 iwth 1295 * errno set to EINVAL. It is up to the caller to use 1296 * regerror to display a more interesting error message, 1297 * if appropriate. 1298 */ 1299int ws_registerhandler(WSHANDLE ws, char *regex, 1300 void(*handler)(WS_CONNINFO*), 1301 int(*auth)(char *, char *), 1302 int addheaders) { 1303 WS_HANDLER *phandler; 1304 WS_PRIVATE *pwsp = (WS_PRIVATE *)ws; 1305 1306 phandler=(WS_HANDLER *)malloc(sizeof(WS_HANDLER)); 1307 if(!phandler) 1308 return -1; 1309 1310 if(regcomp(&phandler->regex,regex,REG_EXTENDED | REG_NOSUB)) { 1311 free(phandler); 1312 errno=EINVAL; 1313 return -1; 1314 } 1315 1316 phandler->req_handler=handler; 1317 phandler->auth_handler=auth; 1318 phandler->addheaders=addheaders; 1319 1320 ws_lock_unsafe(); 1321 phandler->next=pwsp->handlers.next; 1322 pwsp->handlers.next=phandler; 1323 ws_unlock_unsafe(); 1324 1325 return 0; 1326} 1327 1328/* 1329 * ws_findhandler 1330 * 1331 * Given a URI, determine the appropriate handler. 1332 * 1333 * If a handler is found, it returns 0, otherwise, returns 1334 * -1 1335 */ 1336int ws_findhandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc, 1337 void(**preq)(WS_CONNINFO*), 1338 int(**pauth)(char *, char *), 1339 int *addheaders) { 1340 WS_HANDLER *phandler=pwsp->handlers.next; 1341 1342 ws_lock_unsafe(); 1343 1344 *preq=NULL; 1345 1346 DPRINTF(E_DBG,L_WS,"Thread %d: Preparing to find handler\n", 1347 pwsc->threadno); 1348 1349 while(phandler) { 1350 if(!regexec(&phandler->regex,pwsc->uri,0,NULL,0)) { 1351 /* that's a match */ 1352 DPRINTF(E_DBG,L_WS,"Thread %d: URI Match!\n",pwsc->threadno); 1353 *preq=phandler->req_handler; 1354 *pauth=phandler->auth_handler; 1355 *addheaders=phandler->addheaders; 1356 ws_unlock_unsafe(); 1357 return 0; 1358 } 1359 phandler=phandler->next; 1360 } 1361 1362 ws_unlock_unsafe(); 1363 return -1; 1364} 1365 1366/* 1367 * ws_decodepassword 1368 * 1369 * Given a base64 encoded Authentication request header, 1370 * decode it into the username and password 1371 */ 1372int ws_decodepassword(char *header, char **username, char **password) { 1373 static char ws_xlat[256]; 1374 static int ws_xlat_init=0; 1375 int index; 1376 int len; 1377 int rack=0; 1378 int pads=0; 1379 unsigned char *decodebuffer; 1380 unsigned char *pin, *pout; 1381 char *type,*base64; 1382 int lookup; 1383 1384 *username=NULL; 1385 *password=NULL; 1386 1387 if(ws_lock_unsafe() == -1) 1388 return -1; 1389 1390 if(!ws_xlat_init) { 1391 ws_xlat_init=1; 1392 1393 memset((char*)&ws_xlat,0xFF,sizeof(ws_xlat)); 1394 for(index=0; index < 26; index++) { 1395 ws_xlat['A' + index] = index; 1396 ws_xlat['a' + index] = index + 26; 1397 } 1398 1399 for(index=0; index < 10; index++) { 1400 ws_xlat['0' + index] = index + 52; 1401 } 1402 1403 ws_xlat['+'] = 62; 1404 ws_xlat['/'] = 63; 1405 } 1406 if(ws_unlock_unsafe() == -1) 1407 return -1; 1408 1409 /* xlat table is initialized */ 1410 1411 // Trim leading spaces 1412 while((*header) && (*header != ' ')) 1413 header++; 1414 1415 // Should be in the form "Basic <base-64 enc username/pw>" 1416 type=header; 1417 base64 = strchr(header,' '); 1418 if(!base64) { 1419 // invalid auth header 1420 DPRINTF(E_DBG,L_WS,"Bad authentication header: %s\n",header); 1421 return -1; 1422 } 1423 1424 *base64 = '\0'; 1425 base64++; 1426 1427 decodebuffer=(unsigned char *)malloc(strlen(base64)); 1428 if(!decodebuffer) 1429 return -1; 1430 1431 DPRINTF(E_DBG,L_WS,"Preparing to decode %s\n",base64); 1432 1433 memset(decodebuffer,0,strlen(base64)); 1434 len=0; 1435 pout=decodebuffer; 1436 pin=base64; 1437 1438 /* this is more than a little sloppy */ 1439 while(pin[rack]) { 1440 if(pin[rack] != '=') { 1441 lookup=ws_xlat[pin[rack]]; 1442 if(lookup == 0xFF) { 1443 DPRINTF(E_WARN,L_WS,"Got garbage Authenticate header\n"); 1444 return -1; 1445 } 1446 1447 /* valid character */ 1448 switch(rack) { 1449 case 0: 1450 pout[0]=(lookup << 2); 1451 break; 1452 case 1: 1453 pout[0] |= (lookup >> 4); 1454 pout[1] = (lookup << 4); 1455 break; 1456 case 2: 1457 pout[1] |= (lookup >> 2); 1458 pout[2] = (lookup << 6); 1459 break; 1460 case 3: 1461 pout[2] |= lookup; 1462 break; 1463 } 1464 rack++; 1465 } else { 1466 /* padding char */ 1467 pads++; 1468 rack++; 1469 } 1470 1471 if(rack == 4) { 1472 pin += 4; 1473 pout += 3; 1474 1475 len += (3-pads); 1476 rack=0; 1477 } 1478 } 1479 1480 /* we now have the decoded string */ 1481 DPRINTF(E_DBG,L_WS,"Decoded %s\n",decodebuffer); 1482 1483 *username = decodebuffer; 1484 *password = *username; 1485 1486 strsep(password,":"); 1487 1488 DPRINTF(E_DBG,L_WS,"Decoded user=%s, pw=%s\n",*username,*password); 1489 return 0; 1490} 1491 1492/* 1493 * ws_addreponseheader 1494 * 1495 * Simple wrapper around the CONNINFO response headers 1496 */ 1497int ws_addresponseheader(WS_CONNINFO *pwsc, char *header, char *fmt, ...) { 1498 va_list ap; 1499 char value[MAX_LINEBUFFER]; 1500 1501 va_start(ap,fmt); 1502 vsnprintf(value,sizeof(value),fmt,ap); 1503 va_end(ap); 1504 1505 return ws_addarg(&pwsc->response_headers,header,value); 1506} 1507 1508/* 1509 * ws_getvar 1510 * 1511 * Simple wrapper around the CONNINFO request vars 1512 */ 1513char *ws_getvar(WS_CONNINFO *pwsc, char *var) { 1514 return ws_getarg(&(pwsc->request_vars),var); 1515} 1516 1517char *ws_getrequestheader(WS_CONNINFO *pwsc, char *header) { 1518 return ws_getarg(&pwsc->request_headers,header); 1519} 1520