Friday, February 29, 2008

Setting Up Disk Groups In Veritas Volume Manager

Good day,

Today we're going to take a look at the very basics of getting started with Veritas Volume Manager. We're not going to limit ourselves to any specific operating system, as all of these commands work almost exactly the same on Unix and Linux. We'll be using RedHat disk identifiers, for brevity's sake, but it should be noted that wherever we list out a disk like "sda," you can substitute c0t0d0 (or what not) if you're using Solaris. Of course, if you're running RedHat, you're probably already using LVM, in which case VVM might not be worth the hefty asking price.

Tomorrow, we'll take a look at creating volumes with these disk groups , but for now, I thing we'll be chewing up sufficient HTML landscape just running down these basic commands ;) We'll be concentrating on the command line functionality in this small series of posts. You can do all of this stuff using the "vxdiskadm" command also, which is a tty gui that pretty much walks you through the steps very easily in plain English.

Our assumptions for today's exercise (and tomorrow's), are that you have 6 disks on your system of equal size, manufacturer, etc, and that you've already installed and (at least temporarily) licensed Veritas Volume Manager on the OS. Our only other obvious assumption will be that you may or may not have installed the Veritas File System. These commands should work equally well for vxfs, ufs, ext3, etc.

The first thing you'll want to do to set up your disk group(s) is to initialize all of your disks so that Volume Manager recognizes them. This is very simply done on the command line, like so, for our six disks (/dev/hda, /dev/hdb, /dev/hdc, /dev/hdd, /etc/hde, /dev/hdf):

host # /etc/vx/bin/vxdisksetup -if hda
host # /etc/vx/bin/vxdisksetup -if hdb
host # /etc/vx/bin/vxdisksetup -if hdc
host # /etc/vx/bin/vxdisksetup -if hdd
host # /etc/vx/bin/vxdisksetup -if hde
host # /etc/vx/bin/vxdisksetup -if hdf


Note: For all of these operations, you can check on the progress (if you run a command that takes a good while to complete) using the "vxtask" command!

It's generally good practice to include /etc/vx/bin in your PATH environment variable (even on Linux, for backward compatability's sake). At the very least, it will save your fingers from type-cramping ;)

The command line options we've added here, before the disk name declarations (which don't need to be fully qualified with /dev... ), are "-i" to initialize the disks (by writing a disk header instead of just creating a partition for VVM use) and "-f" to force the initialization (This is technically an option to the "vxdisk" command, but can be used with "vxdisksetup").

Now, to create your disk group, or groups, you just simply have to run the following. Let's say we want to create two disk groups (disk_group1 and disk_group2); the first with hda and hdb, and the second with the remaining disks we've initialized. We can take care of that by executing:

host # /usr/sbin/vxdg init disk_group1 disk_group1_01=hda disk_group1_2=hdb
host # /usr/sbin/vxdg init disk_group2 disk_group2_01=hdc disk_group2_2=hdd disk_group2_3=hde disk_group2_4=hdf


We'll also create a disk group , called disk_group3, consisting of all 6 disks (Of course, it's not recommended to have any single Veritas disk belong to more than one disk group, but we're going to do it here, just so we have a disk group that we can use in tomorrow's examples on setting up volume groups).

hosts # /usr/sbin/vxdg init disk_group3 disk_group3_01=hda disk_group3_2=hdb disk_group3_3=hdc disk_group3_4=hdd disk_group3_5=hde disk_group3_6=hdf

And we're all set. Now all six of our disks are initialized for use with Veritas Volume Manager and we've got them ordered into disk groups.

Tomorrow, we'll take a look at creating different types of volumes from the disks, and disk groups, that we've set up today.

Until then,


, Mike




Thursday, February 28, 2008

Finding and Reading Files In The Shell When Your System Is Hung

Howdy,

Today we're going to look at a situation that occurs probably more often than it should ;) If you've done your fair share of system administration on Linux or Unix, you've probably run into a predicament where you got paged, called, etc, to look at a machine that was hanging on the brink, only to find that (asssuming you could log in at all) it was so completely trashed that it couldn't even muster up the strength to run the simple system commands you needed to diagnose the problem before just giving up and rebooting.

Typical errors you'd get at the command line, in this sort of situation, would be similar to:

host # ls /tmp
Insufficient Memory!


or

host # cat file
Cannot fork new process!


Basically, you're stuck in a situation where you can't do anything that relies on executing anything other than the login shell that you're lucky you got in the first place ;)

The good news is that you can, quite possibly, get as much information as you need before rebooting by simply using your shell and it's built-ins. All of these examples should work for sh, ksh, bash, etc (Possibly not in csh, but, hopefully that's not your system default root shell). If you have a good idea what's wrong before your machine goes down entirely, they can even help you decide what you want to do before you boot the system back up (Check out this post for some simple tricks to figure out what commands are available to you in Solaris' PROM).

Here are the things I try to do when I find myself in that sort of situation (in no order of importance ;)

1. Move around the filesystem.

Luckily, the "cd" and "pwd" commands are built into the shell, so you can always move around your filesystem (even if you are, figuratively, in the dark) and get to the hot spots you want to check. For instance:

host # cd /var/log

will work just fine. This is the most obvious thing you can do, but cd (on its own) depends on you to know where you're going. You can't cd to a directory that doesn't exist even when things are up and running perfectly ;)

If you happen to get lost, you can figure out where you are, using the built-in "pwd," like so:

host # pwd
/var/log


2. Take a look at the contents of your filesystem.

This is actually pretty obvious, as well, once you realize how to do it. You won't be able to use "ls" any more, since that is a command that the shell invokes outside of itself, but you can always use the built-in "echo" command, like so:

host # echo *
bin opt sbin usr tmp var


Note that this output won't usually be so pretty. If there are 50 files and/or directories in the directory you're in, you'll just get 50 filenames in a row on one line. But, it's better than nothing :)

3. Capture the contents of critical files that can help you troubleshoot and/or find your root cause so this won't ever happen again (hopefully).

This last one is slightly less obvious, but can be done in a variety of ways. The first two ways are messy, but they work. Simply read your file as though you were sourcing it, using either the "source" or dot "." built-ins. The reason I don't prefer these two methods is that your screen fills up with a lot of garbage and you may hang your system by sourcing the contents of a file that contains executable statements or commands. For our example here, we'll assume a file named BOB with one line in it that says "hi":

host # source ./BOB
-bash: hi: command not found


or

host # . ./BOB
-bash: hi: command not found


You've gotten your output, but you can see where the potential problem would lie. What if "hi" was a command and your "source" directive tried to run it on your already half-dead machine? It might put it all the way down right then and there.

In these instances, I think output redirection is your best bet. You can read the contents of a file by simply executing a new file descriptor and reading from that with "echo." You can use pretty much any number that works (although, try to stay away from 0, 1 and 2 as these are your system's STDIN, STDOUT and STDERR file descriptors), you won't have to read your file through the clutter of a bunch of error messages and you will be insulating yourself from accidentally executing any commands. For instance, the following would get you much better results:

host # exec 7<BOB <--- Execute new file descriptor 7 and redirect the output of your BOB file to it.
host # while read -u7 line; do echo $line; done <--- Then, just read from the file descriptor.
hi

And that's about it. If you use all 3 of these methods in whatever combinations are necessary, you should be able to collect most of the information you need to assess your situation and/or provide root cause.

For one last example that uses all 3, this is how I would go about getting the contents of my /var/log/syslog file (I'll shorten the output to only include the relevant stuff) - Note that I'm also doing the syslog reads in a command line "for loop" because I want to get all the information I can with as little typing as possible:

host # pwd
/home/mydirectory
host # cd /var/log
host # echo *
sysylog syslog.1 syslog.2
host # for x in syslog.2 syslog.1 syslog; do exec 7<$x;while read -u7 line;do echo $line;done;done
blah....
<--- All of the sylog files' output. Notice that I did them in reverse, so that the output would be from oldest to newest. It's also a good idea (if possible) to either log your terminal session or set your terminal client's buffer to a very large number so that you can cut and paste this output into your desktop editor.

Hope this helps you out :)

Best wishes,


, Mike




Wednesday, February 27, 2008

Simplifying File Renaming Using Bash Without Sed

Hello again,

This is a little tip I picked up that I really like. It's not new; just something I never gave any real thought to. It has to do with renaming files (A common enough task) en masse and how to do that in as few keystrokes as possible. But that really puts a limitation on this trick that doesn't exist. It can be used for many other purposes.

I was in the bad habit (Well, it's not a bad habit, really, since it'll work on almost any distro of Linux or Unix) of typing extremely long command lines to change file names. For instance, if I had a directory full of script files that looked like this:

host # ls
script1 script2 script3


and I wanted to copy them all off to files with a .bak extension (In case I screwed up my edits on the originals), I would almost always type something like the following (Note that this command line is a perfectly acceptable way to rename your files if you can't do it the way I'm going to explain here):

host # ls -1 script*|while read name;do newname=`echo $name|sed 's/$/\.bak/'`;cp $name $newname;done

which would work perfectly well, and leave me with:

host #
script1 script1.bak script2 script2.bak script3 script3.bak


In Bash (on Linux or Unix) you can get around this with variable expansion. Like I said, especially in this instance, this may not seem like much, but it does save a lot of typing (and can be used to help you save time in a lot of different ways if you use your imagination :)

So, for purposes of demonstration, we'll put everything back the way it was:

host # rm *.bak
host # ls
script1 script2 script3


Now we're going to look at Bash's expansion operators and see how much easier they can make this whole process. The expansion operators are, basically, curly brackets - which can be nested - containing values. The two most important things to note about them are that:

1. The values must be separated by commas (e.g. {1,2,3})
2. The values can't contain any spaces between themselves and the commas - on either side - unless the values are quoted, like so (there can also be no space between the quotation marks and the comma separators):

Correct = {"1 "," 2 "," 3"}
Who Knows? = {1 , 2 , "3"}


So, now we can do the exact same thing we did above (add the .bak extension to our 3 script files), but do it a lot more cleanly and quickly - not to mention the fact that your input shouldn't bleed over a simple 80 column tty display any more ;)

host # ls -1 script*|while read name;do cp $name{,.bak};done
host # ls
script1 script1.bak script2 script2.bak script3 script3.bak


Here's an even shorter line, but it might cause you problems, given "for x"'s default behavior of reading each variable on a line as the value of x. This wouldn't work very well if you were trying to operate on files with spaces in their names (You can read more on that in our previous post on working with Windows generated file names):

host # for x in `ls -1 script*`;do cp $name{,.bak};done

Not only do Bash's built in variable expansion operators shorten my line, once you get used to using them they make it more easily readable. If you noted that my variable expansion, inside the curly brackets, was missing a value before the first comma, that was intentional. Although you can't have spaces between the values in your expansion set, you can have an empty value (no spaces). This way the first "append" that the variable expansion does is just like doing no append at all.

For example:

host # ls script{1,2}
script1 script2


And now, with a blank first variable (You can put your blank variable anywhere - more than once - but it makes the most sense to use it first in the "cp" command above since I want to copy from the original file name to the new name):

host # ls script{,2}
ls: script: No such file or directory
script2


Like I said, this little trick (well, technically not a trick, since it's built in to Bash ;) has saved me a lot of time and I hope you find it useful as well!

Cheers,

, Mike




Tuesday, February 26, 2008

Offlining, Failing Over And Switching in VCS

Hey there,

Today we're going to address a question that's asked commonly enough by foks who use Veritas Cluster Server: What's the difference between offlining, failing over and switching when dealing with service groups across multiple nodes? That doesn't seem like a very common question. I'm sure it's probably hardly ever phrased that way ;)

Anyway, to the point, all three options above are useful (hence your ability to avail yourself of them) and sufficiently distinct that you should be sure you're using the one you want to, depending on what ends you wish to achieve. All of this information is fairly general and should work on VCS for Linux as well as Unix.

We'll deal with them one bye one, outlining what they basically do, with a short example command line to demonstrate, where applicable. None of this stuff is hard to pick up and run with, as long as all the components of your VCS cluster are setup correctly. Occasionally, you may see errors if things aren't exactly perfect, like we noted in this post on recovering indefinitely hung VCS resources.

1. Offlining. The distinction to be made here, most plainly, is that, when you offline a resource or service group, you are "only" doing that. This differs from failover and switching in that the service group you offline with this particular option is not brought online anywhere else as a result. So, when you execute, for instance:

host # hagrp -offline YOUR_SERVICE_GROUP -sys host1

that service group, and its resources, generally, are taken offline on host1, and nothing else happens. If you're operating in an environment where systems don't run service groups concurrently (active/active), you will have effectively "shut down" that service group, and any services it provides, for the entire cluster.

2. Failing Over. This is more of a concept than any particular command. When you have your cluster setup to fail over, if a resource or service group, etc, goes offline on one node (host1, for instance) and it wasn't brought offline on purpose, VCS will naturally attempt to bring it online on the next available node (listed in your main.cf configuration file). Needless to say, if only one host is listed for a particular service group, its failure on one host will mean the failure of the entire service group. It also obviates the use of VCS in the first place ;)

3. Switching. This is what most folks want to do when they "offline" a service group, as in point 1. Although, since VCS automatically switches unexpectedly offlined resources on its own (when it's set up to), it's reasonable for someone new to the product to assume that offlining a service group would engage VCS in a switching activity. Unfortunately, this isn't the case. If you want to switch a service group from host1 to host2, for example, these would be the command options you'd want to give to hagrp:

host # hagrp -switch YOUR_SERVICE_GROUP -to host2 <--- Assuming you're running this from host1.

Hopefully this little guided mini-FAQ helped out with differentiating between the concepts. If you find the command line examples valuable, even better :)

Happy failing! ;) Over.


, Mike




Monday, February 25, 2008

Booting A Known-Good Solaris Disk On A Different Server Type

Hey There,

For the most part, if you work on Sun (or any other kind of) servers routinely, when really bad things happen (like system crashes, etc), part of the process of recovery may ultimately include parts replacement. At its worst, the process may include a complete replacement machine (This has actually happened to me many times, after 15 or 16 parts replacement visits ;)

Generally, this isn't an issue, as Sun will want to replace, for instance, a v490 with a new v490 and you can just pop your old operating system disks in there and boot right up.

Occassionally, however, you'll run into a situation where the server you need replaced is no longer supported. In these instances (and in instances where your company has just decided to upgrade its servers), you find yourself in a little bit of a pickle. Nothing terrible, but you can't just pop the old disks in the new box and boot up without issue (We'll assume, for this post, that the hardware, itself, is compatible. In a lot of instances, new machines have different physical connection interfaces, which adds another layer of complication to the issue, but I digress...)

So, now that you've got your new box (which has OS compatible parts, compatible hardware, etc) and your OS disk from your old Sun server, you have to address how to get that new box up and running with your old OS disk. A lot of times, this can be a life saver, especially if you keep peripheral data on your boot disk!

The good news is, you only need to do three things to get yourself back up and running and not have to hassle with re-installing your OS, copying back from the original disk and hoping you get everything you need :)

First, after inserting your hard drive in the new server, you'll need to either boot off of cdrom or the network (Check out this post on extended boot options for tips on doing that). Whatever media you do decide to boot off of, local or remote, it's very important that you boot from an image that's as close to the OS on your original disk as possible. In a perfect world, you'll have a CD or netboot image that is the exact release of the OS you have on your original disk. At the very least, it should be the same major version. As a for instance, trying to complete the following steps using a Solaris 10 image to get your Solaris 8 disk to boot will seem to work, but will ultimately result in disappointment.

At the "ok>" prompt (or PROM) level on your new box, type something similar to the following (Basically, boot into single user mode):

ok> boot cdrom -s

Second, you'll want to mount your old disk on the mini Solaris environment you end up in after booting into single user mode, like so:

host # mkdir /tmp/a <--- You'll probably have to make your mount point in /tmp as all the other filesystems should be read-only in single user mode. /mnt may also be available to mount your physical hard drive on.

host # mount /dev/dsk/c0t0d0s0 /tmp/a <--- Note that the exact disk device might be different. You'll know as soon as you try to mount it and run an "ls" on "/tmp/a" whether or not you've gotten the right one. You may even find out sooner if the system outputs errors like it should.

Thirdly, you'll need to execute two crucial commands. These will make the difference between your system booting to a bizarre error, and resultant crash, or a successful start up.

From the single user command prompt, type:

host # devfsadm -C -r /tmp/a -p /tmp/a/etc/path_to_inst <--- This command will rebuild the device tree on your boot disk and create a new /etc/path_to_inst file.

host # installboot /usr/platform/`uname -i`/lib/fs/ufs/bootblk /dev/rdsk/c0t0d0s0 <--- This will install a new boot block on your old hard drive. This is the one step where it really matters what version of the OS your boot CD is. As referenced above, if you try to install a Solaris 10 boot block on a Solaris 8 OS disk, the command will succeed, but the following boot attempt will fail!

Once you've completed these three steps, you should be all set to bring the machine back down to run level 0 and boot up successfully! In the odd case you may need to make sure STDIN, STDOUT and STDERR are setup correctly. A simple fix for this is to just symlink in your mounted root's dev directory:

host # cd /tmp/a/dev
host # ln -s fd/0 stdin
host # ln -s fd/1 stdout
host # ln -s fd/2 stderr


Then boot (you can skip the above step most of the time)

host # init 0
ok > boot


And all should be well again :)

Best wishes,


, Mike




Sunday, February 24, 2008

Creating Your Own Secret Webserver Log With CGI

Howdy,

This is a little trick I like to use every once in a while, if I need to debug visits and don't want to mess with the "official" log files that everyone else looks at and/or may depend upon to do their jobs correctly. The last thing I want to do is cause problems for other people. ...Well, it's pretty close to the end of my list ;) This script (Both the HTML and CGI) should run on any Linux or Unix system. The HTML portion should also run on pretty much any webserver.

The Perl CGI script is very simple, so I've included both the HTML (which you should nest or include in the page you want to track) and the backend CGI script that will record the information for you. The only important things to note are that, in your HTML, you want to set the form variable type to "hidden" (You'll only be passing one variable, which will be bogus, since, in this case, you're just interested in getting environment variables that are normally passed by the POST method). Your CGI output should also be somewhere secure enough that not just anyone can get to it, but unsecure enough so that the user your webserver runs as can actually write to it.

In a worst case scenario here, a malicious user can write bogus data to your file. Putting the file in a separate location is my way of hedging my bets in case there's something else a malicious user can do that I haven't thought of. If they destroy my personal file, I won't have to hear about it from anyone else :)

Without further ado, the HTML and the CGI march on!

Cheers,


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

HTML PORTION

<form name="form" method="post" action="loggen.pl">
<input type="hidden" name="bogus" value="0">
</form>


CGI SCRIPT

#!/usr/bin/perl

#
# loggen.pl
# generate your own secret log file
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#

print "Content-type: text/plain\n\n";
$logfile = "/wherever/you/want/to/put/your/log_file";
chomp($date = `date +%m%d%y`);
open (LOG, ">>$logfile");
print LOG "$date|";
print LOG "$ENV{'REMOTE_HOST'}|";
print LOG "$ENV{'HTTP_USER_AGENT'}|";
print LOG "$ENV{'DOCUMENT_URI'}|";
print LOG "$ENV{'HTTP_REFERER'}\n";
close (LOG);
exit;


, Mike




Saturday, February 23, 2008

Simple CGI AutoResponder Form

Hello again,

Today, we're going to hit up a fairly common CGI form technique used by pretty much every site on the internet today. At least, every e-commerce or interactive site. It's become expected, nowadays, that when you place an order for this or that, or you sign up for a message board, etc, that you'll be receiving an email to confirm this fact in a matter of minutes, if not seconds.

Our form below is a simple Perl CGI script that accepts input and reacts to it, by sending out an email to the person (or robot ;) requesting the information, and alerting us that we have a new mailing list member. It's somewhat similar, although completely different in function, to our previous post on processing and collecting emails from a CGI form. That script was functionally sound, but lacked the "reaction" that this script incorporates.

Like I said, this is a fairly bare-bones script (No limitations on Unix or Linux flavors it'll run on, as long as they can run Perl), so we're not getting deep into error checking or backend database operations, shopping carts, etc. Just a simple give and take (or take and give, depending on how you look at it ;). The user is going to input some requested data, and we're going to send them confirmation that we received that data (as well as a notification to ourselves). This CGI form assumes a frontend that passes it the form variables listed as $form{'VARIABLE_NAME'} throughout.

Here it is. Enjoy it and, hopefully, find some use for it within your larger applications :)


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/usr/bin/perl

#
# JoinMailList.pl
# Simple Autoresponder Example
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#

print "Content-type: text/html\n\n";
if ($ENV{'REQUEST_METHOD'} ne 'POST')
{
print <<"HTML";
<html><head><title>Whoops!</title></head>
<body><h1>Please Only Use the POST Method with this script!</h1>
</body></html>
HTML
exit;
}
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
@pairs = split(/&/, $buffer);
foreach $pair (@pairs)
{
($name, $value) = split(/=/, $pair);
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$form{$name} = $value;
}
chomp($date = `date`);
open(MAIL, "|/usr/lib/sendmail -t") || die "Can't open mailer!";
print MAIL "To: $form{'email'}\n";
print MAIL "From: OURCOMPANY\@OURSITE.COM\n";
print MAIL "Subject: Thanks For Joining Our Mailing List\n\n";
print MAIL <<"EOA";
Hey There,
Thanks a million for joining our mailing list. We will send you your first
email as soon as possible!
EOA
close(MAIL);
open(MAILER, "|/usr/lib/sendmail -t") || die "Can't open mailer!";
print MAILER "To: OURCOMPANY\@OURDOMAIN.COM\n";
print MAILER "From: newusers\@OURDOMAIN.COM\n";
print MAILER "Subject: Someone Just Joined Our Mailing List!\n\n";
print MAILER <<"EOB";
On $date, $form{'fname'} $form{'lname'} decided to join our little mailing list.
Yeehah
EOB
print MAILER <<"EOC";
$form{'fname'}\'s personal info:
$form{'fname'} $form{'lname'}
$form{'email'}
$form{'street'}
$form{'city'}, $form{'state'} $form{'zip'}
EOC
print MAILER "Please add $form{'fname'} to our list ASAP!\n";
print MAILER "Maybe we can finally sell something!\n";
close(MAILER);
print <<"HTML";
<html><head><title>Thanks for signing up!</title></head>
<body><h1> Thank you for joining our mailing list! </h1>
<b>$form{'fname'}</b>!,
Welcome to our mailing list. You should be receiving a confirmation email shortly!
The Mgmt.
</body></html>
HTML
exit;


, Mike




Friday, February 22, 2008

Simple SCP Utility For Network Wide File Distribution

Hey again,

This little shell script is a somewhat follow-up to an older post we did on creating an SSH command line runner. It's basically the same concept, except this time we're looking specifically at copying files all over the network, using SCP, rather than executing commands all over the network using SSH (One might argue that, under the covers, they're the same thing. But one might also argue that each has an advantage over the other, depending on what it's being used for and there's no argument that neither takes the exact same command line options :)

One other thing to note, before you begin using this script, is that it assumes that you have passwordless key-based SSH authentication set up on all the hosts you're going to be copying to. If you haven't got that set up already, check out our post on setting up your SSH keys network wide quickly and easily.

This script is fairly simple to run and should work on most flavors of Linux and Unix. If any modification is necessary it should be minor, and center around the line where we actually invoke SCP. It takes two arguments (where we're copying from and where we're copying to) and can be run simply, from the command line, like so:

host # ./scopy /users/bob/file /users/bob/file

Of course, you may note that the above example is incredibly specific. You don't need to include the directory if you don't want to (I just usually do so to be sure things end up where they're supposed to). You could easily do this, as well (assuming that your home directory isn't in the same location on every box on your network):

host # ./scopy /users/bob/file ~/bob/file

or any number of variations. Have fun with it :) The only thing it looks for outside of itself, is a host file, which I've cleverly named "hostfile" ;) This is completely arbitrary and can be changed, as well. The format I've chosen is "name : IP Address," like this:

host1 : 192.168.0.10

with one entry per line. Again, with some simple modification, this can be changed easily to suit your taste.

Hope you enjoy it and it helps you out some :)

Cheers,


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/bin/ksh

#
# scopy - scp files from one location to
# all of your network hosts
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#

trap 'rm tmpfile.$pid;echo "Caught Signal. Cleaning Up And Quitting...";exit 3"' 1 2 3 9 15

pid=$$

if [ $# -ne 2 ]
then
echo "Usage: $0 fromDirFile ToDirFile"
exit 1
fi

if [ -f hostfile ]
then
hostfile="hostfile"
elif [ -f /users/bob/data/hostfile ]
then
hostfile="/users/bob/data/hostfile"
else
echo "Can't find hostfile. No hosts to copy to. Out..."
exit 2
fi

from=$1
to=$2
squashed_command=`echo $1 $2|/bin/sed -e 's/ * *//g' -e 's/|//' -e 's/\///g'`

print "" >>tmpfile.$pid
print "Report Output for \"scp $1 $2\"" >>tmpfile.$pid
print "______________________________" >>tmpfile.$pid
print "" >>tmpfile.$pid

cat $hostfile|while read name colon address
do
if [ $name == "#" ]
then
:
else
print "\nCopying \"$1 to $2\" on \c" >>tmpfile.$pid
print "$name... " >>tmpfile.$pid
print "$name... \"scp $1 $2\"... "
/usr/bin/scp -r $1 ${address}:$2 2>/dev/null|tee -a tmpfile.$pid
fi
done

mv tmpfile.$pid OUTPUT.${squashed_command}.$pid


, Mike




Thursday, February 21, 2008

Generating Encrypted Strings For Password Restoration

Greetings,

This is a little bit of a twist on an old post (one of many, actually) that we did on password cracking in Linux and Unix using Perl.

In the previous entry in our ongoing series of randomly connected password hacking posts, and pretty much every other one of them, we've looked at how to guess passwords using brute force methods, or otherwise "figure out" a users password. For those of you who missed it, check out this post on generating all possible 8 character passwords with Perl.

Today, we're going to look at something similar, but different enough that it warrants its own post: How to generate the encrypted string (given a user name and password) that you can use to manually edit your Linux or Unix system's shadow file and change someone's password. Of course, you'd need elevated privileges (or a means to get them) in order to do this. But, for the purposes of this post, we'll just assume you do.

Looking at this ethically, it's a good way to get yourself out of a sticky situation if you garble the root password and have to boot off of CD into single user mode and "have to" manually edit the shadow file so you can log back in!

Basically, the script below accepts two forms of input, which we've elected to read from STDIN. This can be easily modified to take arguments, although we chose this method so that the password you were trying to recreate the encrypted field for wouldn't show up in anyone else's "ps" output.

The nature of DES is such that, for virtually every invocation of this script, given the exact same username and password, you'll end up generating an entirely unique string. However, when this is decrypted by the "crypt" function on your OS, each unique string will resolve to the same password you entered each and every time.

Of course, we don't officially endorse cutting and pasting into your shadow file (and strongly recommend you run "pwconv" afterward if you have to), but hopefully this little reverse-password-cracking Perl script will help save your bacon at least once :)

Best wishes and enjoy,


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/usr/bin/perl

#
# encrypted password field generator
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#

print "Enter a user name: ";
$name = <STDIN>;
system("stty -echo");
print "Password: ";
$pass = <STDIN>;
system("stty echo");
print "\n";

chop($name);
chop($pass);
print &cryptPwd($name,$pass);
print "\n";

sub cryptPwd
{
local($cp_name,$cp_passwd) = @_;
@cp_saltine = ('a' .. 'z','A' .. 'Z', '0' .. '9','.','/');

$now = time();
($cp_name_pt1, $cp_name_pt2) = unpack ("C2",$cp_name);
$week = $now / (60*60*24*7) + $cp_name_pt1 + $cp_name_pt2;
$cp_numsalt = $cp_saltine[$week % 64] . $cp_saltine[$now % 64];
$cp_cryptpass = crypt($cp_passwd,$cp_numsalt);
return($cp_cryptpass);
}



, Mike




Wednesday, February 20, 2008

Starting SSL LDAP At Boot Time Without Interaction

Howdy,

Here's something people don't gripe enough about ;) Actually the problem is only common if you work in a shop that runs the iPlanet Administration Server (Formerly, Netscape LDAP) and you have it set up to run securely using TLS or SSL, and you have it set up so that you need to enter a password to start it up. That's a lot of "and"s. Okay, only 2. I'm exaggerating ;)

Having a secure LDAP setup (which runs as the "slapd" process) is good enough without the extra authentication needed to start it up. It's good to know that this is only optional. Depending on your requirements, you may or may not want to make it necessary for someone to know a password in order to start the service, assuming they already have the proper credentials and system privilege. You'll still enjoy the benefits of secure network authentication (for users accessing the database) and encryption of the trasmitted data (for the LDAP requests and responses).

If it makes you feel more secure (or your site security requires it), setting up the TLS/SSL keys for LDAP easily allows you enter that additional password. In fact, it automatically prompts you and makes you go out of your way to type enter twice in order to have a "blank" password (which, in this case, is equivalent to no password at all).

The most common issue this causes (We're not going to go into key setup or port access issues here today) is a failure to boot non-interactively. For instance, if you have LDAP set to start up in an init script called S99ldapstart, with a line like this:

/opt/ldap/slapd-server1-636/start-slapd

and you issue:

/etc/rc2.d/S99ldapstart

while you're logged in, you'll be prompted to enter the security password to start the server. Once you enter the password, the server starts up as normal. This is all well and good if you're on the machine and running the script through an interactive shell, however it won't work at all when the "rc" program tries to run this script at boot time. That sort of situation is analagous to you entering the start command (as above) and then just sitting there and never entering the password you're being prompted for. The secure LDAP server will never start.

Fortunately, even though the answer doesn't seem to be widely available (although it is packed away in online manuals, etc), it is relatively easy to implement and works like a charm. The only downside is that you put yourself at odds with the extra security you've added to the process (by insisting on the startup password) by having to store the password in a plain-text file that (if compromised) will give anyone who can access it the password you don't want them to have!

Deriving our settings from the above information (LDAP Base directory is: /opt/ldap and Server base directory is: /opt/ldap/slapd-server1-636) and accepting, for the purposes of this demonstration, that our secure startup password is "Bingo" (All passwords are case sensitive. Capitalization counts :) we can do the following at the command line, and set ourselves up for hands-free secure LDAP startups forever (or until we change the password):

host # echo "Internal (Software) Token:Bingo" >/opt/ldap/alias/slapd-server1-636-pin.txt
host # chmod 400 /opt/ldap/alias/slapd-server1-636-pin.txt


And that's it :) The most important thing to remember, since you can do this for any SSL/TLS enabled LDAP server instance, is the format of both the "name" and the "contents" of the file that will hold that password.

1. The name of the file must be of the form:

$LDAP_ROOT/alias/$LDAP_SERVER-pin.txt

Where (for this example) LDAP_ROOT=/opt/ldap and the LDAP_SERVER=slapd-server1-636. If it's not named like that, iPlanet Directory Server will not recognize it.

2. The contents of the file must be compact. No spaces are allowed anywhere they're not explictily shown here, in this example, including at the end of the line. Any deviation from this rule-of-thumb may make it so that your secure LDAP server doesn't recognize the password and reverts back to the prompt, leaving you with a server hung at startup. The password must be of the form "Token:PASSWORD" with no spaces.

Once you've done this once or twice, it's a walk in the park. Every time you need to boot your server, you can be sure that your secure LDAP servers will start up properly. Since this setup is so exacting in its requirements, it's also pretty simple to troubleshoot if breaks :)

Cheers,

, Mike




Tuesday, February 19, 2008

Backend Processing Emails Submitted During Maintenance

Hey again,

Today, we're going to take a look at the Perl script that we redirected to, in the HTML form we created in yesterday's post on collecting emails on a site maintenance page.

The script below is kept fairly simple, as its only purpose is to collect email addresses and store them safely, rather than verify that the email addresses are proper. That functionality can be worked in to this script if you like. Generally, I'll throw that overhead into a third script, since we'll have to process all these requests for notification once the site comes back up and this Perl script can't handle that automatically. An example of such an email correctness-checking script can be found in a previous post we did on checking for valid email addresses in CGI forms.

You may notice that the Perl script you find below you on the page seems somewhat archaic. This is for a reason. We chose not to employ any modules like the old-style cgi.pl or the newer CGI.pm in favor of manual environment (ENV) parsing, so that this script would work on older machines and OS's, as well as the latest and greatest versions of Linux or Unix (insofar as they support Perl).

The only two other things we make sure to do, which are important no matter what backend scripting language you choose to process your information, are to:

1. Never act directly on the form input. We simply dish it off to a file. This is a basic security measure to ensure that no one (well, probably almost no one ;) can use your backend script to execute code directly. This script isn't the best example of how that can go bad, but you never want to "run" any form elements you're receiving, if at all possible. Depending upon your webserver, bugs, abnormalities, privilege, etc, this could cause you some major headaches.

2. Read in the HTML thank you page from an external source rather than embed it in the page. This goes hand in hand, sometimes, with the "no direct execution" rule. By reading in a static file that we've written ourselves, and keep in a secure location, it's very unlikely that an outside user will be able to manipulate our system using our backend script. At the most basic level, it makes it more difficult to compromise our webserver or system by not dynamically generating content.

Best wishes and enjoy :)


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#! /usr/bin/perl

# Simple Form Parser
# Nothing Special :)
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#

print "Content-type: text/html\n\n";
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});

@pairs = split(/&/, $buffer);
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$form{$name} = $value;
}

chop($today=`date '+%m/%d/%Y'`);
$email_file_name = ">>/full/path/to/file/where/you/will/store/emails.txt";
open (EMAILFILE,$email_file_name);
print EMAILFILE $today,"",$form{'email'},"","\n";
close(EMAILFILE);

$html_dir="/directory/where/your/thank/you/html/page/is";
$fname = "$html_dir/thanks_for_your_submission.html";

open (OFILE,$fname) ;
while(<OFILE>) {
s/##(\w+)#/$form{$1}/g;
print $_;
}
close(OFILE);


, Mike




Monday, February 18, 2008

Collecting Emails On Your Site Maintenance Page

Example Email Collection HTML Form

Note - Click on the above for the full size example of the page today's HTML script will create!

Hey There,

I thought we'd start the week off and take a break from Linux and Unix Perl and Shell script porting to take a look at something a lot of admins have to deal with routinely.

If you are in any way responsible for your company's website maintenance and/or design, you may have been asked to set up some sort of a solution for handling maintenance, or downtime, situations that keeps the customer in the loop.

The simplest way to do this, is to create and HTML landing page with a form to collect email addresses. This is relatively simple to do (although hard to post correctly on blogspot, as you can see in our previous post on problems with posting Perl, Shell and other code to blogspot. Unless I make a huge mistake or two, the ampersand symbols in the code below are there on purpose and the converted ampersands are showing up as HTML tag boundaries ;)

The next step, which we'll look at tomorrow, is to create the Perl action script that your landing page will redirect to when a customer enters his or her email and submits it.

For today, enjoy this simple front-page. I created it with expansion in mind, hence the tabular structure (Good for filling with logos and custom spacer images, etc) and it should be relatively easy to modify.

Enjoy :)


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

<html>
<head>
<title>XYZ - TEMPORARY SITE MAINTENANCE</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<META HTTP-EQUIV="pragma" CONTENT="no-cache">
</head>
<body bgcolor="003366">
<!-- 2008 - Mike Golvach - eggi@comcast.net -->
<!-- Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License -->
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr valign="top">
<td rowspan="2" height="47" width="15%">
<p>&nbsp;</p>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="5%">&nbsp;</td>
<td width="5%">&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td width="5%">&nbsp;</td>
<td width="5%">&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td width="5%">&nbsp;</td>
<td width="5%">&nbsp;</td>
<td>&nbsp;
</tr>
<tr>
<td width="5%">&nbsp;</td>
<td width="5%">&nbsp;</td>
<td>&nbsp;</td>
<tr>
<td width="5%">&nbsp; </td>
<td width="5%">&nbsp;</td>
<td>&nbsp; </td>
</tr>
</table>
<td width="1%" height="51" >&nbsp;</td>
<td width="79%" height="51">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="2%">&nbsp;</td>
<td width="98%">
<table border="0" cellspacing="0" cellpadding="0" width="545">
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</table>
</td>
</tr>
<td bgcolor="#FFFFFF" width="2%">&nbsp;</td>
<td width="97%" bgcolor="#FFFFFF" valign=bottom>&nbsp;</td>
</tr>
<tr>
<td bgcolor="#FFFFFF" width="2%">&nbsp;</td>
<td width="97%" bgcolor="#FFFFFF" height="22" valign="top">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="92%" valign="top" height="1">&nbsp;</td>
<td width="8%" height="1">&nbsp;</td>
</tr>
<tr>
<td valign="top">
<p>We regret that our site is down for temporary maintenance. As soon as our work is complete the site will be available immediately. We apologize for the inconvenience!</p>
<p>Please enter your email address and we will contact you as soon as our site becomes available!</p><p>&nbsp;</p>
<form ACTION="http://xyz.com/cgi-bin/notify.cgi" METHOD="POST">
<table border="0" width="100%">
<tr>
<td align="right" width="30%"><font size="2" FACE="arial, helvetica, geneva"><b>E-mail Address:</b></font><br><font size="1" FACE="arial, helvetica, geneva">(e.g. bobby.joe@host.com)</font></td>
<td align="left" width="40%">&nbsp;<input NAME="email" TYPE="TEXT" SIZE="30"></td>
<td width="30%" valign="middle" align="left"><font FACE="arial, helvetica, geneva" SIZE="2"><input NAME="name1" TYPE="submit" VALUE="Notify Me When The Site Is Back!"></font></td>
</tr>
</table>
</form>
</td>
<td width="13%">&nbsp;</td>
</tr>
<tr>
<td width="92%" valign="top" height="1">&nbsp;</td>
<td width="8%" height="1">&nbsp;</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#FFFFFF" width="2%">&nbsp;</td>
<td width="97%" bgcolor="#FFFFFF" height="22" valign="top" align="center">&nbsp;</td>
</tr>
<tr>
<td bgcolor="#FFFFFF" width="2%">&nbsp;</td>
<td width="97%" bgcolor="#FFFFFF" height="22">&nbsp;</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>


, Mike




Sunday, February 17, 2008

Bash Shell Script - Part III Of C/Shell/Perl Porting.

Good Morning,

Today's bash shell script version of our 3 part porting post, all alliteration aside, wraps up this little experiment and, hopefully, helps tie it all together.

Please note the warning, posted yesterday, about the dangers of actually using this code plain-vanilla and keep in mind that our objective here was to show how the same objective can be accomplished using, and translated between, C code, Perl and shell script.

You can see how today's code looks written in C in our previous post on C code to add user accounts that we kicked this thing off with. Then, if you like, you can check out, virtually, the exact same thing in Perl on our previous post regarding using Perl to create user accounts.

Today's script should run equally well on both Linux and Unix. If you don't use the bash shell, or don't have it installed on your version of Unix or Linux, minor modifications to this script may be necessary, but they won't be quite as intense as the difference between this script and, say, our previous C code or Perl script.

If you do choose to revisit those previous posts, hopefully we've covered enough ground in each for you to notice the things in each that are wildly different and the things that are practically the same. Next week, amongst other things, we'll continue to look at porting between languages, but at a more specific level (which will also allow us to look at C, Perl and Shell concepts within the same post :)

Hope you enjoy this, and have a restful Sunday :)


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/bin/bash

#
# adduser.sh
#
# Add Users, Set Up Profiles,
# Set The Password And Email
# An Admin
#
# 2008 - Mike Golvach - eggi@comcast.net
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#

trap 'rm -f $home/.profile;exit 3' 1 2 3

if [ $# -ne 3 ]
then
echo "Usage: $0 [dirname - no slashes ] [ logname ] [ comment - in quotes ]"
exit 1
fi

userdir=$1
username=$2
commentfield="$3"
home="/${1}/${2}"
argument2=${#username}

if [ $argument2 -gt 8 -o $argument2 -lt 5 ]
then
echo "Please choose a logname between 5 and 8 characters!"
exit 1
fi

count=`awk -F":" '{print $3}' /etc/passwd|sort -n|tail -1`
let usernumber=$count+1

echo
echo "Check this out before proceeding!!!"
echo "-----------------------------------"
echo "Logname: $username"
echo "Homedir: $home"
echo "Comment: $commentfield"
echo "-----------------------------------"
echo
echo
echo "All of this ok?"
echo
echo "y or n"
echo

read reply

if [ $reply = "n" -o $reply = "N" ]
then
echo
echo "All right, give it another shot if you want!"
exit 0
elif [ $reply = "y" -o $reply = "Y" ]
then
:
else
echo "Only y or n - case insenstive allowed"
echo "Try Again"
exit 1
fi

echo "$username:x:$usernumber:1:$commentfield:/${userdir}/$username:/bin/ksh" >>/etc/passwd

echo "$username:*LK*:::::::" >>/etc/shadow

mkdir -m 0755 $home
cd $home

echo "stty istrip" >>.profile
echo "PATH=/bin:/usr/bin:/usr/local/bin:/usr/share/bin:." >>.profile
echo "export PATH" >>.profile
echo
echo

chown $username $home
chown ${usernumber}:1 .profile
chmod 0644 .profile

(echo "To: devnull@host.com";echo "Subject: New User Added!!!";echo;echo "$commentfield";echo "has a new account set up!";echo "The email address is ${username}@host.com!";echo;echo "Thank you,";echo " Mike Golvach")|/usr/lib/sendmail -t

echo
echo "All Done!!!"
echo
echo "Now set the Password!"
echo
/usr/bin/passwd $username
echo
echo "Password set!!! Take a break..."


, Mike




Saturday, February 16, 2008

Perl Script For User Account Addition - C/Shell/Perl Porting Part II

Hey there,

NOTE: Code revision 2/16/08 --
$home = "$ARGV[0]/$ARGV[0]";
should be
$home = "$ARGV[0]/$ARGV[1]";
Code below updated - thanks to the folks who caught that :)


A brief note before we lay bare today's script. This script, and all the scripts we use here to demonstrate how to port between C, Perl and shell code are all just examples. It is not advised that you actually use these scripts to directly manipulate your system passwd and shadow files. It shouldn't cause a problem, but facilities exist to do this already (If you do use these, run "pwconv" afterward, just in case). This working code/script is merely meant to demonstrate principles of porting between languages.

Now that we have gloom and doom out of the way, let's check out today's version of the C code to add user accounts that we introduced yesterday. This should run equally well on both Linux and Unix - Pick a flavor.

The most obvious thing you'll notice with today's Perl code is that it's a lot easier to read and understand. Porting from C to Perl has demystified a lot of what we're actually doing. And the shell script to come tomorrow will make the process (and actions involved) even more accessible :)

If you find that you have any issues with the code (anything seems presumptuous or convoluted), please refer back to yesterdays post on the original C code, as a lot of those issues were addressed up front.

Hope you enjoy this, and best wishes :)


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/usr/bin/perl

#
# adduser.pl
#
# Add Users, Set Up Profiles,
# Set The Password And Email
# An Admin
#
# 2008 - Mike Golvach - eggi@comcast.net
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#

if ( $#ARGV != 2 ) {
print "Usage: $0 [dirname - no slashes ] [ logname ] [ comment - in quotes ]\n";
exit(1);
}

$userdir = $ARGV[0];
$username = $ARGV[1];
$commentfield = $ARGV[2];
$home = "$ARGV[0]/$ARGV[1]";
@argument2 = split(//,$ARGV[1]);
$argument2 = @argument2;

if ( $argument2 > 8 || $argument2 < 5 ) {
print "Please choose a logname between 5 and 8 characters!\n";
exit(1);
}

$SIG{'HUP'} = 'IGNORE';
$SIG{'INT'} = 'IGNORE';

$count = `awk -F":" '{print \$3}' /etc/passwd|sort -n|tail -1`;
chomp($count);
$usernumber = $count++;

print "\n";
print "Check this out before proceeding!!!\n";
print "-----------------------------------\n";
printf("Logname:\t%s\n", $username);
printf("Homedir:\t/%s/%s\n", $userdir, $username);
printf("Comment:\t%s\n", $commentfield);
print "-----------------------------------\n";
print "\n";
print "\n";
print "All of this ok?\n";
print "\n";
print "y or n\n";
print "\n";

$reply = <STDIN>;
chomp($reply);

if ( $reply =~ /n/i ) {
print "\n";
print "All right, give it another shot if you want!\n";
exit(0);
} elsif ( $reply =~ /y/i ) {
true;
} else {
print "Only y or n - case insenstive allowed\n";
print "Try Again\n";
exit(1);
}

open(A, ">>/etc/passwd");
print A "$username:x:$usernumber:1:$commentfield:/${userdir}/$username:/bin/ksh\n";
close(A);

open(A, ">>/etc/shadow");
print A "$username:*LK*:::::::\n";
close(A);

mkdir($home, 0755);
chdir "$home";

open(A, ">>.profile");
print A "stty istrip\n";
print A "PATH=/bin:/usr/bin:/usr/local/bin:/usr/share/bin:.\n";
print A "export PATH\n";
print A "\n";
print A "\n";
close(A);

chown($home, 1, $username);
system("chown ${usernumber}:1 .profile");
system("chmod 0644 .profile");

open(MAILER, "|/usr/lib/sendmail -t") or die "Cannot Open Sendmail!\n";
print MAILER "To: devnull\@xyz.com\n";
print MAILER "Subject: New User Added!!!\n";
print MAILER "\n";
print MAILER "$commentfield\n";
print MAILER "has a new account set up!\n";
print MAILER "The email address is $username\@host.com!\n";
print MAILER "\n";
print MAILER "Thank you,\n";
print MAILER "\t Mike Golvach\n";
close(MAILER);

print "\n";
print "All Done!!!\n";
print "\n";
print "Now set the Password!\n";
print "\n";
system("/usr/bin/passwd $username");
print "\n";
print "Password set!!! Take a break...\n";


, Mike




Friday, February 15, 2008

C Code To Add User Accounts And Introduce C/Shell/Perl Porting.

Ahoy (I've always wanted to use that greeting ;)

Today, we're putting out a little (?) c code we wrote to standardize user account creations across any sized environment on Unix or Linux (slight modifications may need to be made depending on your environment).

C code isn't generally what we concentrate on in this blog, but we thought it would be interesting to put this out today, and follow it up with the exact same script in ksh/bash and Perl on the next successive days. Kind of a slam-bang intro to porting (Which, you may recall, we began a long time - and many scripts - ago - in this post on the shebang line.

We will, eventually, come full circle with that. It's the blessing and the curse of a blog like this: There's so much to write about and share that maintaining a really specific thread (especially a long one) can sometimes take a while and be presented in a scatter shot manner. Thank goodness for HTML hyperlinks ;)

Note that, in the code below, everything is set up to be interactive and only a few assumptions are made (which you can, of course, change to your liking). We'll keep them consistent between ports of this code, so it's easier to follow, but none of this stuff is set in stone.

Things to look for in this c code that you might want to change and/or may be confusing:

1. The "userdir" variable is the main user directory (like "/users" or something), except we ask that no slashes be used in the input. We actually do ask that in the code itself. We tried to keep it polite ;)

2. The "username" variable is used to both name the user and his/her account. So the user "bobby" would have a home directory of "/users/bobby" in this case.

3. After the fopen of /etc/passwd, we've hard coded the group number to "1" and the shell to "/bin/ksh"

4. You can change anything about this c code that you want to. This code is actually utile, but is also being used as a massive example of porting that we'll explore in the following few posts.

5. You can compile this program easily with gcc, like so:

host # gcc -o whateverNameYouWantToCallTheBinary adduser.c

For now, whether it makes sense to you or not, enjoy!

Cheers,


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License


#include
#include
#include
#include
#include

/* adduser.c
Add Users, Set Up Profiles,
Set The Password And Email
An Admin
2008 - Mike Golvach - eggi@comcast.net
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
*/

main(int argc, char **argv)
{

struct passwd *userlist;
int count, usernumber;
FILE *tmp, *stmp, *mailer, *profile;
char *commentfield, *username, *userdir, *home;
char *mailcomment, *mailemail, reply;

commentfield = (char *)malloc(1024*sizeof(char));
username = (char *)malloc(8*sizeof(char));
userdir = (char *)malloc(256*sizeof(char));
home = (char *)malloc(256*sizeof(char));
mailcomment = (char *)malloc(1024*sizeof(char));
mailemail = (char *)malloc(512*sizeof(char));

if (argc != 4) {
printf("Usage: %s [dirname - no slashes ] [ logname ] [ comment - in quotes ]\n", argv[0]);
exit(1);
}

if (strlen(argv[2]) > 8 || strlen(argv[2]) < 5) {
printf("Please choose a logname between 5 and 8 characters!\n");
exit(1);
}

signal(SIGHUP, SIG_IGN);
signal(SIGINT, SIG_IGN);

setpwent();

count = 0;

while ((userlist = getpwent()) != NULL) {
if (count < userlist->pw_uid) {
count = userlist->pw_uid;
usernumber = count+1;
}
}

endpwent();

sprintf(commentfield, "%s", argv[3]);
sprintf(userdir, "%s", argv[1]);
sprintf(username, "%s", argv[2]);
sprintf(home, "/%s/%s", argv[1], argv[2]);

printf("\n");
printf("Check this out before proceeding!!!\n");
printf("-----------------------------------\n");
printf("Logname:\t%s\n", username);
printf("Homedir:\t/%s/%s\n", userdir, username);
printf("Comment:\t%s\n", commentfield);
printf("-----------------------------------\n");
printf("\n");

printf("\n");
printf("All of this ok?\n");
printf("\n");
printf("y or n [n is the default]\n");
printf("\n");

scanf("%c", &reply);

if ( reply != 'y') {
printf("\n");
printf("All right, give it another shot if you want!\n");
exit(0);
}

tmp = fopen("/etc/passwd", "a");
fprintf(tmp, "%s:x:%d:1:%s:/%s/%s:/bin/ksh\n", username, usernumber, commentfield, userdir, username);
fclose(tmp);

stmp = fopen("/etc/shadow", "a");
fprintf(stmp, "%s:*LK*:::::::\n", username);
fclose(stmp);

mkdir(home, 0755);
chdir(home);

profile = fopen(".profile", "a");
fprintf(profile, "stty istrip\n");
fprintf(profile, "PATH=/bin:/usr/bin:/usr/local/bin:/usr/share/bin:.\n");
fprintf(profile, "export PATH\n");
fprintf(profile, "\n");
fprintf(profile, "\n");
fclose(profile);

chown(home, usernumber, 1);
chown(".profile", usernumber, 1);
chmod(".profile", 0644);

if ((mailer = popen("/usr/lib/sendmail -t", "w")) == NULL) {
perror("Mailer");
exit(1);
}

sprintf(mailcomment, "%s\n", commentfield);
sprintf(mailemail, "The email address is %s@host.com!\n", username);

fputs("To: bob@host.com\n", mailer);
fputs("Subject: New User Added!!!\n", mailer);
fputs("\n", mailer);
fputs(mailcomment, mailer);
fputs("has a new account set up!\n", mailer);
fputs(mailemail, mailer);
fputs("\n", mailer);
fputs("Thank you,\n", mailer);
fputs("\t Mike Golvach\n", mailer);

pclose(mailer);

printf("\n");
printf("All Done!!!\n");
printf("\n");
printf("Now set the Password!\n");
printf("\n");
execl("/usr/bin/passwd", "passwd", username, NULL);
printf("\n");
printf("Password set!!! Take a break...\n");

}


, Mike




Thursday, February 14, 2008

Extended Options For Solaris' Jumpstart Boot

Hey There,

Most Solaris Admins are familiar with the basic procedure for kicking off a JumpStart installation. Bare bones, it's usually getting down to the PROM and executing one of either:

ok> boot cdrom - install

or

ok> boot net - install

There are, however, quite a few more options you can throw at even the older Solaris boot PROMs to kick off your JumpStart the way you like. Note that in the below examples, the "|" character is used to indicate an either-or option.

For instance, you can boot directly off of the local hard disk with this option:

file://jumpstartDirectory/compressedConfigurationFile

or from an NFS server, like so:

nfs://serverName|IP/jumpstartDirectory/compressedConfigurationFile

Even an http server:

http://serverName|IP/jumpstartDirectory/compressedConfigurationFile

To flesh out the above examples to a certain degree, these would the type of commands you would type at the PROM ok> prompt:

ok> boot cdrom - install file://jumpstartDirectory/compressedConfigurationFile
ok> boot net - install nfs://serverName/jumpstartDirectory/compressedConfigurationFile
etc...


In certain modes, you will boot from a tar file on your JumpStart server. Just make sure you've placed your sysidcfg inside your tar ball and try this:

ok> boot net - install http://192.168.0.1/jumpstart/config.tar

And, if you use an http server, you don't even need to go through all the hassle we explored in a past post about using JumpStart across multiple subnets, because you can add the "proxy" option to that argument (much like our workaround for JumpStart across subnets, you need to specify the IP address of the proxy, and not the hostname), like so:

ok> boot net - install http://xyz.com/jumpstart/config.tar&proxy=192.168.0.0.1

Hopefully, some of this trivia will prove useful to you at some point. Now there should be almost no way you can get out of having to JumpStart that new server ;)

Cheers,

, Mike




Wednesday, February 13, 2008

Prtdiag And "Lane Width Failed" Errors On Solaris 10

A lot of admins who work with Solaris 10 Zones may have run into this situation already, but I'm just starting to hear it now from users who like to do their own diagnostics before coming to me with a possible system issue. Don't get me wrong folks; I love it when users show this kind of initiative (as long as they don't go into the data center and start pressing buttons ;)

This problem is similar to the problem Solaris 10 has with old style /usr/ucb/ps, but it isn't quite as prevalent.

The situation that occurs is that a user, trying to gather information on the system that may, or may not be, having a hardware or software issue, runs a pretty standard command called "prtdiag," probably like this:

host # /usr/platform/`uname -i`/sbin/prtdiag

Now, in Solaris 10, this doesn't always cause an error. The reason for this, and how it differs from the old style ps error, is that it only occurs when the user runs prtdiag on Solaris 10 with Zones enabled.

Another thing that makes this unusual error so rare is that, on most Zone-enabled Solaris 10 setups, users accounts are all setup in the non-global Zone and prtdiag will only run in the global Zone. Obviously, the "rules" aren't followed all the time, insofar as system setup and access are concerned. Like they say: the customer is always right, even if he's doing something he probably shouldn't be (or something like that ;)

So far, I've only seen this error on the Mx000 Series Servers from Sun, but that's probably because we use those the most. Generally, the error will present itself in some way similar to the following (Looks scarier than it is and the actual error may vary)

host # /usr/platform/`uname -i`/sbin/prtdiag <-- Stripping the 100 lines preceding the error in my ongoing effort to fight eye-strain ;)

...
IO Lane/Frq
LSB Type LPID RvID,DvID,VnID BDF State Act, Max Name Model
--- ----- ---- ------------------ --------- ----- ----------- ------------------------ ------------------
Logical Path
------------
Getting lane width failed for path /pci@3,800000/SUNW,emlxs@0


And, again, just like our ps error on Solaris 10, running the command as root makes everything work just fine:

root@host # /usr/platform/`uname -i`/sbin/prtdiag

...
IO Lane/Frq
LSB Type LPID RvID,DvID,VnID BDF State Act, Max Name Model
--- ----- ---- ------------------ --------- ----- ----------- ------------------------ ------------------
Logical Path
------------
00 PCIe 3 1, fc21, 10af 1, 0, 0 okay 4, 4 SUNW,emlxs-pci10af,fc21 LPe110094-S
/pci@3,800000/SUNW,emlxs@0


Sun's stock answer, for now, is to change the permissions of the prtdiag command so that it runs setuid root (For those of us who are reading this and don't know what that means - A very small portion of the audience that bothered to read this far, I'm sure - when a program is setuid "username," it will run as that user - with that user's privileges - no matter what user actually executes it)

root@host # chmod 4755 /usr/platform/`uname -i`/sbin/prtdiag

or, if you prefer to change your file modes in alpha:

root@host # chmod u+s /usr/platform/`uname -i`/sbin/prtdiag

Probably the best way to work-around this, and keep with Sun's basic security requirement of not running programs like this setuid root, is to make use of a program called sudo (which comes with Solaris 10). Just include a rule like the one below, so that users can only run "/usr/platform/`uname -i`/sbin/prtdiag" straight up and can't run it with any additional flags or switches. This will allow them to get the information they want and safeguard you, the admin, against any unforeseen issues with this work-around. Example rule below:

ALL ALL = (root) /usr/platform/`uname -i`/sbin/prtdiag ""

Another promise from me that, eventually, we will get to a post devoted entirely to sudo. For now, here's a quick rundown on how this rule reads:

ALL <--- All users can use this sudo rule
ALL <--- This command can be used on any host.
= <--- The cement that connects the preceding user and host restrictions with the commands and options to follow
(root) <--- This command will be run as the user root
/usr/platform/`uname -i`/sbin/prtdiag "" <--- This is the only allowed command. This command specifies that /usr/platform/`uname -i`/sbin/prtdiag can only be run with no additional switches or flags (like "-v"). The "" (double-double quotes) indicate that no switches are allowed after the command.

Hopefully, and in all likelihood, this solution should keep everyone happy. You've kept the security flaw from being exploited, retained the original permissions on /usr/platform/`uname -i`/sbin/prtdiag and allowed users to be able to get their diagnostic output.

All that's left to do is thank your user base for helping make your job easier :)

, Mike