/** sol12.5.c
** ------------------------------------------------------------
A version of webserv that puts some typical CGI
variables into the environment before calling
exec is
sol12.5.c.
Three kinds of variables are demonstrated in this solution:
variables about the server,
variables sent as part of the http header,
and variables about the client.
** ------------------------------------------------------------
**
**
* Version of webserv.c that includes some environment variables
* when running CGI programs.
*
* The variables included for cgi programs are:
* SERVER_SOFTWARE
* SERVER_NAME
* REMOTE_PORT
* REMOTE_ADDR
* REQUEST_URI
* REQUEST_METHOD
* HTTP_USER_AGENT
*
* This program uses the varlib.c system from the smsh.c
* program in the shell Chapter. That system does most
* of the filing and export work for us.
*
* usage: ws portnumber
* features: supports the GET command only
* runs in the current directory
* forks a new child to handle each request
* has MAJOR security holes, for demo purposes only
* has many other weaknesses, but is a good start
*
* build: cc sol12.5.c socklib.c varlib.c -o sol12.5
*
* By showing how easily we can use varlib.c to manage the
* variables for the web server, we see in concrete terms
* how much a web browser work a shell and OS.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <time.h>
#include <errno.h>
#include "varlib.h"
#include "socklib.h"
#define oops(s,x) { perror(s); exit(x); }
void setupvars();
void process_request(int , struct sockaddr_in *);
void process_header(FILE *);
void act_on_request(char *, char *, int );
void header( FILE *, char *);
void cannot_do(int );
void do_404(char *, int );
int isadir(char *);
int not_exist(char *);
void do_ls(char *, int );
char * file_type(char *);
int ends_in_cgi(char *);
void do_exec( char *, int );
void do_cat(char *, int );
int main(int ac, char *av[])
{
int sock, fd;
int len;
struct sockaddr_in clnt_addr;
if ( ac == 1 ){
fprintf(stderr,"usage: ws portnum\n");
exit(1);
}
sock = make_server_socket( atoi(av[1]) );
if ( sock == -1 )
oops("socket", 2);
setupvars();
/* main loop here */
while(1){
/*
* wait for a call
*/
len = sizeof(struct sockaddr);
fd = accept(sock, (struct sockaddr *)&clnt_addr, &len);
/*
* take a call and buffer it
*/
if ( fd >= 0 ){
process_request(fd, &clnt_addr);
close(fd);
}
else
perror("accept");
}
return 0;
}
/*
* load the environment table from environ and
* set some fixed values that describe this host and server
*/
void setupvars()
{
char hostname[512];
VLstore("SERVER_SOFTWARE", "Simple-WebServer 0.2");
VLexport("SERVER_SOFTWARE");
gethostname(hostname, 512);
VLstore("SERVER_NAME", hostname);
VLexport("SERVER_NAME");
/*
add here other fixed values that can be
computed at startup
*/
}
/*
* A more general process_request than the earlier version
* This one has to read the header and put some parts into
* the environment list.
*/
void process_request(int fd, struct sockaddr_in *caller)
{
FILE *fpin;
char request[BUFSIZ];
char portnumstr[10];
char cmd[BUFSIZ], arg[BUFSIZ];
/* process the request in a child process */
if ( fork() != 0 )
return;
/* jot down the caller's adress and port */
VLstore("REMOTE_ADDR", inet_ntoa(caller->sin_addr));
VLexport("REMOTE_ADDR");
sprintf(portnumstr, "%d", ntohs(caller->sin_port));
VLstore("REMOTE_PORT", portnumstr);
VLexport("REMOTE_PORT");
/* buffer the socket for input and read request */
fpin = fdopen(fd, "r" );
if ( fpin == NULL )
exit(2);
fgets(request,BUFSIZ,fpin);
printf("got a call: request = %s", request);
process_header(fpin);
/* then do the request */
strcpy(arg, "./");
if ( sscanf(request,"%s%s", cmd, arg+2) == 2 ){
VLstore("REQUEST_METHOD", cmd);
VLexport("REQUEST_METHOD");
VLstore("REQUEST_URI", arg+2);
VLexport("REQUEST_URI");
act_on_request(cmd, arg, fd);
}
}
/* ------------------------------------------------------ *
process_header(FILE *)
after the HTTP request may be several lines
of header information. These variables provide
define the parameters of the request.
The set of parameters is terminated by a blank line.
This function looks for the header line marked
User-Agent: name-of-browser
And puts the value for that tag in the environment
under HTTP_USER_AGENT. It's pretty easy to add other
http variables to the CGI environment.
------------------------------------------------------ */
void process_header(FILE *fp)
{
char buf[BUFSIZ];
char browser_tag[] = "User-Agent: ";
int b_taglen = strlen(browser_tag);
while( fgets(buf,BUFSIZ,fp) != NULL && strcmp(buf,"\r\n") != 0 )
{
printf("Header line: %s", buf);
if ( strncmp(buf, browser_tag, b_taglen) == 0 )
{
VLstore("HTTP_USER_AGENT",buf+b_taglen);
VLexport("HTTP_USER_AGENT");
}
}
}
/* ------------------------------------------------------ *
act_on_request( char *cmd, char *arg, int fd )
do what the request asks for and write reply to fd
if the request is the HTTP command: GET /foo/bar.html HTTP/1.0
then cmd is "GET" and arg is "/foo/bar.html"
------------------------------------------------------ */
void act_on_request(char *cmd, char *arg, int fd)
{
if ( strcmp(cmd,"GET") != 0 )
cannot_do(fd);
else if ( not_exist( arg ) )
do_404(arg, fd );
else if ( isadir( arg ) )
do_ls( arg, fd );
else if ( ends_in_cgi( arg ) )
do_exec( arg, fd );
else
do_cat( arg, fd );
}
/* ------------------------------------------------------ *
the reply header thing: all functions need one
if content_type is NULL then don't send content type
------------------------------------------------------ */
void header( FILE *fp, char *content_type )
{
fprintf(fp, "HTTP/1.0 200 OK\r\n");
if ( content_type )
fprintf(fp, "Content-type: %s\r\nServer: Myhttpd/0.1\r\n", content_type );//这里有修改标记自己的server版本号
}
/* ------------------------------------------------------ *
simple functions first:
cannot_do(fd) unimplemented HTTP command
and do_404(item,fd) no such object
------------------------------------------------------ */
void cannot_do(int fd)
{
FILE *fp = fdopen(fd,"w");
fprintf(fp, "HTTP/1.0 501 Not Implemented\r\n");
fprintf(fp, "Content-type: text/plain\r\n");
fprintf(fp, "\r\n");
fprintf(fp, "That command is not yet implemented\r\n");
fclose(fp);
}
void do_404(char *item, int fd)
{
FILE *fp = fdopen(fd,"w");
fprintf(fp, "HTTP/1.0 404 Not Found\r\n");
fprintf(fp, "Content-type: text/plain\r\n");
fprintf(fp, "\r\n");
fprintf(fp, "The item you requested: %s\r\nis not found\r\n",
item);
fclose(fp);
}
/* ------------------------------------------------------ *
the directory listing section
isadir() uses stat, not_exist() uses stat
do_ls runs ls. It should not
------------------------------------------------------ */
int isadir(char *f)
{
struct stat info;
return ( stat(f, &info) != -1 && S_ISDIR(info.st_mode) );
}
int not_exist(char *f)
{
struct stat info;
return( stat(f,&info) == -1 );
}
void do_ls(char *dir, int fd)
{
FILE *fp ;
fp = fdopen(fd,"w");
header(fp, "text/plain");
fprintf(fp,"\r\n");
fflush(fp);
dup2(fd,1);
dup2(fd,2);
close(fd);
execlp("ls","ls","-l",dir,NULL);
perror(dir);
exit(1);
}
/* ------------------------------------------------------ *
the cgi stuff. function to check extension and
one to run the program.
------------------------------------------------------ */
char * file_type(char *f)
/* returns 'extension' of file */
{
char *cp;
if ( (cp = strrchr(f, '.' )) != NULL )
return cp+1;
return "";
}
int ends_in_cgi(char *f)
{
return ( strcmp( file_type(f), "cgi" ) == 0 );
}
void do_exec( char *prog, int fd )
{
extern char **environ;
FILE *fp ;
fp = fdopen(fd,"w");
header(fp, NULL);
fflush(fp);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
environ = VLtable2environ();
execl(prog,prog,NULL);
perror(prog);
}
/* ------------------------------------------------------ *
do_cat(filename,fd)
sends back contents after a header
------------------------------------------------------ */
void do_cat(char *f, int fd)
{
char *extension = file_type(f);
char *content = "text/plain";
FILE *fpsock, *fpfile;
int c;
if ( strcmp(extension,"html") == 0 )
content = "text/html";
else if ( strcmp(extension, "gif") == 0 )
content = "image/gif";
else if ( strcmp(extension, "jpg") == 0 )
content = "image/jpeg";
else if ( strcmp(extension, "jpeg") == 0 )
content = "image/jpeg";
fpsock = fdopen(fd, "w");
fpfile = fopen( f , "r");
if ( fpsock != NULL && fpfile != NULL )
{
header( fpsock, content );
fprintf(fpsock, "\r\n");
while( (c = getc(fpfile) ) != EOF )
putc(c, fpsock);
fclose(fpfile);
fclose(fpsock);
}
exit(0);
}
/*
* socklib.c
*
* This file contains functions used lots when writing internet
* client/server programs. The two main functions here are:
*
* make_server_socket( portnum ) returns a server socket
* or -1 if error
* make_server_socket_q(portnum,backlog)
*
* connect_to_server(char *hostname, int portnum)
* returns a connected socket
* or -1 if error
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>
#include <strings.h>
#include <arpa/inet.h>
#define HOSTLEN 256
#define BACKLOG 1
int make_server_socket_q(int , int );
int make_server_socket(int portnum)
{
return make_server_socket_q(portnum, BACKLOG);
}
int make_server_socket_q(int portnum, int backlog)
{
struct sockaddr_in saddr; /* build our address here */
struct hostent *hp; /* this is part of our */
char hostname[HOSTLEN]; /* address */
int sock_id; /* the socket */
sock_id = socket(PF_INET, SOCK_STREAM, 0); /* get a socket */
if ( sock_id == -1 )
return -1;
/** build address and bind it to socket **/
bzero((void *)&saddr, sizeof(saddr)); /* clear out struct */
if ( gethostname(hostname, HOSTLEN) == -1 ) /* where am I ? */
{
perror("gethostname");
exit(1);
}
hp = gethostbyname(hostname); /* get info about host */
if ( hp == NULL ){
perror("Cannot get host");
exit(2);
}
/* fill in host part */
bcopy( (void *)hp->h_addr, (void *)&saddr.sin_addr, hp->h_length);
saddr.sin_port = htons(portnum); /* fill in socket port */
saddr.sin_family = AF_INET ; /* fill in addr family */
saddr.sin_addr.s_addr = INADDR_ANY; /* 作为服务器,你要绑定【bind】到本地的IP地址上进行监听【listen】,
但是你的机器上可能有多块网卡,也就有多个IP地址,这时候你要选择绑定在哪个IP上面,如果指定为INADDR_ANY,那么系统将绑定默认的网卡【即IP地址】。
其中INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。
INADDR_ANY,外部的client ask 从哪个server的地址进来都可以连接到80端口.*/
if ( bind(sock_id, (struct sockaddr *)&saddr, sizeof(saddr)) != 0 )
return -1;
/** arrange for incoming calls **/
if ( listen(sock_id, backlog) != 0 )
return -1;
return sock_id;
}
int connect_to_server(char *host, int portnum)
{
int sock;
struct sockaddr_in servadd; /* the number to call */
struct hostent *hp; /* used to get number */
/** Step 1: Get a socket **/
sock = socket( AF_INET, SOCK_STREAM, 0 ); /* get a line */
if ( sock == -1 )
return -1;
/** Step 2: connect to server **/
bzero( &servadd, sizeof(servadd) ); /* zero the address */
hp = gethostbyname( host ); /* lookup host's ip # */
if (hp == NULL)
return -1;
bcopy(hp->h_addr, (struct sockaddr *)&servadd.sin_addr, hp->h_length);
servadd.sin_port = htons(portnum); /* fill in port number */
servadd.sin_family = AF_INET ; /* fill in socket type */
if ( connect(sock,(struct sockaddr *)&servadd, sizeof(servadd)) !=0)
return -1;
return sock;
}
注意需要加 saddr.sin_addr.s_addr = INADDR_ANY;不然只能用127.0.0.1不能用外网ip
/* * header file for socklib
*/int make_server_socket_q(int , int );int make_server_socket(int );int connect_to_server(char *, int );* socklib.h
/* varlib.c * * a simple storage system to store name=value pairs * with facility to mark items as part of the environment * * interface: * VLstore( name, value ) returns 1 for 0k, 0 for no * VLlookup( name ) returns string or NULL if not there * VLlist() prints out current table * * environment-related functions * VLexport( name ) adds name to list of env vars * VLtable2environ() copy from table to environ * VLenviron2table() copy from environ to table * * details: * the table is stored as an array of structs that * contain a flag for `global' and a single string of * the form name=value. This allows EZ addition to the * environment. It makes searching pretty easy, as * long as you search for "name=" * */ #include <stdio.h> #include <stdlib.h> #include "varlib.h" #include <string.h> #define MAXVARS 200 /* a linked list would be nicer */ struct var { char *str; /* name=val string */ int global; /* a boolean */ }; static struct var tab[MAXVARS]; /* the table */ static char *new_string( char *, char *); /* private methods */ static struct var *find_item(char *, int); int VLstore( char *name, char *val ) /* * traverse list, if found, replace it, else add at end * since there is no delete, a blank one is a free one * return 1 if trouble, 0 if ok (like a command) */ { struct var *itemp; char *s; int rv = 1; /* find spot to put it and make new string */ if ((itemp=find_item(name,1))!=NULL && (s=new_string(name,val))!=NULL) { if ( itemp->str ) /* has a val? */ free(itemp->str); /* y: remove it */ itemp->str = s; rv = 0; /* ok! */ } return rv; } char * new_string( char *name, char *val ) /* * returns new string of form name=value or NULL on error */ { char *retval; retval = malloc( strlen(name) + strlen(val) + 2 ); if ( retval != NULL ) sprintf(retval, "%s=%s", name, val ); return retval; } char * VLlookup( char *name ) /* * returns value of var or empty string if not there */ { struct var *itemp; if ( (itemp = find_item(name,0)) != NULL ) return itemp->str + 1 + strlen(name); return ""; } int VLexport( char *name ) /* * marks a var for export, adds it if not there * returns 1 for no, 0 for ok */ { struct var *itemp; int rv = 1; if ( (itemp = find_item(name,0)) != NULL ){ itemp->global = 1; rv = 0; } else if ( VLstore(name, "") == 1 ) rv = VLexport(name); return rv; } static struct var * find_item( char *name , int first_blank ) /* * searches table for an item * returns ptr to struct or NULL if not found * OR if (first_blank) then ptr to first blank one */ { int i; int len = strlen(name); char *s; for( i = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ ) { s = tab[i].str; if ( strncmp(s,name,len) == 0 && s[len] == '=' ){ return &tab[i]; } } if ( i < MAXVARS && first_blank ) return &tab[i]; return NULL; } void VLlist() /* * performs the shell's `set' command * Lists the contents of the variable table, marking each * exported variable with the symbol '*' */ { int i; for(i = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ ) { if ( tab[i].global ) printf(" * %s\n", tab[i].str); else printf(" %s\n", tab[i].str); } } int VLenviron2table(char *env[]) /* * initialize the variable table by loading array of strings * return 1 for ok, 0 for not ok */ { int i; char *newstring; for(i = 0 ; env[i] != NULL ; i++ ) { if ( i == MAXVARS ) return 0; newstring = malloc(1+strlen(env[i])); if ( newstring == NULL ) return 0; strcpy(newstring, env[i]); tab[i].str = newstring; tab[i].global = 1; } while( i < MAXVARS ){ /* I know we don't need this */ tab[i].str = NULL ; /* static globals are nulled */ tab[i++].global = 0; /* by default */ } return 1; } char ** VLtable2environ() /* * build an array of pointers suitable for making a new environment * note, you need to free() this when done to avoid memory leaks */ { int i, /* index */ j, /* another index */ n = 0; /* counter */ char **envtab; /* array of pointers */ /* * first, count the number of global variables */ for( i = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ ) if ( tab[i].global == 1 ) n++; /* then, allocate space for that many variables */ envtab = (char **) malloc( (n+1) * sizeof(char *) ); if ( envtab == NULL ) return NULL; /* then, load the array with pointers */ for(i = 0, j = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ ) if ( tab[i].global == 1 ) envtab[j++] = tab[i].str; envtab[j] = NULL; return envtab; }
/* * header for varlib.c package * varlib.h */ int VLenviron2table(char **); int VLexport(char *); char *VLlookup(char *); void VLlist(); int VLstore( char *, char * ); char **VLtable2environ(); int VLenviron2table(char **);
#cc socklib.c varlib.c sol12.5.c -o sol1
#./sol1 80
参考:《Unix Linux编程实践教程》第12章