Remote Monitoring

Remote Monitoring

With all this under our belts, let's try to build a simple module. I don't know about anyone else, but at times I've thought it would be nice to be able to monitor remotely certain things about my machine. Say I'm behind one of those aggressive firewalls that Mike keeps talking about, so I can't log in to my machine to check the load average. Nor can I run xload and send the display to the machine I'm on. Since HTTP is the only port I have available, why not monitor the load average from a Web browser? And thus is born the mod_proc module.

I'm going to be building a module that allows me to remotely browse the /proc directory on my machine, with the exception of the process-specific directories. That way I can look at much more than just load average. I should be able to navigate the directories and view files within those directories. I'll avoid any fancy formatting, just to keep things simple. After all, it's just numbers that I'm after.

Of course, the easiest way to do this would be to set up a virtual server with the /proc directory as its document root. But with the obstinancy of a true hacker, I've got my mind set on an Apache module, and nothing's going to stop me!

Writing the Code

The first thing to do is include the appropriate headers:

  #include <sys/types.h>  
  #include <stdio.h>  
  #include <dirent.h>  
  #include "httpd.h"  
  #include "http_config.h"  

I already know I need the first three headers, since I'm going to be opening files and reading directory entries. The remaining two are header files from the Apache distribution, which I need to access the Apache API.

Next , I need to define the module itself, its handler functions, and its commands:

 handler_rec procHandlers[] = { 
 { "proc-handler", procHandler } 
  };  
   
  command_rec procCommands[] = {  
  { NULL }  
  };  
   
  module proc_module = {  
  STANDARD_MODULE_STUFF,  
  NULL, /* Initializer */  
  NULL, /* Dir config creator */  
  NULL, /* Dir merger */  
  NULL, /* Server config */  
  NULL, /* Merge server config */  
  procCommands, /* Commands */  
  procHandlers, /* Handlers */  
  NULL, /* URI mapping */  
  NULL, /* Authentication */  
  NULL, /* Authorization */  
  NULL, /* Other access control */  
  NULL, /* MIME type mapping */  
  NULL, /* Fixups */  
  NULL, /* Logging */  
  NULL /* Header parser */  
  };  

This module isn't interested in intercepting every single request, so most of the handlers for the different request steps are set to NULL. Instead, I'll activate this module when the requested URI has a specific format, which can be done in the httpd.conf file. So I only need to provide a single handler, through the procHandlers structure. Likewise, I'll define a placeholder for the commands through the procCommands structure. These three definitions set up everything that Apache needs to know to use my module.

So now I just need to fill in the procHandler function. Handler functions are passed a pointer to a request (a struct request_rec * ) and should return an integer value indicating whether it has handled the request, chooses to ignore it, or needs to raise a server error.

  static int procHandler(struct request_rec *r) {  
  int code;  
  struct stat fs;  
  char *path;  
   
  r->allowed = (1 << M_GET);  
  if (r->method_number != M_GET) return DECLINED;  
   
  path = ap_pstrcat(r->pool, "/proc", r->path_info, NULL);  
  if (stat(path, &fs) < 0) return HTTP_NOT_FOUND;  
  if (S_ISDIR(fs.st_mode)) {  
  code = showDir(r, path);  
  }  
  else {  
  code = showFile(r, path);  
  }  
  if (code != OK) return code;  
  return OK;  
  }  

This is a fairly simple handler that delegates the major work of reading the files and directories to another pair of functions, showDir, and showFile. The first two lines of code beyond the local variable declarations ensure that the handler will respond only to HTTP GET requests . Since this monitoring module is read-only by nature, I don't want a Web browser to be POSTing information to a matching URI. Hence, any type of request other then GET causes the handler to return the constant DECLINED.

The next line of code retrieves the path_info from the request, which is the part of the URI after the prefix I choose to invoke this handler. Then it constructs the actual path on the file system that corresponds to that URI. For example, if I choose /monitor as the URI prefix, then a request to /monitor/uptime will have a path_info of /uptime and hence will map to the file /proc/uptime.

The next line does a stat call to determine if the path is a file or a directory. If the path is not found and the stat call fails, then I return the constant HTTP_NOT_FOUND from the handler. This will invoke a 404 Not Found error, and Apache will retrieve the appropriate error document and send it to the client. If the path exists, then the appropriate delegate function will be called.

Forging ahead, I'll create the simpler of the two delegates, showFile:

  static int showFile(request_rec *r, const char *name) {  
  FILE *file;  
   
  file = fopen(name, "r");  
  if (file == NULL) {  
  if (errno == EACCES) return HTTP_FORBIDDEN;  
  return HTTP_NOT_FOUND;  
  }  
   
  r->content_type = "text/plain";  
  ap_send_http_header(r);  
   
   ap_send_fd(file, r); 
  fclose(file);  
  return OK;  
  }  

The first step is to open the file and verify that it's readable. If there's an access error, I return the constant HTTP_FORBIDDEN back to the handler function, which is passed in turn back to the server. Even though this function shouldn't be called with a nonexistent path argument, I still code for that case and return HTTP_NOT_FOUND.

If the file is successfully opened, the next two lines set the content type to plain text and send the HTTP headers. Finally, I use the ap_send_fd function to append the contents of an open file to the HTTP response, close the file handle, and return OK back to the handler function. You can see how the Apache APIs make a relatively complex task simple to code, even in such a notoriously long-winded language as C.

The only thing left now is the show Dir function, which turns out to be the most complicated. I want it to output a hyperlinked list of the directory's content, so I'll need to send a content type of text/html.

  static int showDir(request_rec *r, const char *name) {  
  DIR *dir;  
  struct dirent *de;  
  struct stat fs;  
  int is_root;  
   
  dir = opendir(name);  
  if (dir == NULL) {  
  if (errno == EACCES) return HTTP_FORBIDDEN;  
   return HTTP_NOT_FOUND; 
 } 
 
 r->content_type = "text/html"; 
 ap_send_http_header(r); 
 is_root = (strcmp(name, "/") == 0) ? 1 : 0; 
 ap_rputs("<dl>\n", r); 
 while (1) { 
 de = readdir(dir); 
 if (de == NULL  de->d_name == NULL) break; 
 if (is_root && isdigit(de->d_name)) continue; 
 ap_rprintf(r, "<dt><a href=\"%s/%s\">%s</a>\n", name, de-d_name, de->d_name); 
 } 
 ap_rputs("</dl>\n", r); 
 closedir(dir); 
 
 return OK; 
 } 

The first five lines open the directory and perform an access check similar to the one in the showFile function. Then I set the content type and send the headers. After this, the fun starts. The next line outputs some HTML using ap_rputs, and then the code goes into a loop, reading in directory entries. Each directory entry is given its own <dt> tag and hyperlink, which is written out with the ap_rprintf function. However, if I'm in the root directory and the entry name begins with a digit, this entry must be one of the process subdirectories, so I'll skip it instead. The last three lines write out the closing HTML, close the directory, and return OK to the handler.

Compiling it

This is all the code you need to write for a simple module! Next, I need to compile it. Since I don't want to mess around with the Apache source distribution, I'll use the apxs tool, which is just a Perl script that sets up the correct compiler and linker options to build a module. It does require that you have the Apache header files installed under /usr/include, either copied from the source distribution or obtained as a package. To obtain these files under Debian, I simply need to run

 apt-get install apache-dev 

apxs is distributed in the Apache source distribution (so you might have to download it anyway!). It resides in the src/support subdirectory. Unless you're compiling and installing from source, you'll need to modify a few lines in the script to get it working. Find the following lines in apxs and change them appropriately, using paths for your system when applicable .

 my $CFG_LD_SHLIB = q(gcc); # substituted via 
 Makefile.tmpl 
 my $CFG_LDFLAGS_SHLIB = q(-shared); # substituted via 
 Makefile.tmpl 
 my $CFG_PREFIX = q(/usr/local); # substituted via 
 APACI install 
 my $CFG_SBINDIR = q(/usr/sbin); # substituted via 
 APACI install 
 my $CFG_INCLUDEDIR = q(/usr/include/apache-1.3); # substituted via 
 APACI install 
 my $CFG_LIBEXECDIR = q(/usr/local/lib); # substituted via 
 APACI install 
 my $CFG_SYSCONFDIR = q(/etc/apache); # substituted via 
 APACI install 

As you can see from the comments, these values aren't filled correctly until you actually do a make install of the source. If you have a pre-existing installation, you probably don't want to do that, so you're forced to perform these manual edits. On my system, I need to change the following two lines to the values shown. Such is the price of flexibility!

 my $CFG_PREFIX = q(/usr); 
 my $CFG_LIBEXECDIR = q(/usr/lib/apache/1.3); 

Now that apxs is in shape, let's try compiling the module.

 $ apxs -c mod_proc.c 
 gcc -DLINUX=22 -DUSE_HSREGEX -fpic -DSHARED_MODULE -I/usr/include -c mod_proc.c 
 gcc -shared -o mod_proc.so mod_proc.o 

If you get compile or link errors, carefully read what they're saying. If the errors aren't in your code, they probably result from an incorrectly set path in apxs. You'll need to review the entries and ensure that everything is correct.

But it worked for me, so now I can copy the shared library into the appropriate directory. On my Debian system, it's /usr/lib/apache/1.3/; just find where the all the other standard Apache modules are located, and that's the place to put it. If you want, you can even install it in a nonstandard place and provide the full path in the Apache configuration file. Speaking of which, I need to change that file to get this module working:

 LoadModule proc_module /usr/lib/apache/1.3/mod_proc.so 
 
 <Location /monitor> 
 SetHandler proc-handler 
 order deny, allow 
 deny all 
 allow from *.nastyproxy.com 
 </Location> 

The first line tells the server to dynamically load the module at start-up. The first argument is actually the symbol name that Apache looks for, so it must match the name of the module structure in the module source. In this case, I called it proc_module. The second argument, obviously, is the full path to the shared library. It can also be a relative path, in which case the path is taken relative to the server root.

I use the Location directive to tell Apache to invoke this module when I reference any URI starting with the string /monitor. The SetHandler directive defines the name of the handler to use, and, as you remember, I named it proc-handler. The remaining lines provide rudimentary IP-based access control, which really wouldn't be sufficient to keep a determined attacker (or even a lazy attacker) from viewing the guts of my system. In order to make this module reasonably secure, I'd have to set up authentication and SSL by adding a few more lines to the configuration file. Although the module could probably use more verbose error logging, I can still track who might be attempting to use the interface by looking at the access logs.

The Big Moment

Now let's test the end result! I'll try to access http://www.mymachine.net/monitor ( Figure 9-1 ).

Figure 9-1. Browsing the /proc directory.

There are a few things wrong here: I really don't need a parent directory link from this URI, and the directory entries are coming up in their natural order, which is far from alphabetical. But it's good enough for my purposes. Let's try clicking on cpuinfo . I have to scroll down to find it, but it should be there, since it's a standard file in the /proc directory ( Figure 9-2 ).

Figure 9-2. Looking up information.

Perfect ”this is just what I'm after. Obviously, there are still a few issues, some of them serious. For example, if I navigate to /monitor/self/cwd, which is a symlink out of /proc, I can freely browse the entire file system on my machine. This isn't a vulnerability I would want to expose to the world. So the moral of the story is, Don't build this module! At least, don't deploy it on a machine where the Web server is accessible to the outside world. Or write the module with better security. You have been warned !

 



Multitool Linux. Practical Uses for Open Source Software
Multitool Linux: Practical Uses for Open Source Software
ISBN: 0201734206
EAN: 2147483647
Year: 2002
Pages: 257

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net