Information wants to be free...

Python DNS Proxy

Here is a DNS proxy server script written in Python. The purpose is to forward DNS requests to another (real) DNS server, and then send the replies back to the client. No mangling of the UDP datagrams are performed, the packets are simply passing through.

I have only tried to run the script on Linux, and the script will also attempt to find the script host's own DNS server in /etc/resolv.conf to use as the remote DNS server. And since it will attempt to bind on port 53, you will probably have to run the script as root.

Enjoy:

#!/usr/bin/python

from threading import Thread
import socket
import Queue
import time

class Forward(Thread):
    def __init__(self, packet, client_address, dns_ip, reply_queue):
        Thread.__init__(self)
        self.packet = packet
        self.client_address = client_address
        self.dns_ip = dns_ip
        self.dns_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.reply_queue = reply_queue

    def run(self):
        self.dns_sock.sendto(self.packet, (self.dns_ip, 53))
        while True:
            new_packet, address = self.dns_sock.recvfrom(512)
            if address[0] == self.dns_ip:
                self.reply_queue.put((new_packet, self.client_address))
                break

class DNSProxy(object):
    def __init__(self, listen_port, dns_ip):
        self.dns_ip = dns_ip
        self.reply_queue = Queue.Queue()
        self.server_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.server_sock.bind(("", listen_port))
        self.server_sock.setblocking(0)
        print "Using DNS server IP: %s" % (dns_ip)

    def check(self):
        # Check for incoming requests and start threads on them.
        try:
            packet, address = self.server_sock.recvfrom(512)
            if len(packet) > 0:
                print "Recevied request from:", address
                t = Forward(packet, address, self.dns_ip, self.reply_queue)
                t.start()
        except socket.error, e:
            if e[0] != 11 and e[0] != 10035:
                raise

        # Check if any threads has put a reply on the queue to send.
        try:
            packet, address = self.reply_queue.get_nowait()
            self.server_sock.sendto(packet, address)
            print "Sent reply to:", address
        except Queue.Empty:
            pass

def get_dns_server():
    fh = open("/etc/resolv.conf", "r")
    for line in fh:
        if len(line) > 11:
            if line[:11] == "nameserver ":
                return line[11:].strip()
    fh.close()

if __name__ == "__main__":
    proxy = DNSProxy(53, get_dns_server())
    while True:
        proxy.check()
        time.sleep(0.01)
          


Topic: Scripts and Code, by Kjetil @ 25/12-2011, Article Link

Old Curses Game

Here is a re-release of an old game I programmed a long time ago, called simply "cgame". It was one of my first "real" projects with C and the curses library. The code has gone through heavy re-factoring for readability, so it looks almost nothing like the original, but the game mechanics remains the same.

Here's what it looks like:

Screenshot of cgame.


The rules are simple, collect all the "money" and avoid being captured by the yellow 'M'. The idea behind the 'M' actually came from a game on the Commodore 64, called "Escape MCP", that I recalled playing as a kid. Here's what the main screen looks like:

Screenshot of Escape MCP.


Finally, the code:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <ncurses.h>

#define GAME_ROWS 24
#define GAME_COLS 80

#define AMOUNT_OF_MONEY  20
#define AMOUNT_OF_MINES  10
#define AMOUNT_OF_CRATES 5

#define PAIR_YELLOW 1
#define PAIR_CYAN 2

typedef enum {
  DIRECTION_NONE,
  DIRECTION_LEFT,
  DIRECTION_RIGHT,
  DIRECTION_UP,
  DIRECTION_DOWN,
} direction_t;

typedef enum {
  MOVE_OK,
  MOVE_BLOCKED,
  MOVE_ON_MINE,
  MOVE_ON_MONEY,
  MOVE_ON_ENEMY,
} move_t;

typedef struct object_s {
  int y, x;
  char symbol;
  int attributes;
  int taken;
} object_t;

static object_t player;
static object_t enemy;
static object_t money[AMOUNT_OF_MONEY];
static object_t mines[AMOUNT_OF_MINES];
static object_t crates[AMOUNT_OF_CRATES];

static int money_collected;
static int steps_taken;
static int enemy_idle_count;

static void draw_object(object_t *object)
{
  if (object->attributes != 0 && has_colors())
    wattrset(stdscr, object->attributes);
  mvaddch(object->y, object->x, object->symbol);
  if (object->attributes != 0 && has_colors())
    wattrset(stdscr, A_NORMAL);
}

static void find_empty_spot(int *y, int *x)
{
  do {
    move((random() % (GAME_ROWS - 2)) + 1, (random() % (GAME_COLS - 2)) + 1);
  } while ((inch() & A_CHARTEXT) != ' ');
  getyx(stdscr, *y, *x);
}

static void init_game(void)
{
  int i;

  money_collected  = 0;
  steps_taken      = 0;
  enemy_idle_count = 0;

  srandom(time(NULL));

  player.symbol = '@';
  player.attributes = 0;
  find_empty_spot(&player.y, &player.x);
  draw_object(&player);

  enemy.symbol = 'M';
  enemy.attributes = A_BOLD | COLOR_PAIR(PAIR_YELLOW);
  find_empty_spot(&enemy.y, &enemy.x);
  draw_object(&enemy);

  for (i = 0; i < AMOUNT_OF_MONEY; i++) {
    money[i].symbol = '$';
    money[i].attributes = A_BOLD;
    money[i].taken = 0;
    find_empty_spot(&money[i].y, &money[i].x);
    draw_object(&money[i]);
  }

  for (i = 0; i < AMOUNT_OF_MINES; i++) {
    mines[i].symbol = '*';
    mines[i].attributes = COLOR_PAIR(PAIR_CYAN);
    find_empty_spot(&mines[i].y, &mines[i].x);
    draw_object(&mines[i]);
  }

  for (i = 0; i < AMOUNT_OF_CRATES; i++) {
    crates[i].symbol = '#';
    crates[i].attributes = COLOR_PAIR(PAIR_YELLOW);
    find_empty_spot(&crates[i].y, &crates[i].x);
    draw_object(&crates[i]);
  }
}

static direction_t direction_to_player(void)
{
  int y, x;

  y = enemy.y - player.y;
  x = enemy.x - player.x;

  if ((x < 0) && ((x <= (-y)) || (x >= y)) &&
     ((x <= y)                || (x >= (-y))))
    return DIRECTION_RIGHT;
  if ((x > 0) && ((x <= y) || (x >= (-y))) &&
     ((x <= (-y))          || (x >= y)))
    return DIRECTION_LEFT;
  if (y > 0)
    return DIRECTION_UP;
  if (y < 0)
    return DIRECTION_DOWN;

  return DIRECTION_NONE;
}

static direction_t random_direction(void)
{
  switch (random() % 4) {
  case 0:
    return DIRECTION_LEFT;
  case 1:
    return DIRECTION_RIGHT;
  case 2:
    return DIRECTION_UP;
  case 3:
    return DIRECTION_DOWN;
  }
  return DIRECTION_NONE;
}

static void move_object_actual(object_t *object, direction_t direction)
{
  switch (direction) {
  case DIRECTION_LEFT:
    object->x--;
    break;
  case DIRECTION_RIGHT:
    object->x++;
    break;
  case DIRECTION_UP:
    object->y--;
    break;
  case DIRECTION_DOWN:
    object->y++;
    break;
  default:
    break;
  }
}

static move_t move_object_check(object_t *object, direction_t direction)
{
  int i, hit_y, hit_x;
  char hit_object;

  switch (direction) {
  case DIRECTION_LEFT:
    hit_y = object->y;
    hit_x = object->x - 1;
    break;
  case DIRECTION_RIGHT:
    hit_y = object->y;
    hit_x = object->x + 1;
    break;
  case DIRECTION_UP:
    hit_y = object->y - 1;
    hit_x = object->x;
    break;
  case DIRECTION_DOWN:
    hit_y = object->y + 1;
    hit_x = object->x;
    break;
  case DIRECTION_NONE:
  default:
    return MOVE_BLOCKED;
  }
  hit_object = mvinch(hit_y, hit_x) & A_CHARTEXT;

  if (object->symbol == '@') { /* Player. */
    switch (hit_object) {
    case '*':
      move_object_actual(object, direction);
      return MOVE_ON_MINE;
    case '$':
      move_object_actual(object, direction);
      return MOVE_ON_MONEY;
    case 'M':
      move_object_actual(object, direction);
      return MOVE_ON_ENEMY;
    case '#':
      for (i = 0; i < AMOUNT_OF_CRATES; i++) {
        if (hit_y == crates[i].y && hit_x == crates[i].x) {
          if (move_object_check(&crates[i], direction) == MOVE_OK) {
            move_object_actual(object, direction);
            return MOVE_OK;
          } else {
            return MOVE_BLOCKED;
          }
        }
      }
      return MOVE_BLOCKED;
    case ' ':
      move_object_actual(object, direction);
      return MOVE_OK;
    default:
      return MOVE_BLOCKED;
    }

  } else if (object->symbol == 'M') { /* Enemy. */
    if (hit_object == ' ' || hit_object == '@') {
      move_object_actual(object, direction);
      return MOVE_OK;
    } else {
      return MOVE_BLOCKED;
    }

  } else { /* Crates, etc. */
    if (hit_object == ' ') {
      move_object_actual(object, direction);
      return MOVE_OK;
    } else {
      return MOVE_BLOCKED;
    }
  }
}

static void draw_screen(void)
{
  int i;

  erase();

  box(stdscr, '|', '-');

  for (i = 0; i < AMOUNT_OF_MONEY; i++)
    if (! money[i].taken)
      draw_object(&money[i]);
  for (i = 0; i < AMOUNT_OF_MINES; i++)
    draw_object(&mines[i]);
  for (i = 0; i < AMOUNT_OF_CRATES; i++)
    draw_object(&crates[i]);
  draw_object(&player);
  draw_object(&enemy);

  move(player.y, player.x);

  refresh();
}

int main(void)
{
  int i, maxx, maxy, direction;
  char *done = NULL;

  initscr();
  if (has_colors()) {
    start_color();
    init_pair(PAIR_YELLOW, COLOR_YELLOW, COLOR_BLACK);
    init_pair(PAIR_CYAN,   COLOR_CYAN,   COLOR_BLACK);
  }
  noecho();
  keypad(stdscr, TRUE);
  getmaxyx(stdscr, maxy, maxx);

  if (maxy != GAME_ROWS || maxx != GAME_COLS) {
    endwin();
    fprintf(stderr, "Terminal must be %dx%d!\n", GAME_ROWS, GAME_COLS);
    return 1;
  }

  init_game();

  while (done == NULL) {
    draw_screen();

    /* Get player input. */
    direction = DIRECTION_NONE;
    switch (getch()) {
    case KEY_LEFT:
      direction = DIRECTION_LEFT;
      break;

    case KEY_RIGHT:
      direction = DIRECTION_RIGHT;
      break;

    case KEY_UP:
      direction = DIRECTION_UP;
      break;

    case KEY_DOWN:
      direction = DIRECTION_DOWN;
      break;

    case KEY_RESIZE:
      done = "Window resize detected, aborted...";
      break;

    case 'q':
    case 'Q':
    case '\e':
      done = "Quitting...";
      break;

    default:
      break;
    }

    /* Move player, and check for collisions. */
    switch (move_object_check(&player, direction)) {
    case MOVE_OK:
      steps_taken++;
      break;

    case MOVE_BLOCKED:
      break;

    case MOVE_ON_MINE:
      done = "You were killed by a mine!";
      break;

    case MOVE_ON_MONEY:
      for (i = 0; i < AMOUNT_OF_MONEY; i++) {
        if (player.y == money[i].y && player.x == money[i].x) {
          money_collected++;
          money[i].taken = 1;
        }
      }
      break;

    case MOVE_ON_ENEMY:
      done = "You moved onto the enemy!";
      break;
    }

    draw_screen(); /* To update the positions of the objects, not visible. */

    /* Move enemy, and check for collision with player. */
    if (move_object_check(&enemy, direction_to_player()) == MOVE_BLOCKED) {
      enemy_idle_count++;
      if (enemy_idle_count > 3) {
        if (move_object_check(&enemy, random_direction()) == MOVE_OK)
          enemy_idle_count = 0;
      }
    }

    if (player.y == enemy.y && player.x == enemy.x) {
      done = "You were killed by the enemy!";
      break;
    }

    if (money_collected == AMOUNT_OF_MONEY)
      done = "Well done! all money collected!";
  }

  draw_screen();
  endwin();

  fprintf(stderr, "%s\n", done);
  fprintf(stderr, "You collected %d$ of %d$.\n",
    money_collected, AMOUNT_OF_MONEY);
  fprintf(stderr, "You walked %d step%s.\n", 
    steps_taken, (steps_taken == 1) ? "" : "s");

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 06/11-2011, Article Link

Recursive String Search

Here is another one of those simple programs that are missing on the Windows platform; a functioning string search tool! The program's scheme is somewhat similar to using the UNIX grep command's combination of the '-n' '-i' and '-r' switches. It will search in a directory and it's sub-directories for a fixed case-insensitive string and display the name of each file, the line number and the line itself where that string is found.

Here's is a link to the binary for Win32 (compiled with MinGW) and here's the source code:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <unistd.h>
#include <dirent.h>

static char line[1024];

static void recurse(char *path, char *string, int len)
{
  char full_path[PATH_MAX];
  int n, match, name_shown, line_no;
  struct dirent *entry;
  struct stat st;
  DIR *dh;
  FILE *fh;

  dh = opendir(path);
  if (dh == NULL) {
    fprintf(stderr, "Warning: Unable to open directory: %s\n", path);
    return;
  }

  while ((entry = readdir(dh))) {
    if (entry->d_name[0] == '.')
      continue; /* Ignore files with leading dot. */

#ifdef WINNT
    snprintf(full_path, PATH_MAX, "%s\\%s", path, entry->d_name);
#else
    snprintf(full_path, PATH_MAX, "%s/%s", path, entry->d_name);
#endif

    stat(full_path, &st);
    if (S_ISDIR(st.st_mode)) {
      /* Traverse. */
      recurse(full_path, string, len);
    } else if (S_ISREG(st.st_mode)) {
      /* Search. */
      fh = fopen(full_path, "r");
      if (fh == NULL) {
        fprintf(stderr, "Warning: Unable to open file: %s\n", full_path);
        continue;
      }
      name_shown = line_no = 0;
      while (fgets(line, 1024, fh) != NULL) {
        line_no++;
        match = 0;
        for (n = 0; line[n] != '\0'; n++) {
          if (toupper(line[n]) == toupper(string[match])) {
            match++;
            if (match >= len) {
              if (! name_shown) {
                printf("%s\n", full_path);
                name_shown = 1;
              }
              printf("%d:%s", line_no, line);
              break;
            }
          } else {
            match = 0;
          }
        }
      }
      fclose(fh);
    }
  }

  closedir(dh);
  return;
}

int main(int argc, char *argv[])
{

  if (argc != 3) {
    fprintf(stderr, "Usage: %s <directory> <string>\n", argv[0]);
    return 1;
  }

  recurse(argv[1], argv[2], strlen(argv[2]));
  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 01/10-2011, Article Link

Mastermind in Curses

I present yet another curses-based game. This time, it's a "turn-based" one, blocking/waiting for user input, instead of always going forward in an "real-time" oriented manner. It's based on an old popular board game. And more; it's in color!

The rules are simple; try to guess the hidden code in 10 or less attempts. Each time a guess is made, some hints are given that will indicate if a part of the code is correct and in the correct position (X), or part a of the code is correct, but in the wrong position (/).

Here's a screenshot:

Screenshot of curses-based mastermind.


Here's the code:

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ncurses.h>

#define CODE_SLOTS 4
#define GUESS_SLOTS 10

typedef enum {
  CODE_NONE    = 0,
  CODE_ALPHA   = 1,
  CODE_BRAVO   = 2,
  CODE_CHARLIE = 3,
  CODE_DELTA   = 4,
  CODE_ECHO    = 5,
  CODE_FOXTROT = 6,
} code_t;

typedef enum {
  HINT_NONE = 0,
  HINT_CODE_AND_POSITION,
  HINT_CODE_ONLY,
} hint_t;

typedef struct guess_s {
  code_t code[CODE_SLOTS];
  hint_t hint[CODE_SLOTS];
} guess_t;

static const code_t code_to_char[] = {'.', 'A', 'B', 'C', 'D', 'E', 'F'};
static const hint_t hint_to_char[] = {' ', 'X', '/'};

static code_t hidden_code[CODE_SLOTS];
static guess_t guess[GUESS_SLOTS];
static int current_guess, current_slot;

static int guess_is_completed()
{
  int i;
  for (i = 0; i < CODE_SLOTS; i++) {
    if (guess[current_guess].code[i] == CODE_NONE)
      return 0;
  }
  return 1;
}

static int guess_is_correct()
{
  int i;
  for (i = 0; i < CODE_SLOTS; i++) {
    if (guess[current_guess].hint[i] != HINT_CODE_AND_POSITION)
      return 0;
  }
  return 1;
}

static void display_init(void)
{
  initscr();
  if (has_colors()) {
    start_color();
    init_pair(CODE_ALPHA,   COLOR_RED,     COLOR_BLACK);
    init_pair(CODE_BRAVO,   COLOR_GREEN,   COLOR_BLACK);
    init_pair(CODE_CHARLIE, COLOR_YELLOW,  COLOR_BLACK);
    init_pair(CODE_DELTA,   COLOR_BLUE,    COLOR_BLACK);
    init_pair(CODE_ECHO,    COLOR_MAGENTA, COLOR_BLACK);
    init_pair(CODE_FOXTROT, COLOR_CYAN,    COLOR_BLACK);
  }
  noecho();
  keypad(stdscr, TRUE);
}

static void display_update(char *end_msg)
{
  int i, j;

  erase();

  /* Draw area with hidden code. */
  mvaddstr(0, 0, "+---+---+---+---+");
  if (end_msg != NULL) {
    mvaddstr(1, 0, "|   |   |   |   |");
    for (i = 0; i < CODE_SLOTS; i++) {
      move(1, 2 + (i * 4));
      if (has_colors() && hidden_code[i] != CODE_NONE)
        attrset(A_BOLD | COLOR_PAIR(hidden_code[i]));
      addch(code_to_char[hidden_code[i]]);
      if (has_colors() && hidden_code[i] != CODE_NONE)
        attrset(A_NORMAL);
    }
    mvaddstr(1, 18, end_msg);
  } else {
    mvaddstr(1, 0, "|###|###|###|###|");
  }
  mvaddstr(2, 0, "+---+---+---+---+----+");

  /* Draw area with user guesses. */
  for (i = 0; i < GUESS_SLOTS; i++) {
    mvaddstr(3 + (i * 2), 0, "|   |   |   |   |    |");

    for (j = 0; j < CODE_SLOTS; j++) {
      move(3 + (i * 2), 2 + (j * 4));
      if (has_colors() && guess[i].code[j] != CODE_NONE)
        attrset(A_BOLD | COLOR_PAIR(guess[i].code[j]));
      addch(code_to_char[guess[i].code[j]]);
      if (has_colors() && guess[i].code[j] != CODE_NONE)
        attrset(A_NORMAL);
    }

    for (j = 0; j < CODE_SLOTS; j++) {
      move(3 + (i * 2), 17 + j);
      if (has_colors() && guess[i].hint[j] != HINT_NONE)
        attrset(A_BOLD);
      addch(hint_to_char[guess[i].hint[j]]);
      if (has_colors() && guess[i].hint[j] != HINT_NONE)
        attrset(A_NORMAL);
    }

    mvaddstr(4 + (i * 2), 0, "+---+---+---+---+----+");
  }

  /* Place cursor on current slot to select. */
  move(3 + (current_guess * 2), 2 + (current_slot * 4));

  refresh();
}

static void evaluate_and_give_hints(void)
{
  int i, j, code_only = 0, code_and_pos = 0;
  int guess_covered[CODE_SLOTS], hidden_covered[CODE_SLOTS];

  /* Check for correct code and position. */
  for (i = 0; i < CODE_SLOTS; i++) {
    if (guess[current_guess].code[i] == hidden_code[i]) {
      code_and_pos++;
      guess_covered[i] = 1;
      hidden_covered[i] = 1;
    } else {
      guess_covered[i] = 0;
      hidden_covered[i] = 0;
    }
  }

  /* Check for any remaining correct codes. */
  for (i = 0; i < CODE_SLOTS; i++) {
    if (guess_covered[i])
      continue;
    for (j = 0; j < CODE_SLOTS; j++) {
      if (hidden_covered[j])
        continue;
      if (guess[current_guess].code[i] == hidden_code[j]) {
        hidden_covered[j] = 1;
        code_only++;
        break;
      }
    }
  }

  i = 0;
  while (code_and_pos-- > 0)
    guess[current_guess].hint[i++] = HINT_CODE_AND_POSITION;
  while (code_only-- > 0)
    guess[current_guess].hint[i++] = HINT_CODE_ONLY;
}

static void hidden_code_randomize(void)
{
  int i;
  srandom(time(NULL));
  for (i = 0; i < CODE_SLOTS; i++)
    hidden_code[i] = (random() % CODE_FOXTROT) + 1;
}

int main(void)
{
  int user_done, user_quit;

  memset(guess, 0, sizeof(guess_t) * GUESS_SLOTS);
  display_init();
  hidden_code_randomize();

  for (current_guess = GUESS_SLOTS - 1;
       current_guess >= 0;
       current_guess--) {

    /* Wait for user to make a valid guess. */
    current_slot = user_done = user_quit = 0;
    while ((! user_done) && (! user_quit)) {
      display_update(NULL);

      switch (getch()) {
      case KEY_LEFT:
        current_slot--;
        if (current_slot < 0)
          current_slot = 0;
        break;

      case KEY_RIGHT:
        current_slot++;
        if (current_slot >= CODE_SLOTS)
          current_slot = CODE_SLOTS - 1;
        break;

      case KEY_UP:
        guess[current_guess].code[current_slot]++;
        if (guess[current_guess].code[current_slot] > CODE_FOXTROT)
          guess[current_guess].code[current_slot] = CODE_ALPHA;
        break;

      case KEY_DOWN:
        guess[current_guess].code[current_slot]--;
        if (guess[current_guess].code[current_slot] == CODE_NONE ||
            guess[current_guess].code[current_slot] > CODE_FOXTROT)
          guess[current_guess].code[current_slot] = CODE_FOXTROT;
        break;

      case KEY_ENTER:
      case '\n':
      case ' ':
        if (guess_is_completed())
          user_done = 1;
        break;

      case 'q':
      case 'Q':
      case '\e':
        user_quit = 1;
        break;

      default:
        break;
      }
    }

    if (user_quit)
      break;

    evaluate_and_give_hints();

    if (guess_is_correct())
      break;
  }

  if (guess_is_correct()) {
    display_update("Congratulations!");
  } else {
    if (user_quit) {
      display_update("Aborted.");
    } else {
      display_update("Too late!");
    }
  }

  if (! user_quit)
    getch(); /* Press any key... */

  endwin();
  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 03/09-2011, Article Link

Curses Invaders

Following the tradition of curses-based games, I have now created another clone of a game popular in the 70's and 80's.

Here is a screenshot:

Screenshot of invaders in curses.


I have realeased the source code under a MIT License here. Also, for convenience, I have compiled (with MinGW and PDCurses) a Windows binary that can be downloaded here.

Topic: Open Source, by Kjetil @ 01/08-2011, Article Link

Plaintext Steganography

The most common form of Steganography is to hide messages inside images, but what about hiding messages inside (plaintext) messages? This is the question I asked myself before creating this set of programs to do exactly that.

There are several ways to do this, but the approach I used was to utilize the whitespace (spaces and newlines) to hide the message. Basically, the amount of words (odd or even) on a line will indicate if the hidden bit is a one or zero. This works on all kind of text, but unfortunately requires a lot of it to hide even the smallest amounts of data. The only practical usage I can think of right now, is to hide passwords.

Here is the encoder:

#include <stdio.h>

#define WRAP 80

static int getbit(FILE *fh)
{
  static int x = 7;
  static int c = '\0';

  if (x == -1)
    x = 7;
  if (x == 7) {
    c = fgetc(fh);
    if (c == EOF)
      return -1;
  }

  return (c >> x--) & 0x1;
}

int main(int argc, char *argv[])
{
  int c, n, spaces, bit, index, had_space, new_index;
  unsigned char buffer[WRAP];
  FILE *fh;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s <text file>\n", argv[0]);
    return 1;
  }

  fh = fopen(argv[1], "r");
  if (fh == NULL) {
    fprintf(stderr, "Error: Unable to open text file for reading.\n");
    return 1;
  }

  n = spaces = index = had_space = 0;
  while (1) {
    bit = getbit(stdin);

    while ((c = fgetc(fh)) != EOF) {
      if (c == '\n' || c == '\r' || c == '\t')
        c = ' '; /* Convert. */

      if (c == ' ') {
        if (had_space)
          continue;
        had_space = 1;
        spaces++;
        if (bit == -1) {
          if ((spaces % 2) == 0) /* Pad output with zeros. */
            index = n;
        } else {
          if ((spaces % 2) == bit)
            index = n;
        }
      } else {
        had_space = 0;
      }

      buffer[n] = c;
      n++;

      if (n >= WRAP) {
        if (index == 0) {
          fprintf(stderr, "Error: Words in text are too large.\n");
          fclose(fh);
          return 1;
        }

        for (n = 0; n < index; n++) {
          fputc(buffer[n], stdout);
        }
        fputc('\n', stdout);

        spaces = new_index = had_space = 0;
        index++;
        for (n = index; n < WRAP; n++) {
          buffer[n - index] = buffer[n];
          if (buffer[n] == ' ') {
            spaces++;
            new_index = n;
          }
        }
        n = WRAP - index;
        index = new_index;

        break;
      }
    }

    if (feof(fh)) {
      fclose(fh);
      if (bit == -1) {
        return 0;
      } else {
        fprintf(stderr, "Error: Not enough text to cover data.\n");
        return 1;
      }
    }
  }

  return 0;
}
          


And here is the decoder:

#include <stdio.h>

static int power_of_two(int n)
{
  if (n == 0)
    return 1;
  else
    return 2 * power_of_two(n - 1);
}

int main(int argc, char *argv[])
{
  int n, c, spaces, value;

  n = 7;
  spaces = value = 0;
  while ((c = fgetc(stdin)) != EOF) {
    if (c == '\n') {
      if ((spaces % 2) == 0)
        value += power_of_two(n);
      n--;

      if (n < 0) {
        fputc(value, stdout);
        value = 0;
        n = 7;
      }

      spaces = 0;
    } else if (c == ' ') {
      spaces++;
    }
  }

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 17/07-2011, Article Link

DVD Batch Copying

I needed to copy a lot of files from a lot of DVDs over to a local hard-drive. In order to help with this, I made a shell script to semi-automate the process. This script will eject the tray, wait for the user to press enter, then close the tray and start copying files. After it is done copying, the process loops, asking for the next DVD to be put in the tray.

Here it is, hope you can make use of it:

#!/bin/bash

function abort {
  echo "Aborting..."
  eject -t /dev/cdrom
  exit
}

trap abort SIGINT

# Find first available directory.
DVD_NO=1
while [ -d "./dvd_$DVD_NO" ]; do
  DVD_NO=`expr $DVD_NO + 1`
done

while /bin/true; do
  eject /dev/cdrom

  # Wait for user to press enter.
  while /bin/true; do
    echo -n "Feed me! "
    read -n 1 CHAR
    if [ "$CHAR" == "" ]; then
      break
    fi
  done

  eject -t /dev/cdrom
  echo "Reading DVD #$DVD_NO..."
  sleep 30
  mkdir -v "./dvd_$DVD_NO" || exit 1
  mount /mnt/cdrom || exit 2

  cp -v -R /mnt/cdrom/* "./dvd_$DVD_NO"
  find "./dvd_$DVD_NO" -type d -exec chmod 755 {} \;
  find "./dvd_$DVD_NO" -type f -exec chmod 644 {} \;

  sleep 3
  umount /mnt/cdrom
  sleep 3
  DVD_NO=`expr $DVD_NO + 1`
done
          


Topic: Scripts and Code, by Kjetil @ 12/06-2011, Article Link

CRLF to LF Converter

This is a really simple program, but it's always missing on the Windows platform, where it is needed the most. Because of this, I will include both the source code and a Windows binary here for easy access.

Notice that the program does not use standard in/out; this has to do with the strange Windows behaviour on binary files. Instead, the files must be opened directly using "binary mode".

Here's the binary (compiled with MinGW) and here's the source:

#include <stdio.h>

int main(int argc, char *argv[])
{
  int c, last;
  FILE *in, *out;

  if (argc != 3) {
    fprintf(stderr, "Usage: %s <infile> <outfile>\n", argv[0]);
    return 1;
  }

  in = fopen(argv[1], "rb");
  if (in == NULL) {
    fprintf(stderr, "Error: Cannot open input file.\n");
    return 1;
  }

  out = fopen(argv[2], "wb");
  if (out == NULL) {
    fprintf(stderr, "Error: Cannot open output file.\n");
    fclose(in);
    return 1;
  }

  last = -1;
  while ((c = fgetc(in)) != EOF) {
    if (c == '\n' && last == '\r') {
      fputc(c, out);
      last = -1;
    } else {
      if (last != -1)
        fputc(last, out);
      last = c;
    }
  }
  if (last != -1)
    fputc(last, out);

  fclose(in);
  fclose(out);

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 07/05-2011, Article Link

Motivational Poster Generator

Here is a short program that will take an input image, combine it with some input text and produce a motivational poster. The size and aspect of the original input image will be kept, and the frames, text and everything else will be scaled according to it. Unfortunately, text is not automatically wrapped, so this must be handled by the caller if using multiple lines.

The program is written in C and uses the GD library to produce the graphics. This library is already available on most Unix platforms by default. There are some hard-coded paths to font files that may need to be adjusted on some platforms. (I tested this on Slackware 12.2.)

Here's an example of an output motivational poster:

An example output image.


And here is the code to make it happen:

#include <gd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define UPPER_LOWER_MARGIN_FACTOR 0.12
#define LEFT_RIGHT_MARGIN_FACTOR 0.18
#define TITLE_FONT_SIZE_DIVISOR 16
#define MESSAGE_FONT_SIZE_DIVISOR 48
#define TEXT_PADDING_DIVISOR 50
#define FRAME_PADDING 3

#define TITLE_FONT_PATH "/usr/share/fonts/TTF/DejaVuSerif.ttf"
#define MESSAGE_FONT_PATH  "/usr/share/fonts/TTF/DejaVuSans.ttf"

#define PNG_MAGIC "\x89PNG"
#define JPEG_MAGIC "\xff\xd8"

int main(int argc, char *argv[])
{
  gdImagePtr poster, image;
  FILE *fh;
  char magic[4];
  int is_jpeg, white, black;
  int title_rect[8], message_rect[8];
  int upper_lower_margin, left_right_margin;
  int title_font_size, message_font_size;
  int poster_height, poster_width;
  int text_padding, text_height;
  int title_pos_x, title_pos_y, message_pos_x, message_pos_y;

  if (argc != 5) {
    fprintf(stderr,
      "Usage: %s <input image> <title> <message> <output image>\n", argv[0]);
    return 1;
  }

  fh = fopen(argv[1], "rb");
  if (fh == NULL) {
    fprintf(stderr, "%s: error: cannot open image file: %s\n",
      argv[0], strerror(errno));
    return 1;
  }

  fread(magic, sizeof(char), 4, fh);
  if (memcmp(magic, PNG_MAGIC, 4) == 0) {
    is_jpeg = 0;
  } else if (memcmp(magic, JPEG_MAGIC, 2) == 0) {
    is_jpeg = 1;
  } else {
    fprintf(stderr, "%s: error: image file must be in PNG or JPEG format\n",
      argv[0]);
    fclose(fh);
    return 1;
  }
    
  rewind(fh);
  if (is_jpeg) {
    image = gdImageCreateFromJpeg(fh);
  } else {
    image = gdImageCreateFromPng(fh);
  }
  fclose(fh);

  if (image == NULL) {
    fprintf(stderr, "%s: error: unable to parse image file\n", argv[0]);
    return 1;
  }

  upper_lower_margin = image->sy * UPPER_LOWER_MARGIN_FACTOR;
  left_right_margin  = image->sx * LEFT_RIGHT_MARGIN_FACTOR;
  title_font_size    = image->sx / TITLE_FONT_SIZE_DIVISOR;
  message_font_size  = image->sx / MESSAGE_FONT_SIZE_DIVISOR;

  gdImageStringFT(NULL, title_rect, white, TITLE_FONT_PATH,
    title_font_size, 0, 0, 0, argv[2]);
  gdImageStringFT(NULL, message_rect, white, MESSAGE_FONT_PATH,
    message_font_size, 0, 0, 0, argv[3]);

  text_padding = image->sy / TEXT_PADDING_DIVISOR;
  text_height = abs(title_rect[7]) + abs(message_rect[7]) + (text_padding * 3);
  poster_width = image->sx + (left_right_margin * 2);
  poster_height = image->sy + (upper_lower_margin * 2) + text_height;

  poster = gdImageCreateTrueColor(poster_width, poster_height);

  black = gdImageColorAllocate(poster, 0, 0, 0); /* First is also BG color. */
  white = gdImageColorAllocate(poster, 255, 255, 255);

  gdImageCopy(poster, image, left_right_margin, upper_lower_margin,
              0, 0, image->sx, image->sy);

  gdImageLine(poster,
              left_right_margin - (FRAME_PADDING + 1),
              upper_lower_margin - (FRAME_PADDING + 1),
              left_right_margin + image->sx + FRAME_PADDING,
              upper_lower_margin - (FRAME_PADDING + 1),
              white);

  gdImageLine(poster,
              left_right_margin - (FRAME_PADDING + 1),
              upper_lower_margin - (FRAME_PADDING + 1),
              left_right_margin - (FRAME_PADDING + 1),
              upper_lower_margin + image->sy + FRAME_PADDING,
              white); 

  gdImageLine(poster,
              left_right_margin - (FRAME_PADDING + 1),
              upper_lower_margin + image->sy + FRAME_PADDING,
              left_right_margin + image->sx + FRAME_PADDING,
              upper_lower_margin + image->sy + FRAME_PADDING,
              white); 

  gdImageLine(poster,
              left_right_margin + image->sx + FRAME_PADDING,
              upper_lower_margin - (FRAME_PADDING + 1),
              left_right_margin + image->sx + FRAME_PADDING,
              upper_lower_margin + image->sy + FRAME_PADDING,
              white); 

  title_pos_x   = (poster_width / 2) - (abs(title_rect[2]) / 2);
  message_pos_x = (poster_width / 2) - (abs(message_rect[2]) / 2);
  title_pos_y = upper_lower_margin + image->sy +
    text_padding + abs(title_rect[7]);
  message_pos_y = title_pos_y + (text_padding * 2) + abs(message_rect[7]);

  gdImageStringFT(poster, NULL, white, TITLE_FONT_PATH,
    title_font_size, 0, title_pos_x, title_pos_y, argv[2]);
  gdImageStringFT(poster, NULL, white, MESSAGE_FONT_PATH,
    message_font_size, 0, message_pos_x, message_pos_y, argv[3]);

  fh = fopen(argv[4], "wbx");

  if (fh == NULL) {
    fprintf(stderr, "%s: error: cannot open output file: %s\n",
      argv[0], strerror(errno));
    gdImageDestroy(image);
    gdImageDestroy(poster);
    return 1;
  }

  if (is_jpeg) {
    gdImageJpeg(poster, fh, -1);
  } else {
    gdImagePng(poster, fh);
  }

  fclose(fh);

  gdImageDestroy(image);
  gdImageDestroy(poster);

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 03/04-2011, Article Link

Python Instant Messaging

I looked around to find a simple way of setting up a private instant messaging server, but were unable to find anything easy. Instead, I just made a simple custom solution myself, with a server script and client script written in the Python programming language.

The finished solution turned out to be more like a conference/chat room (IRC with only one channel), but with some features found in instant messaging clients that I think are important. The idea is that there is one server, and then many clients connect to it and everyone will see everything anyone writes.

Here's a screenshot of the graphical Tk-based client:

PIMC client screenshot


I have tested the server with Python version 2.5.x and the client with versions 2.5.x and 2.7.x without issues. And since no special libraries are used, it is enough to just download the standard Python distribution, and start using the scripts.

The client and server is available under a MIT License, and can be downloaded here. Unfortunately, there is no documentation, but the scripts should be straightforward to use.

Topic: Open Source, by Kjetil @ 13/03-2011, Article Link

Snake in Curses

This is just something I whipped up in my spare time. Another example of an idea turned into software. And once again, the curses library is involved. I present to you, a real-time action-packed snake game. Collect "apples" and extend until you crash.

Take a look...:

Screenshot of snake in curses.


...and compile this code to play:

#include <stdlib.h>
#include <ncurses.h>
#include <time.h>
#include <unistd.h>

typedef enum {
  DIRECTION_UP,
  DIRECTION_DOWN,
  DIRECTION_LEFT,
  DIRECTION_RIGHT,
} direction_t;

typedef struct direction_queue_s {
  direction_t direction;
  struct direction_queue_s *next, *prev;
} direction_queue_t;

static direction_queue_t *queue_head = NULL, *queue_tail = NULL;
static int score;
static int pos_y;
static int pos_x;
static int apple_y;
static int apple_x;

static void queue_insert(direction_t direction, int remove_tail)
{
  direction_queue_t *new, *temp;

  new = (direction_queue_t *)malloc(sizeof(direction_queue_t));
  if (new == NULL)
    exit(1);
  new->direction = direction;

  new->next = NULL;
  if (queue_head != NULL)
    queue_head->next = new;
  new->prev = queue_head;
  queue_head = new;
  if (queue_tail == NULL)
    queue_tail = new;

  if (remove_tail && queue_tail != NULL) {
    temp = queue_tail;
    queue_tail = queue_tail->next;
    queue_tail->prev = NULL;
    free(temp);
  }
}

static void update_screen(void)
{
  int current_y, current_x;
  direction_queue_t *current;

  erase();

  box(stdscr, '|', '-');

  current_y = pos_y;
  current_x = pos_x;
  for (current = queue_head; current != NULL; current = current->prev) {
    mvaddch(current_y, current_x, '#');
    switch (current->direction) {
    case DIRECTION_UP:
      current_y++;
      break;
    case DIRECTION_DOWN:
      current_y--;
      break;
    case DIRECTION_LEFT:
      current_x++;
      break;
    case DIRECTION_RIGHT:
      current_x--;
      break;
    }
  }

  mvaddch(apple_y, apple_x, '@');

  move(pos_y, pos_x);

  refresh();
}

static void place_apple(void)
{
  int maxx, maxy;
  getmaxyx(stdscr, maxy, maxx);
  do {
    move((random() % (maxy - 2)) + 1, (random() % (maxx - 2)) + 1); 
  } while (inch() != ' ');
  getyx(stdscr, apple_y, apple_x);
}

static void exit_handler(void)
{
  direction_queue_t *current, *last = NULL;

  if (queue_tail != NULL) {
    for (current = queue_tail; current != NULL; current = current->next) {
      if (last != NULL)
        free(last);
      last = current->prev;
    }
    if (last != NULL)
      free(last);
  }

  endwin();
  printf("\nScore: %d\n", score);
}

int main(void)
{
  int c, maxy, maxx;
  direction_t current_direction, original_direction;

  /* Initialize curses. */
  initscr();
  atexit(exit_handler);
  noecho();
  keypad(stdscr, TRUE);
  timeout(0); /* Non-blocking mode */

  getmaxyx(stdscr, maxy, maxx);
  srandom(time(NULL));

  /* Set initial variables. */
  current_direction = DIRECTION_RIGHT;
  queue_insert(DIRECTION_RIGHT, 0);
  queue_insert(DIRECTION_RIGHT, 0);
  queue_insert(DIRECTION_RIGHT, 0);
  score = 0;
  pos_y = maxy / 2;
  pos_x = maxx / 2;
  place_apple();

  update_screen();

  while (1) {

    /* Read character buffer until empty. */
    original_direction = current_direction;
    while ((c = getch()) != ERR) {
      switch (c) {
      case KEY_RESIZE:
        return 0; /* Not allowed! */
      case KEY_UP:
        if (original_direction != DIRECTION_DOWN)
          current_direction = DIRECTION_UP;
        break;
      case KEY_DOWN:
        if (original_direction != DIRECTION_UP)
          current_direction = DIRECTION_DOWN;
        break;
      case KEY_LEFT:
        if (original_direction != DIRECTION_RIGHT)
          current_direction = DIRECTION_LEFT;
        break;
      case KEY_RIGHT:
        if (original_direction != DIRECTION_LEFT)
          current_direction = DIRECTION_RIGHT;
        break;
      case 'q':
      case 'Q':
      case '\e':
        exit(0);
      }
    }

    /* Move player. */
    switch (current_direction) {
    case DIRECTION_UP:
      pos_y--;
      break;
    case DIRECTION_DOWN:
      pos_y++;
      break;
    case DIRECTION_LEFT:
      pos_x--;
      break;
    case DIRECTION_RIGHT:
      pos_x++;
      break;
    }

    /* Check for collisions. */
    c = mvinch(pos_y, pos_x);
    if (c == ' ') { /* Normal progression. */
      queue_insert(current_direction, 1);
    } else if (c == '@') { /* Expand. */
      queue_insert(current_direction, 0);
      score++;
      place_apple();
    } else { /* Died. */
      flash();
      exit(0);
    }

    /* Redraw screen. */
    update_screen();
    usleep(75000);
  }

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 01/02-2011, Article Link

Simple Curses Menu

Here is another one of those applications that fill a small niche. This application will simply present the user with a list, where one item is selected. The list itself is read from a file, and the selection (line number from the file) is returned as the exit code from the application itself. Based on this information, it is clear that the application is meant to be used in coordination with a shell script. The application uses the (n)curses library to present the "graphics", and also features a fancy scrollbar!

Here's a screenshot...:

Simple curses menu screenshot


...and here's the code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
#include <signal.h>

#define ENTRY_LIMIT 255 /* Same as limit for exit code. */
#define TEXT_LIMIT 128

static char list[ENTRY_LIMIT][TEXT_LIMIT];
static int list_size      = 0;
static int scroll_offset  = 0;
static int selected_entry = 0;

static void update_screen(void)
{
  int n, i, maxy, maxx;
  int scrollbar_size, scrollbar_pos;

  getmaxyx(stdscr, maxy, maxx);
  erase();

  /* Draw text lines. */
  for (n = 0; n < maxy; n++) {
    if ((n + scroll_offset) >= list_size)
      break;

    if (n == (selected_entry - scroll_offset)) {
      attron(A_REVERSE);
      mvaddstr(n, 0, list[n + scroll_offset]);
      for (i = strlen(list[n + scroll_offset]); i < maxx - 2; i++)
        mvaddch(n, i, ' ');
      attroff(A_REVERSE);
    } else {
      mvaddstr(n, 0, list[n + scroll_offset]);
    }
  }

  /* Draw scrollbar. */
  if (list_size <= maxy)
    scrollbar_size = maxy;
  else
    scrollbar_size = maxy / (list_size / (double)maxy);

  scrollbar_pos = selected_entry / (double)list_size * (maxy - scrollbar_size);
  attron(A_REVERSE);
  for (i = 0; i <= scrollbar_size; i++)
    mvaddch(i + scrollbar_pos, maxx - 1, ' ');
  attroff(A_REVERSE);

  mvvline(0, maxx - 2, 0, maxy);

  /* Place cursor at end of selected line. */
  move(selected_entry - scroll_offset, maxx - 3);
}

static void exit_handler(void)
{
  endwin();
}

static void winch_handler(void)
{
  endwin(); /* To get new window limits. */
  update_screen();
  flushinp();
  keypad(stdscr, TRUE);
}

static void interrupt_handler(int signo)
{
  exit(0); /* Exit with code 0, always. */
}

int main(int argc, char *argv[])
{
  int c, maxy, maxx;
  char line[TEXT_LIMIT], *p;
  FILE *fh;

  if (argc != 2) {
    printf("Usage: %s <file with menu lines>\n", argv[0]);
    return 0;
  }

  fh = fopen(argv[1], "r");
  if (fh == NULL) {
    printf("Error: Unable to open menu file for reading.\n");
    return 0;
  }

  while (fgets(line, TEXT_LIMIT, fh) != NULL) {
    for (p = line; *p != '\0'; p++) {
      if ((*p == '\r') || (*p == '\n')) {
        *p = '\0';
        break;
      }
    }
    strncpy(list[list_size], line, TEXT_LIMIT);
    list_size++;
    if (list_size == ENTRY_LIMIT)
      break;
  }
  fclose(fh);

  signal(SIGINT, interrupt_handler);

  initscr();
  atexit(exit_handler);
  noecho();
  keypad(stdscr, TRUE);

  while (1) {
    update_screen();
    getmaxyx(stdscr, maxy, maxx);
    c = getch();

    switch (c) {
    case KEY_RESIZE:
      /* Use this event instead of SIGWINCH for better portability. */
      winch_handler();
      break;

    case KEY_UP:
      selected_entry--;
      if (selected_entry < 0)
        selected_entry++;
      if (scroll_offset > selected_entry) {
        scroll_offset--;
        if (scroll_offset < 0)
          scroll_offset = 0;
      }
      break;

    case KEY_DOWN:
      selected_entry++;
      if (selected_entry >= list_size)
        selected_entry--;
      if (selected_entry > maxy - 1) {
        scroll_offset++;
        if (scroll_offset > selected_entry - maxy + 1)
          scroll_offset--;
      }
      break;

    case KEY_NPAGE:
      scroll_offset += maxy / 2;
      while (maxy + scroll_offset > list_size)
        scroll_offset--;
      if (scroll_offset < 0)
        scroll_offset = 0;
      if (selected_entry < scroll_offset)
        selected_entry = scroll_offset;
      break;

    case KEY_PPAGE:
      scroll_offset -= maxy / 2;
      if (scroll_offset < 0)
        scroll_offset = 0;
      if (selected_entry > maxy + scroll_offset - 1)
        selected_entry = maxy + scroll_offset - 1;
      break;

    case KEY_ENTER:
    case '\n':
    case '\r':
      /* Need to start at 1 to differentiate between a valid selection
         and other kinds of exits, that returns 0. */
      return selected_entry + 1;

    case '\e': /* Escape */
    case 'Q':
    case 'q':
      return 0;
    }

  }

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 15/01-2011, Article Link