Backup system using Samba

It is well known that Windows owns the desktop/laptop market in personal computing. Despite I don’t have anything against Windows, this situation is not precisely to my liking because it would be easier to manage a network of workstations if they would be running Linux instead of Windows.

I don’t want to get any deeper with my operating systems preferences because it would probably lead into some sort of awkward post explaining my point of view or the philosophical reason of my preference to Linux so, we should probably just stick with the code :D.

The problem I’m facing looks like this:

1. There is a network of Windows computers.
2. There is a Linux machine serving as Internet gateway and some other services.
3. They needed a backup system which stores data outside they workstations.
4. They needed to have privacy (password protected).
5. They demanded it to be quick (“I want it know”).

If the computers were running Linux it would have taken me about 5 minutes to configure sshfs (user space filesystem) but since Windows is in scene I needed an easy way to integrate a backup system that seamlessly attach itself to the Windows user interface and, what’s better than SMB/CIFS file sharing for this case?.

To achieve this I used SAMBA following the steps below (supposing that you already installed Samba).

1. Determine how many users will be using the backup system and create a Linux user for every single one of them if they don’t exists.

[bash]
# useradd lorena -m -c “Lorena Algo”
[/bash]

2. Create Samba users/passwords for the users you just created. In this case, the password changing program will also create the user if the option “-a” is specified.

[bash]
# smbpasswd -a lorena
[/bash]

3. Configure smb.conf file to share the user’s home directory or another directory you want. In my case, there is three users configured: Lorena, Eve and Ada.

[bash]
[global]
workgroup = WORKGROUP
passdb backend = tdbsam
security = user

[lorena]
path = /home/lorena
guest ok = No
valid users = lorena
read only = No

[eve]
path = /home/eve/
guest ok = No
valid users = eve
read only = No

[ada]
path = /home/ada/
guest ok = No
valid users = ada
read only = No
[/bash]

4. Start or restart smbd deamon.

[bash]
# /etc/init.d/smb restart
[/bash]

Give it a try. If everything were configured correctly, every user will only have access to their home directory and nowhere else. It is simple to do but there is a serious problem: what if the user wants to change the password of its account?, there is no way to do it with this method.

A PHP web script is a good approach to change passwords from a web page but since web pages are served by Apache normally with a non-root account there would be a problem calling smbpasswd program from the script. Sudo is the answer for this and with exactly one line we found the solution.

[bash]
# visudo
[/bash]

and append this line (wwwrun is the Apache user, you should change it with the one you are using):

[bash]
wwwrun ALL=NOPASSWD:/usr/bin/smbpasswd
[/bash]

The front-end is a (horrible) HTML page with just a web form.

[html]

Username:
Current :

New Password:

Re-type Password:

[/html]

I know, it looks absolutely ugly but it makes my point :D.

And finally, the password logic changing is implemented here:
[php]
&1`;

return !(preg_match(‘/NT_STATUS_LOGON_FAILURE/’, $output));
}

function change_password($username, $newpassword)
{
$output = `(echo $newpassword; echo $newpassword) | /usr/bin/sudo /usr/bin/smbpasswd -U $username -s 2>&1`;

return !trim($output);
}

?>
[/php]

Aaaand that’s it. I hope it could be useful for you as it was for me.

Thank you for reading 🙂

Data serialization

I recently needed a reliable way to pass binary information between two processes through an IPC mechanism. One of the programs (P1) consumes data from a DB2 database which is kinda complex to work with when the code is written in C, so, I ended up looking for a way to remove this (unnecessary) complexity by writing a Perl (P2) script much more user friendly when comes to working with database connections.

The query was as simple as

SELECT int1, long1, chars FROM table

which means that a simple C structure would be enough to map the entire selected fields

typedef struct rem_data
{
        int i;
	long l;
	char sz[10];
} rem_data_t;

P1 works as a TCP/IP server listening at port 7007. P2 connects to that endpoint, executes the query and packs the resulting row as a binary data packet to finally send it through the opened socket. P1 deserializes the bytes received to build the structure rem_data.

P1 is possibly long but self-explanatory :).

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct rem_data
{
	int i;
	long l;
	char sz[10];
} rem_data_t;

typedef unsigned int _uint;
typedef void * (*dflt_func_ptr_t) (void *);

void *deserializer (void *param);
static void *listener(void *param);
pthread_t *ip_start_listener(_uint port, void * (*callback) (void *));

int main(int argc, char **argv)
{
      ip_start_listener(7007, deserializer);

      /*
       * OK, it's busy wait :D
       */ 
      for(;;);
}

void *deserializer (void *param)
{
	rem_data_t data;
	int offset		= 0;
	char *rdata		= (char *) param;

	memcpy(&data.i, &rdata[offset], sizeof(data.i));
	offset			+= sizeof(data.i);

	memcpy(&data.l, &rdata[offset], sizeof(data.l));
	offset			+= sizeof(data.l);

	memcpy(&data.sz, &rdata[offset], sizeof(data.sz));

	printf("data: i %dn", data.i);
	printf("data: l %dn", data.l);
	printf("data: sz %sn", data.sz);
}

pthread_t *ip_start_listener(_uint port, dflt_func_ptr_t callback)
{
	int sock_fd, opt = 1;
	struct sockaddr_in server;
	pthread_t *listener_thread	= NULL;

	if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
//		LOG(L_ERROR, "%s | socket(): %s", __FUNCTION__, strerror(errno));
		return NULL;
	}

	setsockopt (sock_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt));

	server.sin_family 	= AF_INET;
	server.sin_port 	= htons(port);
	server.sin_addr.s_addr 	= INADDR_ANY;

	bzero(&(server.sin_zero), 8);

	if(bind(sock_fd, (struct sockaddr*)&server, sizeof(struct sockaddr)) == -1)
	{
//		LOG(L_ERROR, "%s | bind(): %sn", __FUNCTION__, strerror(errno));
		return NULL;
	}

	if(listen(sock_fd, 1000) == -1)
	{
//		LOG(L_ERROR, "ip_start_listener.listen(): %sn", strerror(errno));
		return NULL;
	}

	listener_thread		= malloc(sizeof(pthread_t));

	if (pthread_create(listener_thread, NULL, listener, (void *) ( void*[] ) { (void*) &sock_fd, (void*) callback } ) != 0)
	{
//		LOG(L_ERROR, "%s | pthread_create(): %sn", __FUNCTION__, strerror(errno));
		return NULL;
	}

	return listener_thread;
}

static void *listener(void *param)
{
	struct sockaddr_in client;
	_uint sin_size, *params		=  param;
	int client_fd, *sock_fd 	= (int *) params[0];
	dflt_func_ptr_t callback	= (dflt_func_ptr_t) params[1];

	/*
	 *  no buffering for standard output
	 */
	setbuf(stdout, NULL);
	sin_size = sizeof(struct sockaddr_in);

	for(;;)
	{
		char buffer[1024];

		if ((client_fd = accept(*sock_fd, (struct sockaddr *) &client, &sin_size)) == -1)
		{
//		    LOG(L_ERROR, "%s | accept(): %sn", __FUNCTION__, strerror(errno));
		    continue;
		}

//		LOG(L_DEBUG, "Connection from %sn",  inet_ntoa(client.sin_addr));

		read(client_fd, buffer, sizeof(buffer));

		callback(buffer);

		close(client_fd);
	}

	return NULL;
}

Compile it with

$  gcc server.c -o server -lpthread

P2 is more than simple. In case you don’t know the pack() function, here is very well explained.

#!/usr/bin/perl

use warnings;
use strict;

use IO::Socket;


my $sock 	= IO::Socket::INET->new('localhost:7007');

# Assume that the data retrieving logic is implemented here
$sock->write(pack("i l Z11", 111, 999,'hola'));

$sock->close();

And then, we just run it.

$ ./server &
$ perl pack.pl

And P1’s output should be

data: i 111
data: l 999
data: sz hola

Garbage generator

When your teacher asks you to write a script that randomly mixes grammatical parts of a sentence because you didn’t did your homework is what causes this kind of post.

Actually there were more restrictions. I had to do it in less than 10 minutes, the resulting sentence needed to be understandable despite the possible genre errors and finally, the sentence should be generated in Spanish.

The script is made in Perl. It was easy to build and luckily took me less than 10 minutes to entirely write both the code and the feeders (the files with the words to build the sentence).

[perl]
#!/usr/bin/perl

use warnings;
use strict;

sub main()
{
my @articles = open_file($ARGV[0]);
my @subjects = open_file($ARGV[1]);
my @verbs = open_file($ARGV[2]);
my @complements = open_file($ARGV[3]);

my %rules = ( ‘a’ => @articles,
‘s’ => @subjects,
‘v’ => @verbs,
‘c’ => @complements);

my @expr = split(/+/, $ARGV[4]);

for my $token ( @expr )
{
my $index = int(rand(scalar(@{$rules{$token}})));

my $word = @{$rules{$token}}[$index];
$word =~ s/n+/ /g;
print $word;
}

print “n”;

}

sub open_file($)
{
my $file = shift;

open(FILE, “< $file") || die "Error: $!n"; return ;
}

&main();
[/perl]

The program receives 5 parameters from command line: the articles file, the subjects file, the verbs file, the complements file and the expression which determines how to build the sentences combining the words.

[bash]

$ perl random-sentence.pl articles subjects verbs complements “a+s+v+c”

[/bash]

The above call produces mostly nonsense and funny sentences like:

– el edificio duerme todo al pedo
– un alumno estudia como la mierda
– la puerta rie
– las perro habla todo al pedo
– una profesor come cualquier cosa

It was a funny thing to do. I don’t know if the script worth a post in my blog but here it is and I laughed a lot 😀