My Sunday

Coffee in, code out.

I’ve been writing a tool to help a non-mail admin set up auto-forwarding using procmail.  So far it compiles and runs but does the Wrong Thing when a user doesn’t already have a .procmailrc.

Here’s some source.  I wish I could upload the file but WordPress won’t let me.

/**
 * autofw.c - configures automatic mail forwarding via procmail
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <pwd.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <stdio.h>

#define MAX_LINE_LENGTH 0xff // maximum length of a line of ASCII text
#define UCFW_LEN        0x05 // length of the unconditional forward
#define DISP_NOCOPY     ":0" // nocopy procmail DI
#define FORWARD		"! " // forward directive
#define EOL		0x0a // linefeed

void print_version(void);
void print_help(void);
void list(int, const char*);
int  write_new_address(const char*, const char*);
int  write_new_blank_forward(char*);
int  find_unconditional_forward(FILE*);
char <strong>*get_home(const char*</strong>);
FILE <strong>*open_procmailrc(char*</strong>, char*, struct stat *);

int main(int argc, char *argv[]) {
	int  c, print_and_quit = 0, user_specified = 0, option_index = 1;
	char *user;

	/* get the uid of the caller */
	user = getenv("LOGNAME");

	const struct option long_options[] = {
		{"version", no_argument,       NULL, 'v'},
		{"help",    no_argument,       NULL, 'h'},
		{"user",    required_argument, NULL, 'u'},
		{"list",    no_argument,       NULL, 'l'},
		{0,         0,                 0,      0}
	};

	while ((c = getopt_long(argc, argv, "vhu:l", long_options, NULL)) != -1) {
		option_index++;

		#if _DEBUG
		fprintf(stderr, "Option 0x%x\n", c);
		#endif

		switch (c) {
			case 0:
				/* if this option set a flag, then break */
				if (long_options[option_index].flag != 0)
					break;

				printf ("option %s", long_options[option_index].name);

				/* dump out optarg, if any */
				if (optarg)
					printf (" with arg %s", optarg);

				/* GNU recommended break */
				printf("\n");
				break;

			case 'v':
				/* version */
				print_version();
				exit(EXIT_SUCCESS);
				break;  /* superfluous break */

			case 'h':
				/* help */
				print_help();
				exit(EXIT_SUCCESS);
				break;  /* superfluous break */

			case 'u':
				/* user takes an argument, increment option_index */
				option_index++;

				/* user specified - copy optarg off to user */
				user = (char*) calloc(sizeof (char), strlen(optarg) + 1);

				/* obligatory OOM checking */
				if (!user)
					exit(EXIT_FAILURE);

				strcpy(user, optarg);
				break;

			case 'l':
				/* asked to list the forwards */
				list(user_specified, user);
				exit(EXIT_SUCCESS);
				break; /* superfluous break */

			case '?':
				/* ? means that getopt couldn't parse the argument, so break */
				//break;
				/* fallthrough to EXIT_FAILURE */

			default:
				exit(EXIT_FAILURE);

		}

	}

	/**
	 * If getopt did its job right, we might still have one
	 * argument left in argv - the new forwarding e-mail
         * address.  If we haven't exited by now, the caller must
	 * intend for us to attempt to use this new forwarding
         * address.
	 */

	#if _DEBUG
	fprintf(stderr, "option_index is 0x%x and argc is 0x%x\n", option_index, argc);
	#endif 

	if (!(option_index &lt; argc)) {
		/**
		 * If optind isn't less than argc at this point, then we have
		 * no more arguments - the user failed to specify the new
                 * forwarding e-mail address.  Act confused.
		 */

		fprintf(stderr, "No new e-mail address specified.\n");
		exit(EXIT_FAILURE);
	}

	/**
         * If we haven't exited, then we must still have one more argument.
         * Store it in a new buffer.
         */

	char new_address[strlen(argv[option_index]) + 1];
	strcpy(new_address, argv[option_index]);

	/* get the home directory for this user */
	char *home = get_home(user);

	if (!home) {
		fprintf(stderr, "Unable to resolve a home for the specified user.\n");
		exit(EXIT_FAILURE);
	}

	/* write the new forwarding address into the user's .procmailrc */
	exit(write_new_address(home, new_address));

	/* I don't care about any errors - UNIX will handle them appropriately. */
	return 0;
}

void print_version() {
	/* TODO:  Put version info here */
	printf("autofw version 0.1, (c) 2010 Joshua Armstrong.\n");
	printf("Released under the GNU GPLv2.\n");
}

void print_help() {
	/* TODO:  Put help info here */
	printf("Pretend this is help text.\n");
}

int write_new_address(const char *home, const char *new_address) {
	char *procmailrc_path;
	char *mmap_base;
	char *newline_addr;
	char *forward_addr;
	FILE *procmailrc_fp;
	int  forward_offset;
	int  old_addr_len, new_addr_len, addr_len_diff, file_len_diff, new_file_len;
	int  i;

	/**
	 * Using char* instead of BYTE* for the mmapped region lets us operate
	 * on it using string functions.
	 */

	struct stat procmailrc_stat;
	procmailrc_path = (char*) calloc(sizeof (char), strlen(home) + 0x11);
	if(!procmailrc_path) {
		fprintf(stderr, "out of memory\n");
		exit(EXIT_FAILURE);
	}

	procmailrc_fp   = open_procmailrc(procmailrc_path, home, &amp;procmailrc_stat);

	/* mmap the file */
	mmap_base = mmap(
		NULL,			  /* no base address preference */
		procmailrc_stat.st_size,  /* file size from stat() call */
		PROT_READ | PROT_WRITE,   /* map for read and write     */
		MAP_SHARED,               /* use shared memory for map  */
		procmailrc_fp,            /* open file pointer          */
		0x00                      /* file base byte offset      */
	);

	if (mmap_base == -1 || mmap_base == NULL) {
		/* mmap failed */
		fprintf(stderr, "mmap failed\n");

		/* close the file */
		fclose(procmailrc_fp);
		exit(EXIT_FAILURE);
	}

	/**
	 * Since we've mmapped the file using MAP_SHARED, we can do
	 * conventional I/O as long as we explicitly call msync() before each
	 * op.  Since the find_unconditional_forward function operates
	 * directly on the file, we need to msync before jumping.
	 */

	if (msync(mmap_base, procmailrc_stat.st_size, NULL) == -1) {
		/* msync failed */
		fprintf(stderr, "msync failed\n");

		/* unmap the file */
		munmap(mmap_base, procmailrc_stat.st_size);

		/* close the file */
		fclose(procmailrc_fp);
		exit(EXIT_FAILURE);
	}

	if (forward_offset = find_unconditional_forward(procmailrc_fp) != -1) {
		/**
		 * We've found an existing unconditional forward.  Since
		 * procmailrc_fp is in the mmapped region, we can operate on
		 * it using string functions.
		 */

		/* find the newline */
		forward_addr = mmap_base + forward_offset;
		if (!(newline_addr = strchr(forward_addr, EOL))) {
			/* bad file format or read error */
			fprintf(stderr, "wrong file format or read error\n");

			/* unmap the file */
			munmap(mmap_base, procmailrc_stat.st_size);

			/* close the file */
			fclose(procmailrc_fp);
			exit(EXIT_FAILURE);
		}

		/**
		 * We need to write the new address between the forward and
		 * the newline.  If the new address is shorter than or the
		 * same length as the old one, we can just write and then
		 * byte shift the rest of the file back.  Else, we'll have to
		 * actually rewrite the whole file.
		 */

		old_addr_len  = newline_addr - forward_addr;
		new_addr_len  = strlen(new_address);
		addr_len_diff = old_addr_len - new_addr_len;
		file_len_diff = procmailrc_stat.st_size - (forward_offset + new_addr_len);

		if (addr_len_diff &gt;= 0) {
			/* write the new address */
			memcpy(forward_addr, new_address, addr_len_diff);

			/* shift the file back addr_len_diff bytes */
			forward_addr += new_addr_len;
			for (i = 0; i &lt; file_len_diff; i++)
				forward_addr[i] = newline_addr[i];

			/* msync */
		        if (msync(mmap_base, procmailrc_stat.st_size, NULL) == -1) {
	        	        /* msync failed */
				fprintf(stderr, "msync failed\n");

		                /* unmap the file */
                		munmap(mmap_base, procmailrc_stat.st_size);

	                 	/* close the file */
	                 	fclose(procmailrc_fp);
	                 	exit(EXIT_FAILURE);
	         	}

			/* truncate the file */
			new_file_len = procmailrc_stat.st_size - addr_len_diff;
			if (ftruncate(procmailrc_fp, new_file_len) != 0) {
				/* ftruncate failed*/
				fprintf(stderr, "ftruncate failed\n");

				/* unmap the file */
				munmap(mmap_base, procmailrc_stat.st_size);

				/* close the file */
				fclose(procmailrc_fp);
				exit(EXIT_FAILURE);
			}

			/**
			 * Truncate succeeded.  We need to remap the file now
			 * before any other process tries to access the shared
			 * mapped region.
			 */

			mmap_base = mremap(
				mmap_base,               /* suggest that we use the same base */
				procmailrc_stat.st_size, /* the old file length               */
				new_file_len,            /* the new file length               */
				MREMAP_MAYMOVE           /* let the system move the map base  */
			);

			if (mmap_base == -1 || mmap_base == NULL) {
				/* mremap failed */
				fprintf(stderr, "mremap failed\n");

				/* we can't unmap */

				/* close the file */
				fclose(procmailrc_fp);
				exit(EXIT_FAILURE);
			}

			/**
			 * We've successfully rewritten the file.
			 * Call msync() and close.  Before we proceed,
			 * it might be helpful to update the stat struct with
			 * the new file size.
			 */

			if (fstat(procmailrc_fp, &amp;procmailrc_stat) != 0) {
				/* stat failed */
				fprintf(stderr, "cannot stat %s\n", procmailrc_path);

				/* unmap the file */
				munmap(mmap_base, procmailrc_stat.st_size);

				/* close the file */
				fclose(procmailrc_fp);
				exit(EXIT_FAILURE);
			}

			/* msync */
		        if (msync(mmap_base, procmailrc_stat.st_size, NULL) == -1) {
	        	        /* msync failed */
				fprintf(stderr, "msync failed\n");

		                /* unmap the file */
                		munmap(mmap_base, procmailrc_stat.st_size);

	                 	/* close the file */
	                 	fclose(procmailrc_fp);
	                 	exit(EXIT_FAILURE);
	         	}

			/* unmap the file */
			if (munmap(mmap_base, procmailrc_stat.st_size) != 0) {
				/* munmap failed */
				fprintf(stderr, "munmap failed\n");

				/* close the file */
				fclose(procmailrc_fp);
				exit(EXIT_FAILURE);
			}

			/* close the file */
			if (fclose(procmailrc_fp) != 0) {
				/* fclose failed */
				fprintf(stderr, "could not close %s\n", procmailrc_path);
				exit(EXIT_FAILURE);
			}

			return 0;
		}

		else {
			/**
			 * The new address is longer than the old one.
			 * The strategy is to extend the file by the size
			 * difference between the two addresses in bytes,
			 * remap it, and then write the address.
			 */

			/* get the new file size */
			new_file_len = procmailrc_stat.st_size + addr_len_diff;

			/* call msync */
		        if (msync(mmap_base, procmailrc_stat.st_size, NULL) == -1) {
	        	        /* msync failed */
				fprintf(stderr, "msync failed\n");

		                /* unmap the file */
                		munmap(mmap_base, procmailrc_stat.st_size);

	                 	/* close the file */
	                 	fclose(procmailrc_fp);
	                 	exit(EXIT_FAILURE);
	         	}

			/* extend the file */
			if (ftruncate(procmailrc_fp, new_file_len) != 0) {
				/* ftruncate failed */
				fprintf(stderr, "failed to extend %s\n", procmailrc_path);

				/* unmap the file */
				munmap(mmap_base, procmailrc_stat.st_size);

				/* close the file */
				fclose(procmailrc_fp);
				exit(EXIT_FAILURE);
			}

			/**
			 * Shift the contents forward by the difference
			 * between the two addresses in bytes.
			 */

			char *orig_file_end   = mmap_base     + procmailrc_stat.st_size;
			char * new_file_end   = mmap_base     + new_file_len;
			int  rewrite_byte_len = orig_file_end - newline_addr;

			for (i = rewrite_byte_len; i &gt; 0; i--)
				new_file_end[i] = *(newline_addr + i);

			/* write the new address */
			memcpy(forward_addr, new_address, new_addr_len);

			/**
			 * We've successfully rewritten the file.
			 * Call msync() and close.  Before we proceed,
			 * it might be helpful to update the stat struct with
			 * the new file size.
			 */

			if (fstat(procmailrc_fp, &amp;procmailrc_stat) != 0) {
				/* stat failed */
				fprintf(stderr, "cannot stat %s\n", procmailrc_path);

				/* unmap the file */
				munmap(mmap_base, procmailrc_stat.st_size);

				/* close the file */
				fclose(procmailrc_fp);
				exit(EXIT_FAILURE);
			}

			/* call msync */
		        if (msync(mmap_base, procmailrc_stat.st_size, NULL) == -1) {
	        	        /* msync failed */
				fprintf(stderr, "msync failed\n");

		                /* unmap the file */
                		munmap(mmap_base, procmailrc_stat.st_size);

	                 	/* close the file */
	                 	fclose(procmailrc_fp);
	                 	exit(EXIT_FAILURE);
	         	}

			/* unmap the file */
			if (munmap(mmap_base, procmailrc_stat.st_size) != 0) {
				/* munmap failed */
				fprintf(stderr, "munmap failed\n");

				/* close the file */
				fclose(procmailrc_fp);
				exit(EXIT_FAILURE);
			}

			/* close the file */
			if (fclose(procmailrc_fp) != 0) {
				/* fclose failed */
				fprintf(stderr, "could not close %s\n", procmailrc_path);
				exit(EXIT_FAILURE);
			}

			return 0;
		}

		/* call msync */
	        if (msync(mmap_base, procmailrc_stat.st_size, NULL) == -1) {
        	        /* msync failed */
			fprintf(stderr, "msync failed\n");

	                /* unmap the file */
               		munmap(mmap_base, procmailrc_stat.st_size);

                 	/* close the file */
                 	fclose(procmailrc_fp);
                 	exit(EXIT_FAILURE);
         	}

	}

	else {

		/* get the new file size */
		new_file_len = procmailrc_stat.st_size + addr_len_diff + UCFW_LEN;

		/* call msync */
		if (msync(mmap_base, procmailrc_stat.st_size, NULL) == -1) {
			/* msync failed */
			fprintf(stderr, "msync failed\n");

			/* unmap the file */
			munmap(mmap_base, procmailrc_stat.st_size);

			/* close the file */
			fclose(procmailrc_fp);
			exit(EXIT_FAILURE);
		}

		/* extend the file */
		if (ftruncate(procmailrc_fp, new_file_len) != 0) {
			/* ftruncate failed */
			fprintf(stderr, "failed to extend %s\n", procmailrc_path);

			/* unmap the file */
			munmap(mmap_base, procmailrc_stat.st_size);

			/* close the file */
			fclose(procmailrc_fp);
			exit(EXIT_FAILURE);
		}

		/**
		 * Shift the contents forward by the difference
		 * between the two addresses in bytes.
		 */

		char *orig_file_end   = mmap_base     + procmailrc_stat.st_size;
		char * new_file_end   = mmap_base     + new_file_len;
		int  rewrite_byte_len = orig_file_end - newline_addr;

		for (i = rewrite_byte_len; i &gt; 0; i--)
			new_file_end[i] = *(newline_addr + i);

		/* write a blank forward */
		write_new_blank_forward(orig_file_end);
		forward_addr = orig_file_end + UCFW_LEN;

		/* write the new address */
		memcpy(forward_addr, new_address, new_addr_len);

		/**
		 * We've successfully rewritten the file.
		 * Call msync() and close.  Before we proceed,
		 * it might be helpful to update the stat struct with
		 * the new file size.
		 */

		if (fstat(procmailrc_fp, &amp;procmailrc_stat) != 0) {
			/* stat failed */
			fprintf(stderr, "cannot stat %s\n", procmailrc_path);

			/* unmap the file */
			munmap(mmap_base, procmailrc_stat.st_size);

			/* close the file */
			fclose(procmailrc_fp);
			exit(EXIT_FAILURE);
		}

		/* call msync */
		if (msync(mmap_base, procmailrc_stat.st_size, NULL) == -1) {
			/* msync failed */
			fprintf(stderr, "msync failed\n");

			/* unmap the file */
			munmap(mmap_base, procmailrc_stat.st_size);

			/* close the file */
			fclose(procmailrc_fp);
			exit(EXIT_FAILURE);
		}

		/* unmap the file */
		if (munmap(mmap_base, procmailrc_stat.st_size) != 0) {
			/* munmap failed */
			fprintf(stderr, "munmap failed\n");

			/* close the file */
			fclose(procmailrc_fp);
			exit(EXIT_FAILURE);
		}

		/* close the file */
		if (fclose(procmailrc_fp) != 0) {
			/* fclose failed */
			fprintf(stderr, "could not close %s\n", procmailrc_path);
			exit(EXIT_FAILURE);
		}

		return 0;
	}

	/* call msync */
        if (msync(mmap_base, procmailrc_stat.st_size, NULL) == -1) {
       	        /* msync failed */
		fprintf(stderr, "msync failed\n");

                /* unmap the file */
   		munmap(mmap_base, procmailrc_stat.st_size);

               	/* close the file */
               	fclose(procmailrc_fp);
               	exit(EXIT_FAILURE);
       	}

	/* unmap the file */
	if (munmap(mmap_base, procmailrc_stat.st_size) != 0) {
		/* munmap failed */
		fprintf(stderr, "munmap failed\n");

		/* close the file */
		fclose(procmailrc_fp);
		exit(EXIT_FAILURE);
	}

	/* close the file */
	if (fclose(procmailrc_fp) != 0) {
		/* fclose failed */
		fprintf(stderr, "could not close %s\n", procmailrc_path);
		exit(EXIT_FAILURE);
	}

	return 0;
}

int write_new_blank_forward(char *mmap_base) {
	sprintf(mmap_base, "\n:0\n! ");
	return 0;
}

int find_unconditional_forward(FILE *procmailrc_fp) {
	FILE *beginning_of_file = procmailrc_fp;
	FILE *beginning_of_forward;
	char *line;

	/* read through file looking for the unconditional forward */
	while (!feof(procmailrc_fp)) {
		fgets(line, MAX_LINE_LENGTH, procmailrc_fp);  

		if (strstr(line, DISP_NOCOPY)) {
			/* save current file pointer in case this is the line we want */
			beginning_of_forward = procmailrc_fp;

			/* get next line */
			fgets(line, MAX_LINE_LENGTH, procmailrc_fp);

			/* check for an unconditional forward */
			if (strstr(line, FORWARD)) {
				/* we've found an unconditional forward */
				/* roll forward two bytes to the beginning of the address */
				beginning_of_forward += 0x02;

				/* swap places */
				procmailrc_fp = beginning_of_forward;

				/* return the offset */
				return (beginning_of_forward - beginning_of_file);
			}

		}

	}

	/* if we got this far, there's a problem */
	return -1;
}

char *get_home(const char *username) {
	static char   *pw_dir;
	struct passwd *pw_struct = getpwnam(username);

	if (!pw_struct) {
		/* user wasn't found */
		fprintf(stderr, "user %s not found\n", username);
		exit(EXIT_FAILURE);
	}

	#if _DEBUG
	fprintf(stderr, "username is %s\n", username);
	fprintf(stderr, "pw_struct is at %p\n", pw_struct);
	fprintf(stderr, "pw_dir is %s\n", pw_struct-&gt;pw_dir);
	#endif

	pw_dir = (char*) calloc(sizeof (char), strlen(pw_struct-&gt;pw_dir));
	if (!pw_dir) {
		fprintf(stderr, "out of memory\n");
		exit(EXIT_FAILURE);
	}

	strcpy(pw_dir, pw_struct-&gt;pw_dir);
	return(pw_dir);
}

FILE *open_procmailrc(char *procmailrc_path, char *home, struct stat *procmailrc_stat) {
	static FILE *procmailrc_fp;
	strcpy(procmailrc_path, home);
	strcat(procmailrc_path, "/.procmailrc");

	/**
	 * Even though we're using mmap() to access the file, we still need to
	 * call stat() to get the file size;
	 */

	if (stat(procmailrc_path, procmailrc_stat) != 0) {
		fprintf(stderr, "unable to stat %s\n", procmailrc_path);
		exit(EXIT_FAILURE);
	}

	if (!(procmailrc_fp = fopen(procmailrc_path, "r+"))) {
		fprintf(stderr, "can't open %s for r/w\n", procmailrc_path);
		exit(EXIT_FAILURE);
	}

	return (procmailrc_fp);
}

void list(int user_option, const char *user) {
	char *user_home = get_home(user);
	char *current_addr, *newline_addr;
	char *procmailrc_path;
	FILE *procmailrc_fp;
	int  addr_len;

	struct stat procmailrc_stat;

	procmailrc_fp = open_procmailrc(procmailrc_path, user_home, &amp;procmailrc_stat);

	if (find_unconditional_forward(procmailrc_fp) != -1) {
		/* read current address */
		if (!(newline_addr = strchr(procmailrc_fp, EOL))) {
			/* bad file format or read error */
			fprintf(stderr, "wrong file format or read error\n");

			/* close the file */
			fclose(procmailrc_fp);
			exit(EXIT_FAILURE);
		}

		addr_len     = newline_addr - (int) procmailrc_fp;
		current_addr = (char*) calloc(sizeof (char), addr_len);
		if (!current_addr) {
			fprintf(stderr, "out of memory\n");
			exit(EXIT_FAILURE);
		}

		strncpy(current_addr, procmailrc_fp, addr_len);
		printf("currently forwarding all mail for %s to %s\n", user, current_addr);
	}

	else
		printf("mail is not currently auto-forwarded for %s\n", user);

	free(current_addr);
}
Advertisements
  1. #1 by Chadwick on January 17, 2010 - 6:19 PM

    And if you put crap coffee in, you get crap code out, right?

    • #2 by Joshua on January 18, 2010 - 8:15 AM

      Lol. Yeah but in this case I put good coffee in and still got crap code. Debugging is taking me as long as coding did. 😛

      • #3 by Chadwick on January 18, 2010 - 9:18 AM

        Yes well, errors can be introduced at any (and every) stage. Best to start from good materials.

  2. #4 by Matt on January 18, 2010 - 12:39 AM

    Erm, couldn’t you just force the user to get this procmailrc thing, and then go from there?

    • #5 by Joshua on January 18, 2010 - 8:16 AM

      Procmail is an e-mail sorting and routing engine with regex-based rules matching. It’s a server-side tool. Each mail account on the server (e-mail address) has a .procmailrc file that controls how his mail is sorted. That’s the file I’m working with here.

  3. #6 by Joshua on January 18, 2010 - 5:09 PM

    grr… I’m having problems with pointers going stale before they logically should. I think I’m at least on the right track.

    • #7 by Chadwick on January 18, 2010 - 7:38 PM

      Have you tried Saran Wrap?

      • #8 by Joshua on January 19, 2010 - 7:45 AM

        That’d probably work. Then again, if I wouldn’t leave them out on the table all day… 🙂

  4. #9 by Joshua on January 19, 2010 - 9:44 AM

    For teh lulz, I tried to bang out the autofw package in java this morning. Everything worked up until I got to the “Get name of current user” part. Then I realized that I needed to make a UNIX syscall. UNIX supports making system calls from assembly, C, C++, Fortran, and Objective C. Java doesn’t have a native syscall interface. Which sucks. There are ways around it but it just isn’t worth it.

    • #10 by Joshua on January 19, 2010 - 9:49 AM

      Perl, Python, and PHP have syscall interfaces, too but those are more like wrappers.

  5. #11 by Joshua on January 19, 2010 - 9:57 AM

    Okay – here’s where my pointer goes stale:

    (gdb) ni
    0x08049c45 in open_procmailrc (procmailrc_fp=0x897a4d8, procmailrc_path=0xbfa9c2b0 "maple",
        home=0x508560 "\207(­û§\205P", procmailrc_stat=0x8049fc4) at autofw.c:786
    786     }
    (gdb) print *procmailrc_fp
    $24 = {_flags = 1836017711, _IO_read_ptr = 0x74732f65 
    , _IO_read_end = 0x6d6165
    , _IO_read_base = 0x21
    , _IO_write_base = 0x6d6f682f
    , _IO_write_ptr = 0x74732f65
    , _IO_write_end = 0x2f6d6165
    , _IO_buf_base = 0x6f72702e
    , _IO_buf_end = 0x69616d63
    , _IO_save_base = 0x63726c
    , _IO_backup_base = 0x0, _IO_save_end = 0x81
    , _markers = 0x5091e8, _chain = 0x5091e8, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '', _shortbuf = "", _lock = 0x0, _offset = 0, __pad1 = 0x0, __pad2 = 0x0, __pad3 = 0x0, __pad4 = 0x0, __pad5 = 0, _mode = 0, _unused2 = '' } (gdb) ni write_new_address (home=0x897a4d8 "/home/steam", new_address=0xbfa9c2b0 "maple") at autofw.c:193 193 fseek(procmailrc_fp, 0x00, SEEK_SET); (gdb) print *procmailrc_fp Cannot access memory at address 0x20288 (gdb)

    procmailrc_fp is a global variable. Why does the pointer change when the call returns? I’ma go through an instruction trace and see what’s happening here.

  6. #12 by Joshua on January 19, 2010 - 10:23 AM

    Okay:

    0x08049c45 in open_procmailrc (procmailrc_fp=0x9f544d8, procmailrc_path=0xbfd54ef0 "maple",
        home=0x508560 "\207(­û§\205P", procmailrc_stat=0x8049fc4) at autofw.c:786
    786     }
    1: x/i $pc
    0x8049c45 :        ret
    (gdb)
    write_new_address (home=0x9f544d8 "/home/steam", new_address=0xbfd54ef0 "maple") at autofw.c:193
    193             fseek(procmailrc_fp, 0x00, SEEK_SET);
    1: x/i $pc
    0x8048d9f :      movl   $0x0,0x8(%esp)
    (gdb) print procmailrc_fp
    $6 = (FILE *) 0x20288
    (gdb) info registers
    eax            0x9f54d80        167071104
    ecx            0xffffffff       -1
    edx            0x5090dc 5279964
    ebx            0x507ff4 5275636
    esp            0xbfd54df0       0xbfd54df0
    ebp            0xbfd54ec8       0xbfd54ec8
    esi            0x3c2ca0 3943584
    edi            0x9f544e4        167068900
    eip            0x8048d9f        0x8048d9f 
    eflags         0x200286 [ PF SF IF ID ]
    cs             0x73     115
    ss             0x7b     123
    ds             0x7b     123
    es             0x7b     123
    fs             0x0      0
    gs             0x33     51
    

    So… in the call, procmailrc_fp is 0x9f54d80, which is a valid pointer to a file buffer. Somehow, the ret instruction is messing things up, since after the ret, procmailrc_fp is 0x20288, which is unallocated memory. Interestingly, EAX still contains the correct address, 0x9f54d80. So why/how is procmailrc_fp getting messed up? Lemme rerun the trace and watch the address of the pointer (i.e. the FILE**).

  7. #13 by Joshua on January 19, 2010 - 10:28 AM

    Right here:

    1: x/i $pc
    0x8049c44 :        pop    %ebp
    (gdb) print &procmailrc_fp
    $26 = (FILE **) 0xbfeda8a0
    (gdb) ni
    0x08049c45 in open_procmailrc (procmailrc_fp=0x93fc4d8, procmailrc_path=0xbfeda9a0 "maple",
        home=0x508560 "\207(­û§\205P", procmailrc_stat=0x8049fc4) at autofw.c:786
    786     }
    1: x/i $pc
    0x8049c45 :        ret
    (gdb) print &procmailrc_fp
    $27 = (FILE **) 0xbfeda980
    

    For some reason, even though procmailrc_fp is a global, the ret instruction changes the address of the variable. The question is why.

  8. #14 by Joshua on January 19, 2010 - 10:36 AM

    I figured it out. I had a local shadowing the global and the entire open_procmailrc call was modifying the local. When the call returned, procmailrc_fp then pointed to the global which was never initialized.

  9. #15 by Joshua on January 19, 2010 - 10:57 AM

    now fseek() is fucking the fd. Before calling fseek(), the fd looks like this:

    (gdb) print *procmailrc_fp
    $15 = {
      _flags = -72539008,
      _IO_read_ptr = 0x0,
      _IO_read_end = 0x0,
      _IO_read_base = 0x0,
      _IO_write_base = 0x0,
      _IO_write_ptr = 0x0,
      _IO_write_end = 0x0,
      _IO_buf_base = 0x0,
      _IO_buf_end = 0x0,
      _IO_save_base = 0x0,
      _IO_backup_base = 0x0,
      _IO_save_end = 0x0,
      _markers = 0x0,
      _chain = 0x252560,
      _fileno = 6,
      _flags2 = 0,
      _old_offset = 0,
      _cur_column = 0,
      _vtable_offset = 0 '',
      _shortbuf =     "",
      _lock = 0x865ae18,
      _offset = -1,
      __pad1 = 0x0,
      __pad2 = 0x865ae24,
      __pad3 = 0x0,
      __pad4 = 0x0,
      __pad5 = 0,
      _mode = 0,
      _unused2 =     '' 
    }
    

    AFTER the fseek(procmailrc_fp, 0x00, SEEK_SET); call, the fd looks like this:

    (gdb) print *procmailrc_fp
    $16 = {
      _flags = -72539008,
      _IO_read_ptr = 0xb7fd4000 "",
      _IO_read_end = 0xb7fd4000 "",
      _IO_read_base = 0xb7fd4000 "",
      _IO_write_base = 0xb7fd4000 "",
      _IO_write_ptr = 0xb7fd4000 "",
      _IO_write_end = 0xb7fd4000 "",
      _IO_buf_base = 0xb7fd4000 "",
      _IO_buf_end = 0xb7fd5000 
    , _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x252560, _fileno = 6, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '', _shortbuf = "", _lock = 0x865ae18, _offset = 0, __pad1 = 0x0, __pad2 = 0x865ae24, __pad3 = 0x0, __pad4 = 0x0, __pad5 = 0, _mode = 0, _unused2 = '' }

    fseek() should be seeking to the beginning of the file. Why, then, does it set the _IO_buff_end to a bogus address?

  10. #16 by Joshua on January 19, 2010 - 11:56 AM

    So it turns out that the fd is fine – I can cat the whole file using while(!feof(procmailrc_fp) putc(getc(procmailrc_fp), stderr);

    So why does read() fail?

  11. #17 by Joshua on January 19, 2010 - 11:58 AM

    Now I know for future reference that having a _IO_buf_end that’s 4KB out of bounds is normal. Heh.

  12. #18 by Joshua on January 19, 2010 - 12:17 PM

    Okay: I have a valid file descriptor, I can read from it:

    (gdb) print *procmailrc_fp
    $13 = {
      _flags = -72539008,
      _IO_read_ptr = 0xb7fd8000 "\n:0\n! ",
      _IO_read_end = 0xb7fd8000 "\n:0\n! ",
      _IO_read_base = 0xb7fd8000 "\n:0\n! ",
      _IO_write_base = 0xb7fd8000 "\n:0\n! ",
      _IO_write_ptr = 0xb7fd8000 "\n:0\n! ",
      _IO_write_end = 0xb7fd8000 "\n:0\n! ",
      _IO_buf_base = 0xb7fd8000 "\n:0\n! ",
      _IO_buf_end = 0xb7fd9000 
    , _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x508560, _fileno = 6, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '', _shortbuf = "", _lock = 0x9be9e18, _offset = 0, __pad1 = 0x0, __pad2 = 0x9be9e24, __pad3 = 0x0, __pad4 = 0x0, __pad5 = 0, _mode = -1, _unused2 = '' }

    Yet mmap() still fails:

    (gdb) info registers
    eax            0xffffffff       -1
    ecx            0xffffffcc       -52
    edx            0x9      9
    ebx            0x508560 5277024
    esp            0xbfd725b0       0xbfd725b0
    ebp            0xbfd72688       0xbfd72688
    esi            0x3c2ca0 3943584
    edi            0x9be94e4        163484900
    eip            0x80490d6        0x80490d6 
    eflags         0x200286 [ PF SF IF ID ]
    cs             0x73     115
    ss             0x7b     123
    ds             0x7b     123
    es             0x7b     123
    fs             0x0      0
    gs             0x33     51
    

    Errno is 9 (EBADF). Why would mmap think that procmailrc_fp is a bad fd if I can read from it?

    • #19 by Joshua on January 19, 2010 - 12:19 PM

      FYI, Linux syscalls on x86 boxen put the retval in EAX and the errno in EDX. So the 0xffffffff (-1) in EAX above is the return value from the mmap() call and the 0x9 (9) in EDX is the errno (9 is EBADF).

  13. #20 by Joshua on January 21, 2010 - 11:27 AM

    AHA! mmap() expects a FILE DESCRIPTOR (an int) and NOT a pointer to a FILE struct. Yayyy!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: