Documentation Source Text

Check-in [f23fa402b1]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Enhance althttpd with new options --nojail, --port N, and --debug. If the --port N flag is used, then run as a server. --nojail avoids setting the chroot jail. --debug disables the timeout alarms.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: f23fa402b1a389f6fd4db346d25b63896623038b
User & Date: drh 2012-06-09 01:22:23
Context
2012-06-09
02:24
Fixes to the --port option of althttpd. check-in: 7b4eed2b49 user: drh tags: trunk
01:22
Enhance althttpd with new options --nojail, --port N, and --debug. If the --port N flag is used, then run as a server. --nojail avoids setting the chroot jail. --debug disables the timeout alarms. check-in: f23fa402b1 user: drh tags: trunk
2012-06-07
16:30
Updates in preparation for 3.7.13. check-in: 45f3811cfd user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to misc/althttpd.c.

78
79
80
81
82
83
84

85
86
87
88
89
90
91
...
141
142
143
144
145
146
147

148
149
150
151
152
153
154
...
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
...
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
....
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
....
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
....
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
....
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
....
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429

1430




























































































1431
1432
1433


1434
1435
1436
1437
1438
1439
1440
1441


1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458












1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479

1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490



1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
#include <arpa/inet.h>
#include <stdarg.h>
#include <time.h>
#include <sys/times.h>
#ifdef linux
#include <sys/sendfile.h>
#endif


/*
** Configure the server by setting the following macros and recompiling.
*/
#ifndef DEFAULT_PORT
#define DEFAULT_PORT "80"
#endif
................................................................................
static int debugFlag = 0;        /* True if being debugged */
static time_t beginTime;         /* Time when this process starts */
static int closeConnection = 0;  /* True to send Connection: close in reply */
static int nRequest = 0;         /* Number of requests processed */
static int omitLog = 0;          /* Do not make logfile entries if true */
static int useHttps = 0;         /* True to use HTTPS: instead of HTTP: */
static char *zHttp = "http";     /* http or https */


/*
** Change every space or unprintable character in the zAgent[] string
** into an _.
**
** If the user agent string contains certain prohibited string, then
** exit immediately.
................................................................................
    }
    if( zAgent==0 || zAgent[0]==0 ) zAgent = "*";
    time(&now);
    pTm = localtime(&now);
    strftime(zDate, sizeof(zDate), "%Y-%m-%d %H:%M:%S", pTm);
    times(&sTms);
    rScale = 1.0/(double)sysconf(_SC_CLK_TCK);
    chdir((zRoot && zRoot[0]) ? zRoot : "/");
    if( (log = fopen(zLogFile,"a"))!=0 ){
#ifdef COMBINED_LOG_FORMAT
      strftime(zDate, sizeof(zDate), "%d/%b/%Y:%H:%M:%S %z", pTm);
      fprintf(log, "%s - - [%s] \"%s %s %s\" %s %d \"%s\" \"%s\"\n",
              zRemoteAddr, zDate, zMethod, zScript, zProtocol,
              zReplyStatus, nOut, zReferer, zAgent);
#else
................................................................................
  }
  nRequest++;

  /*
  ** We must receive a complete header within 15 seconds
  */
  signal(SIGALRM, Timeout);
  alarm(15);

  /* Get the first line of the request and parse out the
  ** method, the script and the protocol.
  */
  if( fgets(zLine,sizeof(zLine),stdin)==0 ){
    exit(0);
  }
................................................................................
        "Could not open \"%s\" for writing\n"
        "</body>\n", zTmpNam
      );
      MakeLogEntry(0);
      exit(0);
    }
    zBuf = SafeMalloc( len );
    alarm(15 + len/2000);
    n = fread(zBuf,1,len,stdin);
    nIn += n;
    fwrite(zBuf,1,n,out);
    free(zBuf);
    fclose(out);
  }

  /* Make sure the running time is not too great */
  alarm(10);

  /* Convert all unusual characters in the script name into "_".
  **
  ** This is a defense against various attacks, XSS attacks in particular.
  */
  for(z=zScript; *z; z++){
    unsigned char c = *(unsigned char*)z;
................................................................................
    if( in==0 ){
      CgiError();
    }

    /* Read and process the first line of the header returned by the
    ** CGI script.
    */
    alarm(15);
    while( fgets(zLine,sizeof(zLine),in) ){
      if( strncmp(zLine,"Location:",9)==0 ){
        int i;
        RemoveNewline(zLine);
        z = &zLine[10];
        while( isspace(*z) ){ z++; }
        for(i=0; z[i]; i++){
................................................................................
        for(i=0; zLine[i] && !isspace(zLine[i]) && zLine[i]!=':'; i++){}
        if( i<2 || zLine[i]!=':' ) break;
      }
    }

    /* Copy everything else thru without change or analysis.
    */
    alarm(60*5);
    while( (c = getc(in))!=EOF ){
      putc(c,stdout);
      nOut++;
    }
    fclose(in);
  }else{
    /* If it isn't executable then it
................................................................................
    nOut += printf("Content-length: %d\r\n\r\n",(int)statbuf.st_size);
    fflush(stdout);
    if( strcmp(zMethod,"HEAD")==0 ){
      MakeLogEntry(0);
      fclose(in);
      return;
    }
    alarm(30 + statbuf.st_size/1000);
#ifdef linux
    {
      off_t offset = 0;
      nOut += sendfile(fileno(stdout), fileno(in), &offset, statbuf.st_size);
    }
#else
    while( (c = getc(in))!=EOF ){
................................................................................
  }
  fflush(stdout);
  MakeLogEntry(0);

  /* The next request must arrive within 30 seconds or we close the connection
  */
  omitLog = 1;
  alarm(30);
}































































































int main(int argc, char **argv){
  int i;                    /* Loop counter */
  char *zPermUser = 0;      /* Run daemon with this user's permissions */



  /* Record the time when processing begins.
  */
  time(&beginTime);

  /* Parse command-line arguments
  */
  while( argc>2 && argv[1][0]=='-' ){


    if( strcmp(argv[1],"-user")==0 ){
      zPermUser = argv[2];
      argv += 2;
      argc -= 2;
    }else if( strcmp(argv[1],"-root")==0 ){
      zRoot = argv[2];
      argv += 2;
      argc -= 2;
    }else if( strcmp(argv[1],"-logfile")==0 ){
      zLogFile = argv[2];
      argv += 2;
      argc -= 2;
    }else if( strcmp(argv[1],"-https")==0 ){
      useHttps = atoi(argv[2]);
      zHttp = useHttps ? "http" : "https";
      argv += 2;
      argc -= 2;












    }else{
      Malfunction(__LINE__, "unknown argument: [%s]", argv[1]);
    }
  }
  if( zRoot==0 ){
    Malfunction(__LINE__, "no root directory specified");
  }
  
  /* Change directories to the root of the HTTP filesystem
  */
  if( chdir(zRoot)!=0 ){
    Malfunction(__LINE__, "cannot change to directory [%s]", zRoot);
  }

  /* Attempt to go into a chroot jail as user zPermUser
  */
  if( zPermUser ){
    struct passwd *pwd = getpwnam(zPermUser);
    if( pwd ){
      if( chroot(".")<0 ){
        Malfunction(__LINE__, "unable to create chroot jail");

      }
      setgid(pwd->pw_gid);
      setuid(pwd->pw_uid);
      zRoot = "";
    }else{
      Malfunction(__LINE__, "no home directory for user [%s]", zPermUser);
    }
  }
  if( getuid()==0 ){
    Malfunction(__LINE__, "cannot run as root");
  }




  /* Get the IP address from when the request originates
  */
  {
    struct sockaddr_in remoteName;
    int size = sizeof(struct sockaddr_in);
    if( getpeername(fileno(stdin), (struct sockaddr*)&remoteName, &size)>=0 ){
      zRemoteAddr = StrDup(inet_ntoa(remoteName.sin_addr));
    }
  }

  /* Process the input stream */
  for(i=0; i<100; i++){
    ProcessOneRequest(0);
  }
  ProcessOneRequest(1);
  exit(0);
}







>







 







>







 







<







 







|







 







|








|







 







|







 







|







 







|







 







|


>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



>
>








>
>
|



|



|



|




>
>
>
>
>
>
>
>
>
>
>
>





|













|

>



<







>
>
>





|
|











78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
...
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
...
201
202
203
204
205
206
207

208
209
210
211
212
213
214
...
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
....
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
....
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
....
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
....
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
....
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593

1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
#include <arpa/inet.h>
#include <stdarg.h>
#include <time.h>
#include <sys/times.h>
#ifdef linux
#include <sys/sendfile.h>
#endif
#include <assert.h>

/*
** Configure the server by setting the following macros and recompiling.
*/
#ifndef DEFAULT_PORT
#define DEFAULT_PORT "80"
#endif
................................................................................
static int debugFlag = 0;        /* True if being debugged */
static time_t beginTime;         /* Time when this process starts */
static int closeConnection = 0;  /* True to send Connection: close in reply */
static int nRequest = 0;         /* Number of requests processed */
static int omitLog = 0;          /* Do not make logfile entries if true */
static int useHttps = 0;         /* True to use HTTPS: instead of HTTP: */
static char *zHttp = "http";     /* http or https */
static int useTimeout = 1;       /* True to use times */

/*
** Change every space or unprintable character in the zAgent[] string
** into an _.
**
** If the user agent string contains certain prohibited string, then
** exit immediately.
................................................................................
    }
    if( zAgent==0 || zAgent[0]==0 ) zAgent = "*";
    time(&now);
    pTm = localtime(&now);
    strftime(zDate, sizeof(zDate), "%Y-%m-%d %H:%M:%S", pTm);
    times(&sTms);
    rScale = 1.0/(double)sysconf(_SC_CLK_TCK);

    if( (log = fopen(zLogFile,"a"))!=0 ){
#ifdef COMBINED_LOG_FORMAT
      strftime(zDate, sizeof(zDate), "%d/%b/%Y:%H:%M:%S %z", pTm);
      fprintf(log, "%s - - [%s] \"%s %s %s\" %s %d \"%s\" \"%s\"\n",
              zRemoteAddr, zDate, zMethod, zScript, zProtocol,
              zReplyStatus, nOut, zReferer, zAgent);
#else
................................................................................
  }
  nRequest++;

  /*
  ** We must receive a complete header within 15 seconds
  */
  signal(SIGALRM, Timeout);
  if( useTimeout ) alarm(15);

  /* Get the first line of the request and parse out the
  ** method, the script and the protocol.
  */
  if( fgets(zLine,sizeof(zLine),stdin)==0 ){
    exit(0);
  }
................................................................................
        "Could not open \"%s\" for writing\n"
        "</body>\n", zTmpNam
      );
      MakeLogEntry(0);
      exit(0);
    }
    zBuf = SafeMalloc( len );
    if( useTimeout ) alarm(15 + len/2000);
    n = fread(zBuf,1,len,stdin);
    nIn += n;
    fwrite(zBuf,1,n,out);
    free(zBuf);
    fclose(out);
  }

  /* Make sure the running time is not too great */
  if( useTimeout ) alarm(10);

  /* Convert all unusual characters in the script name into "_".
  **
  ** This is a defense against various attacks, XSS attacks in particular.
  */
  for(z=zScript; *z; z++){
    unsigned char c = *(unsigned char*)z;
................................................................................
    if( in==0 ){
      CgiError();
    }

    /* Read and process the first line of the header returned by the
    ** CGI script.
    */
    if( useTimeout ) alarm(15);
    while( fgets(zLine,sizeof(zLine),in) ){
      if( strncmp(zLine,"Location:",9)==0 ){
        int i;
        RemoveNewline(zLine);
        z = &zLine[10];
        while( isspace(*z) ){ z++; }
        for(i=0; z[i]; i++){
................................................................................
        for(i=0; zLine[i] && !isspace(zLine[i]) && zLine[i]!=':'; i++){}
        if( i<2 || zLine[i]!=':' ) break;
      }
    }

    /* Copy everything else thru without change or analysis.
    */
    if( useTimeout ) alarm(60*5);
    while( (c = getc(in))!=EOF ){
      putc(c,stdout);
      nOut++;
    }
    fclose(in);
  }else{
    /* If it isn't executable then it
................................................................................
    nOut += printf("Content-length: %d\r\n\r\n",(int)statbuf.st_size);
    fflush(stdout);
    if( strcmp(zMethod,"HEAD")==0 ){
      MakeLogEntry(0);
      fclose(in);
      return;
    }
    if( useTimeout ) alarm(30 + statbuf.st_size/1000);
#ifdef linux
    {
      off_t offset = 0;
      nOut += sendfile(fileno(stdout), fileno(in), &offset, statbuf.st_size);
    }
#else
    while( (c = getc(in))!=EOF ){
................................................................................
  }
  fflush(stdout);
  MakeLogEntry(0);

  /* The next request must arrive within 30 seconds or we close the connection
  */
  omitLog = 1;
  if( useTimeout ) alarm(30);
}

#define MAX_PARALLEL 50  /* Number of simultaneous children */

/*
** Implement an HTTP server daemon listening on port iPort.
**
** As new connections arrive, fork a child and let child return
** out of this procedure call.  The child will handle the request.
** The parent never returns from this procedure.
**
** Return 0 to each child as it runs.  If unable to establish a
** listening socket, return non-zero.
*/
int http_server(int iPort, int localOnly){
  int listener = -1;           /* The server socket */
  int connection;              /* A socket for each individual connection */
  fd_set readfds;              /* Set of file descriptors for select() */
  socklen_t lenaddr;           /* Length of the inaddr structure */
  int child;                   /* PID of the child process */
  int nchildren = 0;           /* Number of child processes */
  struct timeval delay;        /* How long to wait inside select() */
  struct sockaddr_in inaddr;   /* The socket address */
  int opt = 1;                 /* setsockopt flag */

  memset(&inaddr, 0, sizeof(inaddr));
  inaddr.sin_family = AF_INET;
  if( localOnly ){
    inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  }else{
    inaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  }
  inaddr.sin_port = htons(iPort);
  listener = socket(AF_INET, SOCK_STREAM, 0);
  if( listener<0 ){
    iPort++;
    fprintf(stderr, "cannot open listening socket on port %d\n", iPort);
    return 1;
  }

  /* if we can't terminate nicely, at least allow the socket to be reused */
  setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

  if( bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr))<0 ){
    close(listener);
    fprintf(stderr, "bind failed\n");
    return 1;
  }
  listen(listener,10);
  printf("Listening for HTTP requests on TCP port %d\n", iPort);
  fflush(stdout);
  while( 1 ){
    if( nchildren>MAX_PARALLEL ){
      /* Slow down if connections are arriving too fast */
      sleep( nchildren-MAX_PARALLEL );
    }
    delay.tv_sec = 60;
    delay.tv_usec = 0;
    FD_ZERO(&readfds);
    assert( listener>=0 );
    FD_SET( listener, &readfds);
    select( listener+1, &readfds, 0, 0, &delay);
    if( FD_ISSET(listener, &readfds) ){
      lenaddr = sizeof(inaddr);
      connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr);
      if( connection>=0 ){
        child = fork();
        if( child!=0 ){
          if( child>0 ) nchildren++;
          close(connection);
        }else{
          int nErr = 0, fd;
          close(0);
          fd = dup(connection);
          if( fd!=0 ) nErr++;
          close(1);
          fd = dup(connection);
          if( fd!=1 ) nErr++;
          close(2);
          fd = dup(connection);
          if( fd!=2 ) nErr++;
          close(connection);
          return nErr;
        }
      }
    }
    /* Bury dead children */
    while( waitpid(0, 0, WNOHANG)>0 ){
      nchildren--;
    }
  }
  /* NOT REACHED */  
  exit(1);
}


int main(int argc, char **argv){
  int i;                    /* Loop counter */
  char *zPermUser = 0;      /* Run daemon with this user's permissions */
  int iPort = 0;            /* Implement an HTTP server process */
  int useChrootJail = 1;    /* True to use a change-root jail */

  /* Record the time when processing begins.
  */
  time(&beginTime);

  /* Parse command-line arguments
  */
  while( argc>2 && argv[1][0]=='-' ){
    char *z = argv[1];
    if( z[0]=='-' && z[1]=='-' ) z++;
    if( strcmp(z,"-user")==0 ){
      zPermUser = argv[2];
      argv += 2;
      argc -= 2;
    }else if( strcmp(z,"-root")==0 ){
      zRoot = argv[2];
      argv += 2;
      argc -= 2;
    }else if( strcmp(z,"-logfile")==0 ){
      zLogFile = argv[2];
      argv += 2;
      argc -= 2;
    }else if( strcmp(z,"-https")==0 ){
      useHttps = atoi(argv[2]);
      zHttp = useHttps ? "http" : "https";
      argv += 2;
      argc -= 2;
    }else if( strcmp(z, "-port")==0 ){
      iPort = atoi(argv[2]);
      argv += 2;
      argc -= 2;
    }else if( strcmp(z, "-nojail")==0 ){
      useChrootJail = 0;
      argv++;
      argc--;
    }else if( strcmp(z, "-debug")==0 ){
      useTimeout = 0;
      argv++;
      argc--;
    }else{
      Malfunction(__LINE__, "unknown argument: [%s]", argv[1]);
    }
  }
  if( zRoot==0 ){
    Malfunction(__LINE__, "no --root specified");
  }
  
  /* Change directories to the root of the HTTP filesystem
  */
  if( chdir(zRoot)!=0 ){
    Malfunction(__LINE__, "cannot change to directory [%s]", zRoot);
  }

  /* Attempt to go into a chroot jail as user zPermUser
  */
  if( zPermUser ){
    struct passwd *pwd = getpwnam(zPermUser);
    if( pwd ){
      if( useChrootJail && chroot(".")<0 ){
        Malfunction(__LINE__, "unable to create chroot jail");
        zRoot = "";
      }
      setgid(pwd->pw_gid);
      setuid(pwd->pw_uid);

    }else{
      Malfunction(__LINE__, "no home directory for user [%s]", zPermUser);
    }
  }
  if( getuid()==0 ){
    Malfunction(__LINE__, "cannot run as root");
  }
  if( iPort && http_server(iPort, 0) ){
    Malfunction(__LINE__, "failed to start server");
  }

  /* Get the IP address from when the request originates
  */
  {
    struct sockaddr_in remoteName;
    unsigned int size = sizeof(struct sockaddr_in);
    if( getpeername(0, (struct sockaddr*)&remoteName, &size)>=0 ){
      zRemoteAddr = StrDup(inet_ntoa(remoteName.sin_addr));
    }
  }

  /* Process the input stream */
  for(i=0; i<100; i++){
    ProcessOneRequest(0);
  }
  ProcessOneRequest(1);
  exit(0);
}