Update: Mac OS X and Novell eDirectory Integration
Novell Cool Solutions: Feature
By Randall R. Saeks
Reader Rating
from 4 ratings
|
Digg This -
Slashdot This
Updated: 12 Jul 2006 |
Mac OS X and Novell eDirectory Integration
Note: To download the latest update to the article, click here.Randy Saeks, Support Technician
Glenbrook High School District #225, Illinois
Introduction
This document serves as a guide to help you integrate Apple computers into a Novell environment. The same concepts, however, can be applied to any directory service. This document focuses on supplementing various other guides that are already published. It will include the various setups utilized in District 225, as well as challenges, problems and solutions encountered during the process. In addition, this will hopefully aid in building a stronger community where professionals working in a similar environment can correspond to address issues.
Background
School District #225 is comprised of two high schools: Glenbrook North High School and Glenbrook South High School. The schools have a high adoption of technology into the curriculum. Glenbrook North is comprised of 31% Macs (302 machines) while Glenbrook South is roughly 42% Macintosh (410 machines).
Once Apple released OS X.2, a plan was devised to upgrade the machines running OS 9.x to OS X. When Apple introduced 10.3, they incorporated version 3 of the Lightweight Directory Access Protocol (LDAP). This allowed OS X desktops to take better advantage of open standards. One of these advantages is the ability to use the LDAP services built into NetWare. Using the LDAP protocol provided by Novell and read by the OS X desktop, it is possible to have a Macintosh workstation authenticate to eDirectory to provide network services to the end user.
There are three basic elements to integrating Apple computers into a Novell environment:
- Directory setup
- Desktop setup
- Managing preferences
Directory Setup
Before you do the client and server setup, you need to add certain extensions to eDirectory. These attributes can be broken down into several main areas:
- User
- Group
- Computer
- Mount
- Printer
- Machine List
- Configuration
- Preset
The user authentication attributes are the Mount and User attributes. The others can be used later on in the Client and Server setup to allow for managed settings to be applied. The Mount attributes tell the operating system where and how to mount the directory that will be the network home for the user. The User attributes are used by the OS X machine to authenticate the user and determine who is using the machine. This includes the user's home directory, password, name, unique ID, Group ID, etc.
The next pages explain the schema extensions and what they are used for. In the LDIF file used to extend the schema, unnecessary items may be commented out. Keep in mind that to ensure the setup will work for you, apply it to a test environment. Additionally, you don't need to apply all the attributes at once - you can add more at a later time. The schema extensions can be obtained from Macenterprise.org. You can find the link to the specific article by Dan Sinema in the Additional Information section at the end of this document.
User Attributes
| Attribute | Description |
| apple-user-homeurl | AFP address of the server that has the user's home directory. *Note: This will need to be in the same case as the apple-user-homeDirectory attribute. |
| apple-user-class | Unused |
| apple-user-homequota | Home Directory quota, in Kilobytes. This cannot be exceeded. |
| apple-user-mailattribute | Mail-related settings, in XML |
| apple-mcxflags | Attribute that alerts the workstation at login that there are Managed Client settings to be applied to the user |
| apple-mcxsettings and apple-mcxsettings2 | Where the plist-formatted preferences (XML) are stored in the directory. If the first attribute (apple-mcxsettings) becomes too large, it will carry over into apple-mcxsettings2. |
| apple-user-picture | Path to the picture the user account will use |
| apple-user-printattribute | Print quota settings |
| apple-user-adminlimits | User capabilities, in Workgroup Manager. The settings here have no effect on the workstation. |
| apple-user-authenticationhint | Once a user types the password incorrectly three times, this text appear in the login window. For example: "If you forgot your password, please contact the Help Desk?. |
| apple-user-homesoftquota | Allocated disk space. A warning will be issued once this is reached. |
| apple-user-passwordpolicy | Password settings applied to the user, such as password length, expiration time, etc. |
| apple-keyword | Keywords / Comments on the user account. |
| apple-user-homeDirectory | UNIX path to the user's home directory, once the AFP object apple-user-homeurl is mounted. This is partially determined by the mount object mountDir. |
| apple-generateduid | Generated Unique ID |
Group Attributes
| Attribute | Description |
| apple-group-homeurl | AFP address of the folder of the Group to which the user belongs |
| apple-group-homeowner | Owner of the folder apple-group-homeurl |
| apple-mcxflags | Attribute that alerts the workstation at login that there are Managed Client settings to be applied to the group |
| apple-mcxsettings | Actual Managed Client settings. These settings will apply to the group the user belongs to. |
| apple-group-realname | 'Real Name' of the group. This can be something like "OS X Admins," "Students," or "Marketing." |
| apple-user-picture | Picture for the group |
| apple-keyword | Keywords/comments for the group names, such as "Created on 11/22 by Randy Saeks." |
| apple-generateduid | UniqueID of the group |
| apple-group-memberUid | UniqueID of the members of the group |
Computer Attributes
| Attribute | Description |
| apple-realname | Name of the workstation |
| Description | Description of the workstation |
| macAddress | MAC address of the machine (used to identify the machine on the network) |
| apple-computer-list-groups | Groups that the workstation belongs to. |
| apple-mcxflags | Attribute that alerts the workstation at login that there are Managed Client settings to be applied to the workstation. |
| apple-mcxsettings | Where the plist-formatted preferences (XML) are stored in the directory. |
| apple-xmlplist | XML Plist Data |
| authAuthority | Authentication authority |
| uidNumber | Unique ID number of workstation |
| gidNumber | Group ID number of the workstation |
| apple-generateduid | Generated unique ID |
Computer List Attributes
| Attribute | Description |
| apple-mcxflags | Attribute that alters the workstation at login that there are Managed Client settings to be applied to the workstation. |
| apple-mcxsettings | Where the plist-formatted preferences (XML) are stored in the directory. |
| apple-computer-list-groups | XML Plist Data |
| apple-computers | Computers in the group |
| apple-keyword | Notes / Keyword about the computer |
| gidNumber | Group ID number of the workstation |
Mount Attributes
| Attribute | Description |
| apple-mountDirectory | Location within the file system where the server will be mounted |
| apple-mountType | Mount VFS Type |
| apple-mountOption | Mount Options |
| apple-mountDumpFrequency | Mount Dump Frequency |
| apple-mountPassNo | Mount Passno |
Printer Attributes
| Attribute | Description |
| apple-printer-attributes | Printer attributes (in /etc/printcap format) |
| apple-printer-lprhost | Printer LPR hostname |
| apple-printer-lprqueue | Printer LPR queue |
| apple-printer-type | Printer Type |
| apple-printer-note | Notes about the printer |
Machine Attributes
| Attribute | Description |
| apple-machine-software | Installed system software |
| apple-machine-hardware | System hardware description |
| apple-machine-server | Net Info Domain Server Binding |
| apple-machine-suffix | DIT suffix |
Configuration Attributes
| Attribute | Description |
| apple-config-realname | Real name of the configuration object |
| apple-data-stamp | Data Stamp on the configuration |
| apple-password-server-location | Address of the password server |
| apple-password-server-list | List of the password servers |
| apple-ldap-replica | information regarding the LDAP replica |
| apple-ldap-writable-replica | information regarding the writable LDAP directory |
| apple-keyword | Keyword on the configuration |
| apple-kdc-authkey | KDC authentication key |
| apple-kdc-configdata | KDC configuration data |
| apple-xmlplist | XML Plist of the data |
Location Attributes
| Attribute | Description |
| apple-dns-domain | DNS Domain |
| apple-dns-nameserver | DNS Name Server List |
Authentication Authority Attributes
| Attribute | Description |
| authAuthority | Authentication Authority |
XML Plist Attributes
| Attribute | Description |
| apple-xmlplist | XML Plist Data |
Preset User Attributes
| Attribute | Description |
| apple-user-homequota | Home Directory quota, in KB. This value cannot be exceeded |
| apple-user-mailattribute | Mail-related settings, in XML |
| apple-mcxflags | Attribute that alerts the workstation at login that there are Managed Client settings to be applied to the user. |
| apple-mcxsettings and apple-mcxsettings2 | These two attributes are where the plist-formatted preferences (XML) are stored in the directory. If the first attribute (apple-mcxsettings) becomes too large, it will carry over into apple-mcxsettings2. |
| apple-user-picture | Path to the picture the user account will use |
| apple-user-printattribute | Print quota settings |
| apple-user-adminlimits | User capabilities, in Workgroup Manager. These settings have no effect on the workstation. |
| uid | Preset UID of the user |
| apple-user-homesoftquota | Allocated disk space. A warning will be issued once this is reached. |
| apple-user-passwordpolicy | Password settings applied to the user, such as password length, expiration time, etc. |
| apple-keyword | Keywords/comments on the user account |
| memberUid | UID of the user in group |
| apple-generateduid | Generated Unique ID |
| gidNumber | Group ID Number |
| homeDirectory | Home Directory |
| apple-user-printattribute | Printer settings for the user |
| userPassword | Password for the user |
| loginShell | Default login shell for the user. In ConsoleOne, you can put in the shell and then the script for the user to run. |
| Description | Description of the user |
| shadowLastChange | Last time the shadow password was changed |
| shadowExpire | When the shadow password is set to expire |
| authAuthority | Authentication authority |
| apple-preset-user-is-admin | Determines whether preset users should be put into the Admin group |
| apple-user-homeurl | AFP address of the server that has the users home directory. Note: This will need to be in the same case as the apple-user-homeDirectory attribute. |
| apple-user-class | Unused |
Preset Computer List Attributes
| Attribute | Description |
| apple-mcxflags | Default managed client flags for machines |
| apple-mcxsettings | Default managed client settings for machines |
| apple-computer-list-groups | Default group to which new machines belong |
| apple-keyword | Default comments/keyword about a machine |
Preset Group Attributes
| Attribute | Description |
| memberUid | UID of member in the group |
| gidNumber | Default group ID |
| apple-group-homeurl | Default group AFP address |
| apple-group-homeowner | Default owner of the group folder |
| apple-mcxflags | Default managed flags for the group |
| apple-mcxsettings | Default managed settings for the group |
| apple-group-realname | Real Name of the group |
| apple-keyword | Keyword(s) for the group |
Desktop Setup
There currently is a managed desktop for each user. The user who logs in belongs to a group. Workgroup Manager is used to manage preferences for the machine that are retrieved from eDirectory. This is accomplished with the Managed Client settings of OS X Server, where the same LDAP mappings are used on the OS X Server as on the Client. This will tell the server where to store the settings, and the client where to retrieve them. For more details, see the Managing Preferences section.
The file system has some non-manageable preferences and settings. These are set using loginhooks. When a user successfully logs into the system, a hook is run with root privileges. There is a relatively standard desktop for users that appears at first login. The idea was taken from the University of Utah setup with UlabMin. A link to their page is contained in the Additional Information section at the end of this article. The desktop wallpaper, which has tips on using the OS X setup, was modified to fit our setup. A screen shot of the desktop is shown below.
Figure 1: Standard desktop
(Larger image)Login Scripts
Below is a basic flowchart of how the login process works. It shows the flow of scripts and conditions required for them to run. The best thing to do when setting up loginhooks that perform various actions is to take them one command at a time. This will make troubleshooting much easier. It also is good to logically plan a setup that will work out the best. preferably a modular one. That way, if one script needs to be changed, you can send out one smaller file. Planning and thinking though a hierarchal setup for login scripts will make developing them much easier.
Figure 2: Login process flowchart
(Larger image)The first script I use is shown below. Line numbers are added for reference.
1 echo "$1 `date`" >> /var/log/login.log 2 sudo -H -u $1 /Library/Management/desktop.sh 3 exit 0
The main purpose of line 1 is to write the login user and the date to the log file stored on the machine. The parameter $1 in bash is the user ID of the current user. Line 2 runs the desktop creation script with the credentials of the user who is logged in. This is needed, because otherwise the script will run as root, and the items will be placed on the root user's desktop. The final line is an exit statement.
As seen on line 2, the login.sh script calls the desktop.sh script. This is what I use for managing the desktop and copying things that cannot be done though Workgroup Manager. The script follows:
1 id_num=`id -u`
2 if [ $id_num -le 9999 ] 3 then 4 if echo "$HOME" | grep -q '10\.2'; then 5 /Library/Management/GBN/staff.sh 6 elif echo "$HOME" | grep -q '10\.3'; then 7 /Library/Management/GBS/staff.sh 8 elif echo "$HOME" | grep -q '10\.1'; then 9 echo "TEST" 10 fi 11 elif [ $id_num -gt 10000 ]
12 then 13 if echo "$HOME" | grep -q '10\.2'; then 14 /Library/Management/GBN/student.sh 15 ln -shf /Network/Servers/10.2.1.22/GBNCS3.DATA/Data/GBN ~/Desktop/Teacher\ Folders 16 elif echo "$HOME" | grep -q '10\.3'; then 17 /Library/Management/GBS/student.sh 18 ln -shf /Network/Servers/10.3.1.22/GBSCS3.DATA/Data/GBS ~/Desktop/Teacher\ Folders 19 elif echo "$HOME" | grep -q '10\.1'; then 20 echo "TEST" 21 fi 22 fi
23 ln -shf ~/ ~/Desktop/My\ User\ Folder 24 rm -r ~/Library/Caches/MS\ Internet\ Cach 25 ln -shf /Users/Shared/Library/Caches/MS\ Internet\ Cache ~/Library/Caches/
26 rm -r ~/Movies 27 ln -shf /Users/Shared/Movies ~/
28 rm -r ~/Library/Preferences/ByHost 29 ln -shf /Users/Shared/Library/Preferences/ByHost ~/Library/Preferences
30 if echo "$HOME" | grep -q '10\.2'; then 31 ldapsearch -x -h gbnsvc1.glenbrook.k12.il.us -LLL "(uidNumber=`id -u`)" 32 loginGraceRemaining > /var/log/grace_remaining.log 33 lgr=`grep loginGraceRemaining /var/log/grace_remaining.log` 34 lgr_count=${lgr##*loginGraceRemaining:\ } 35 if [ $lgr_count -lt 5 ] 36 then 37 open /Applications/Utilities/Password\ Expire.app 38 fi 39 elif echo "$HOME" | grep -q '10\.3'; then 40 ldapsearch -x -h gbssvc1.glenbrook.k12.il.us -LLL "(uidNumber=`id -u`)" 41 loginGraceRemaining > /var/log/grace_remaining.log 42 lgr=`grep loginGraceRemaining /var/log/grace_remaining.log` 43 lgr_count=${lgr##*loginGraceRemaining:\ } 44 if [ $lgr_count -lt 5 ] 45 then 46 open /Applications/Utilities/Password\ Expire.app 47 fi 48 fi 49 exit 0
The first line will be used throughout the entire script. In bash, it is fairly simple to assign a variable a value. However, using some of the commands can be tricky. In this case, the id --u command is used. The actual command ID has various ways it can return the values. By default, the user ID and primary group ID are returned. By specifying the --u parameter, only the user ID is returned. The two ticks (the non-shifted tilde key) are used to signify the result of the command. In this manner we assign the value of the command id --u to id_num. That way, each time this command is run it must query the network. In saving the value, the network only needs to be queried once, and that value can now be put into memory.
Lines 2 though 11 determine if the user logging in as a student or staff, based on the unique ID of the user. Because all our staff have 4-digit ID's, we set the unique ID of each user in ConsoleOne to the staff ID. That enables us to easily log the user and determine who, student or staff, is logging in. This is done by the command:
if [$id_num --le 9999]
If the user ID is less than 9999, it is assumed that the person is a staff member, and the next set of IF statements execute. Because the image being used is for two locations, there may be different software setups at each building. To overcome this, we check where the user is located. The UNIX $HOME command is used to print the full path of the user's home directory, based upon the home path in the user's UNIX profile.
The IP scheme we use has different subnets at each location. The following command:
echo ?$HOME? | grep --q ?10\.2'
will print out to the terminal the homepath, and then perform a search of that value for 10.2. The "\" signifies to the terminal that the ?.' should be interpreted as an ASCII value, as opposed to a control character. Nesting this command with an IF will allow requested items to run only when the condition is true.
The same principle applies to student users. This can be seen on lines 12 though 22. With their ID numbers being 5 or more digits, if the value is greater than 10,000 it is assumed the user is a student. The building settings are done with the same comparison as the staff.
The next section of code deals with items pertaining to each and every user that logs into the machine. Lines 23-29 are things that all users will receive. The first,
ln --shf ~/ ~/Desktop/My\ User\ Folder
will place a link (similar to an alias) on the user's desktop that goes to their user folder. Again, the "\" is used before a non-text character to signify not to break from the script.
Line 24 removes the Internet cache from the user's folder. This cache takes up 10 MB per user, and with a limitation of 100MB of storage on student folders in our setup, it made sense to free up the extra space. To make up for it however, the cache on the machine will be from the same file for all users. A link is made (line 25) to re-direct the file to the one on the machine.
Similarly, some students will be using iMovie to make movies, and folders can fill up rather quickly. Using the same method as the Internet cache, the Movies folder that appears in a user folder is re-directed to the local machine. This frees up the file size issue; however, users will need to remember that their data is only on that machine.
One of the most important links for functionality is in lines 28 and 29. If you are using NetWare 6, there is a limitation of 31 characters based on the AFP module that Novell uses. There are per-user settings (Classic is one) that are stored in the user folder on the network that violate this limitation. Therefore, Classic will not function. The preferences from a local account are copied into a shared folder on the machine. The Preferences in the user's folder are recursively deleted, and a link is made to the ones on the local hard drive. This way, when OS X looks in the usual location for these preferences (the network), it will find the link, resolve it to the machine, and then use the preferences on the local drive. The link is seen below.
Figure 3: Standard desktop
Figure:
Since the machine can handle the longer file names, the issue is resolved.
Finally, lines 30-49 notify users when passwords have expired. There is no built-in mechanism for users to be notified of expired LDAP passwords in OS X. Some locations force users to change their Novell password periodically. It is entirely possible that a user can login to OS X and never be notified the password has expired, and thus lock themselves out.
The lines 30 and 39 are used so that the LDAP server in the building where the user is from can be determined. This prevents users having to go across buildings to one single server to check for expiration.
Lines 31 and 32 go out to the LDAP server specified with an anonymous bind. With the unique ID of the user logging in, a query is made to the LDAP server for the value of the LDAP attribute loginGraceRemaining. This attribute is then placed it into a text file, overwriting what is already there. Most likely, a LDAP mapping will have to be created on the LDAP group in ConsoleOne to map the eDirectory attribute of loginGraceRemaining to an LDAP value of your choice.
Line 33 sets the value of lgr to the result of searching the file for the LDAP attribute. Once that is completed, the actual numeric value is placed into lgr_count. This is done by setting the value to the text after loginGraceRemaining, then a colon, backslash, and a space (":\ "). That value is then compared on line 35 to the value of how many grace logins users receive. If is below that, it will open a program on the local machine.
Script Resources
The program and scripts I wrote to accomplish this are available under the GPL and are listed at the end of this document. That is a basic outline on what processes on our workstations after a successful authentication. However, there are still a few customizations that can be made. These are the scripts that are opened based on the location, and user type (see lines 5, 7, 14, and 17). The first two scripts (ending in staff.sh) and the last two (ending in student.sh) do very similar tasks.
/Library/Management/GBN/staff.sh
1 ln -shf /Library/Management/GBN/FS\ Only/Glenbrook\ Applications ~/Desktop/Glenbrook\ Applications
2 ln -shf /Network/Servers/10.2.1.21/gbncs2.data/Data/FacStaff ~/Desktop/FacStaff
/Library/Management/GBS/staff.sh
1 ln -shf /Library/Management/GBS/FS\ Only/Glenbrook\ Applications ~/Desktop/Glenbrook\ Applications
2 ln -shf /Network/Servers/10.3.1.21/gbscs2.data/Data/FacStaff ~/Desktop/FacStaff
/Library/Management/GBN/student.sh
1 ln -sfh /Library/Management/GBN/profiles/studentuser/Desktop/Glenbrook\ Applications ~/Desktop/Glenbrook\ Applications
2 cp /Library/Management/GBN/profiles/studentuser/com.apple.desktop.plist ~/Library/Preferences
/Library/Management/GBS/student.sh 1 ln -shf
/Library/Management/GBS/profiles/studentuser/Desktop/Glenbrook\ Applications ~/Desktop/Glenbrook\ Applications
Deploying the Scripts
In building one image that is going to be used at two locations with various different software setups, it was necessary to present the staff at each building with the different folder of Applications. To do this, the Application setup on our Windows machines was mimicked. For all users, there are shortcuts to commonly used programs on their desktop, in a folder called Glenbrook Applications. A location was created based on building to create the profiles of applications and settings to be copied.
In this example, on line 1 of both staff.sh scripts, a link is made in the current users desktop to the folder Glenbrook Applications, in the /Library/Management/building folder. In this case, all applications that users at GBN will need are to be placed in the Glenbrook Applications folder in the GBN folder; likewise for GBS users to the GBS folder.
Line 2 in the staff.sh script places a shortcut to the shared space on the network that all Staff members have access to. On our Windows machines, this is accomplished with a drive mapping in my computer as well as shortcut on the desktop.
The last 2 scripts (student.sh) on line 1 have the same functionality as the staff scripts. The only difference is the path from where they are copied. In the GBN student.sh script there is one difference from the GBS script. This deals with the plist file that controls the appearance of the desktop for the students. Since the desktop appearance cannot be set with WorkGroup Manager, I had to set the wallpaper for an account and find which file was changed. In doing so, that file is copied to each students profile at login. Although they may set a wallpaper for themselves at login, the default one will be there the next time they log in.
Managing Preferences
Once all the login items that pertain to the machine are set, the next task is managing preferences. They can be broken into four areas; user, group, computer, and computer list. Preferences apply in this order: Computer, Group, User.
These settings are set on the OS X Server itself. It will need to have the same LDAP mappings as the clients, so you will want to apply the same package of eDirectory attributes to the server as the workstation and set up the LDAP profile in Directory Access. It is important to first plan out the user structure - what settings you want groups to have, how to make it easy to get that done, etc.
The nice thing about using the UNIX settings for groups is that the users merely have to have a group ID, and not actually be in the eDirectory group. For example, putting all students in a default GroupID of 20 allows every student within the defined search base to receive the same settings, without adding each person into the group with that Unix ID.
We currently have a base setup with students in a Primary Group ID of 20. A dummy group is created to hold the preferences for Group ID 20, so the preferences are organized. We also added all staff members into a Primary Group ID of 40. The same setup was used. A dummy group was created with Group ID 40, and the preferences applied to that group. We had to change the primary group ID of the users to 40 for this to work. Finally, we created another group with Group ID of 80. This group will allow users to be admins on the machines. There currently are no preferences set to this group, but login items will be added so the users connect to the same servers as the other staff members.
Note: If everyone has a primary group ID of 20, no matter whom they are, they will receive the preferences for the group number. If there are additional groups that you want to apply settings to, you can simply create a group, give it a unique group ID, and assign members into that group. This will make it easier to create special groups with access. Keep in mind that it is much easier to track settings when done on a group level than a user level.
Every preference that appears on the server can be managed, so if there are additional programs that install preference panes, it might be a good idea to install them on the server. Each preference can either be enabled or disabled to the user. Some, such as the dock, login items, printers, can be managed for the user. This is where the "manage once" preference setting may be beneficial.
Manage Once allows for the preference to be set on the machine, but be modifiable. Giving the users a simple dock at login with common items, then allowing them to change it is a good setup. There is a Cool Solution talks about using Workgroup Manager to connect to other servers automatically, with the user logging in credentials.
Additional Information
This section contains links to other sites and guides that you may find useful. If there are any comments or concerts, feel free to contact me via email, at rsaeks@gmail.com.
Novell
Multiple Mac Connections without Additional Logins
http://www.novell.com/coolsolutions/nds/features/a_mac_ldap_connections_edir.html
Leveraging eDirectory with Apple Workgroup Manager
http://www.novell.com/coolsolutions/nds/features/tips/t_apple_workgroup_edir.html
Student Login Versus Staff Login http://www.novell.com/coolsolutions/nnlsmag/features/trenches/tr_staff_vs_students_nls.html</p>
LDAP Expired Password
http://www.novell.com/coolsolutions/tools/1969.html
Integrating Mac OS X and Novell eDirectory White Paper
http://whitepapers.zdnet.co.uk/0,39025945,60045021p-39000512q,00.htm
MacEnterprise.org
Integrating Mac OS X 10.3 and Novell eDirectory
http://macenterprise.org/content/view/80/77/
Resources
http://macenterprise.org/component/option,com_weblinks/Itemid,88/
University of Utah University of Utah Mac OS Support http://www.macos.utah.edu/Documentation/ulabmin/ulabmin.html
Reader Comments
- Thanks ... this has helped us.
- Thanks much for this excellent article!
- This piece documents Apple features better than even some Apple documentation.
Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com
