Added support for lists.
[licom.git] / book.cgi
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use lib (qw(lib));
6
7 use CGI (':cgi');
8 use CGI::Carp (qw(fatalsToBrowser));
9 use URI::Escape;
10 use Data::Dumper;
11
12 use Person;
13
14 our $Debug = 0;
15 our %Config = ();
16
17 our @MultiFields = (qw(address homephone cellphone officephone fax mail uri group));
18
19 our %FieldNames = 
20 (
21         address         => 'Address',
22         homephone       => 'Home Phone',
23         cellphone       => 'Cell Phone',
24         officephone     => 'Office Phone',
25         fax             => 'FAX',
26         mail            => 'E-Mail',
27         uri             => 'URI (Homepage)',
28         group           => 'Group'
29 );
30
31 our $MySelf = $ENV{'SCRIPT_NAME'};
32
33 our $Action = param ('action');
34 $Action ||= 'default';
35
36 our %Actions =
37 (
38         browse  => [\&html_start, \&action_browse,  \&html_end],
39         default => [\&html_start, \&action_browse,  \&html_end],
40         detail  => [\&html_start, \&action_detail,  \&html_end],
41         edit    => [\&html_start, \&action_edit,    \&html_end],
42         list    => [\&html_start, \&action_list,    \&html_end],
43         save    => [\&html_start, \&action_save,    \&html_end],
44         search  => [\&html_start, \&action_search,  \&html_end],
45         verify  => [\&html_start, \&action_verify,  \&html_end],
46         vcard   => \&action_vcard
47 );
48
49 read_config ();
50
51 # make sure AuthLDAPRemoteUserIsDN is enabled.
52 die unless ($ENV{'REMOTE_USER'});
53 $Config{'base_dn'} = $ENV{'REMOTE_USER'};
54
55 Person->connect
56 (
57         uri     => $Config{'uri'},
58         base_dn => $Config{'base_dn'},
59         bind_dn => $Config{'bind_dn'},
60         password => $Config{'password'}
61 ) or die;
62
63 our ($UserCN, $UserID) = Person->get_user ($Config{'base_dn'});
64
65 if (!$UserID and $Action ne 'save')
66 {
67         $Action = 'edit';
68 }
69
70 if (!$UserCN)
71 {
72         die;
73 }
74
75 if (!defined ($Actions{$Action}))
76 {
77         die;
78 }
79
80 if (ref ($Actions{$Action}) eq 'CODE')
81 {
82         $Actions{$Action}->();
83 }
84 elsif (ref ($Actions{$Action}) eq 'ARRAY')
85 {
86         for (@{$Actions{$Action}})
87         {
88                 $_->();
89         }
90 }
91
92 #print qq#<div>Authenticated as ($UserCN, $UserID, #, $Config{'base_dn'}, qq#)</div>\n#;
93
94 Person->disconnect ();
95
96 exit (0);
97
98 ###
99
100 sub action_browse
101 {
102         my $group = param ('group');
103         $group = shift if (@_);
104         $group ||= '';
105
106         my @all;
107         if ($group)
108         {
109                 @all = Person->search ([[group => $group]]);
110         }
111         else
112         {
113                 @all = Person->search ();
114         }
115
116         if (!$group)
117         {
118                 my %groups = ();
119                 for (@all)
120                 {
121                         my $person = $_;
122                         my @g = $person->get ('group');
123
124                         $groups{$_} = (defined ($groups{$_}) ? $groups{$_} + 1 : 1) for (@g);
125                 }
126
127                 print qq(\t\t<h2>Contact Groups</h2>\n\t\t<ul class="groups">\n);
128                 for (sort (keys (%groups)))
129                 {
130                         my $group = $_;
131                         my $group_esc = uri_escape ($group);
132                         my $num = $groups{$group};
133
134                         print qq(\t\t\t<li><a href="$MySelf?action=browse&group=$group_esc">$group</a> ($num)</li>\n);
135                 }
136                 if (!%groups)
137                 {
138                         print qq(\t\t\t<li class="empty">There are no groups yet.</li>\n);
139                 }
140                 print qq(\t\t</ul>\n\n);
141         }
142
143         if ($group)
144         {
145                 print qq(\t\t<h2>Contact Group &quot;$group&quot;</h2>\n);
146         }
147         else
148         {
149                 print qq(\t\t<h2>All Contacts</h2>\n);
150         }
151
152         print qq(\t\t<ul class="results">\n);
153         for (sort { $a->name () cmp $b->name () } (@all))
154         {
155                 my $person = $_;
156                 my $cn = $person->name ();
157                 my $cn_esc = uri_escape ($cn);
158
159                 print qq(\t\t\t<li><a href="$MySelf?action=detail&cn=$cn_esc">$cn</a></li>\n);
160         }
161         print qq(\t\t</ul>\n\n);
162
163         print qq(\t\t<div class="menu">\n);
164         if ($group)
165         {
166                 my $group_esc = uri_escape ($group);
167                 print qq(\t\t\t[<a href="$MySelf?action=list&group=$group_esc">List</a>]\n),
168                 qq(\t\t\t[<a href="$MySelf?action=browse">Back</a>]\n);
169         }
170         else
171         {
172                 print qq(\t\t\t[<a href="$MySelf?action=list">List</a>]\n);
173         }
174         print qq(\t\t</div>\n);
175 }
176
177 sub action_list
178 {
179         my $group = param ('group');
180         $group = shift if (@_);
181         $group ||= '';
182
183         my $title = $group ? "List of group &quot;$group&quot;" : 'List of all addresses';
184         my @fields = (qw(address homephone cellphone officephone fax mail));
185
186         my @all = ();
187         if ($group)
188         {
189                 @all = Person->search ([[group => $group]]);
190         }
191         else
192         {
193                 @all = Person->search ();
194         }
195
196         print <<EOF;
197                 <h2>$title</h2>
198
199                 <table class="list">
200                         <tr>
201                                 <th>Name</th>
202 EOF
203         for (@fields)
204         {
205                 print "\t\t\t\t<th>" . (defined ($FieldNames{$_}) ? $FieldNames{$_} : $_) . "</th>\n";
206         }
207         print "\t\t\t</tr>\n";
208
209         for (sort { $a->name () cmp $b->name () } (@all))
210         {
211                 my $person = $_;
212                 my $sn = $person->lastname ();
213                 my $gn = $person->firstname ();
214
215                 print "\t\t\t<tr>\n",
216                 "\t\t\t\t<td>$sn, $gn</td>\n";
217
218                 for (@fields)
219                 {
220                         my $field = $_;
221                         my @values = $person->get ($field);
222                         print "\t\t\t\t<td>" . join ('<br />', @values) . "</td>\n";
223                 }
224
225                 print "\t\t\t</tr>\n";
226         }
227         print "\t\t</table>\n\n";
228
229         if ($group)
230         {
231                 my $group_esc = uri_escape ($group);
232                 print qq(\t\t<div class="menu">[<a href="$MySelf?action=browse&group=$group_esc">Back</a>]</div>\n);
233         }
234         else
235         {
236                 print qq(\t\t<div class="menu">[<a href="$MySelf?action=browse">Back</a>]</div>\n);
237         }
238 }
239
240 sub action_detail
241 {
242         my $cn = param ('cn');
243         $cn = shift if (@_);
244         die unless ($cn);
245
246         my $person = Person->load ($cn);
247         if (!$person)
248         {
249                 print qq(\t<div>Entry &quot;$cn&quot; could not be loaded from DB.</div>\n);
250                 return;
251         }
252
253         print qq(\t\t<h2>Details for $cn</h2>\n);
254
255         my $cn_esc = uri_escape ($cn);
256
257         print <<EOF;
258                 <table class="detail">
259                         <tr>
260                                 <th>Name</th>
261                                 <td>$cn</td>
262                         </tr>
263 EOF
264         for (@MultiFields)
265         {
266                 my $field = $_;
267                 my $values = $person->get ($field);
268                 my $num = scalar (@$values);
269                 my $print = defined ($FieldNames{$field}) ? $FieldNames{$field} : $field;
270
271                 next unless ($num);
272
273                 print "\t\t\t<tr>\n";
274                 if ($num > 1)
275                 {
276                         print qq(\t\t\t\t<th rowspan="$num">$print</th>\n);
277                 }
278                 else
279                 {
280                         print qq(\t\t\t\t<th>$print</th>\n);
281                 }
282
283                 for (my $i = 0; $i < $num; $i++)
284                 {
285                         my $val = $values->[$i];
286
287                         if ($field eq 'group')
288                         {
289                                 my $val_esc = uri_escape ($val);
290                                 $val = qq(<a href="$MySelf?action=browse&group=$val_esc">$val</a>);
291                         }
292                         elsif ($field eq 'uri')
293                         {
294                                 my $uri = $val;
295                                 $uri = qq(http://$val) unless ($val =~ m#^[a-z]+://#);
296                                 $val = qq(<a href="$uri" class="extern">$val</a>);
297                         }
298                         elsif ($field eq 'mail')
299                         {
300                                 $val = qq(<a href="mailto:$val" class="mail">$val</a>);
301                         }
302                         
303                         print "\t\t\t<tr>\n" if ($i);
304                         print "\t\t\t\t<td>$val</td>\n",
305                         "\t\t\t</tr>\n";
306                 }
307         }
308         print <<EOF;
309                 </table>
310
311                 <div class="menu">
312                         [<a href="$MySelf?action=verify&cn=$cn_esc">Verify</a>]
313                         [<a href="$MySelf?action=vcard&cn=$cn_esc">vCard</a>]
314                         [<a href="$MySelf?action=edit&cn=$cn_esc">Edit</a>]
315                 </div>
316
317 EOF
318 }
319
320 sub action_search
321 {
322         my $search = param ('search');
323
324         $search ||= '';
325         $search =~ s/[^\s\w]//g;
326
327         if (!$search)
328         {
329                 print qq(\t<div class="error">Sorry, the empty search is not allowed.</div>\n);
330                 action_default ();
331                 return;
332         }
333
334         my @patterns = split (m/\s+/, $search);
335         my @filter = ();
336
337         for (@patterns)
338         {
339                 my $pattern = "$_*";
340                 push (@filter, [[lastname => $pattern], [firstname => $pattern]]);
341         }
342
343         my @matches = Person->search (@filter);
344
345         if (!@matches)
346         {
347                 print qq(\t<div>No entries matched your search.</div>\n);
348                 return;
349         }
350
351         if (scalar (@matches) == 1)
352         {
353                 my $person = shift (@matches);
354                 my $cn = $person->name ();
355                 action_detail ($cn);
356                 return;
357         }
358
359         print qq(\t<ul class="result">\n);
360         for (@matches)
361         {
362                 my $person = $_;
363                 my $cn = $person->name ();
364                 my $cn_esc = uri_escape ($cn);
365
366                 print qq(\t\t<li><a href="$MySelf?action=detail&cn=$cn_esc">$cn</a></li>\n);
367         }
368         print qq(\t</ul>\n);
369 }
370
371 sub action_edit
372 {
373         my %opts = @_;
374
375         my $cn = param ('cn');
376
377         $cn = $opts{'cn'} if (defined ($opts{'cn'}));
378         $cn ||= '';
379
380         if (!$UserID)
381         {
382                 $cn = $UserCN;
383         }
384
385         my $person;
386
387         my $lastname;
388         my $firstname;
389
390         my $contacts = {};
391         $contacts->{$_} = [] for (@MultiFields);
392
393         if ($cn)
394         {
395                 $person = Person->load ($cn);
396
397                 if (!$person)
398                 {
399                         print qq(\t<div class="error">Unable to load CN &quot;$cn&quot;. Sorry.</div>\n);
400                         return;
401                 }
402         
403                 $lastname    = $person->lastname ();
404                 $firstname   = $person->firstname ();
405
406                 for (@MultiFields)
407                 {
408                         $contacts->{$_} = $person->get ($_);
409                 }
410         }
411
412         $lastname    = param ('lastname')    if (param ('lastname')  and $UserID);
413         $firstname   = param ('firstname')   if (param ('firstname') and $UserID);
414
415         get_contacts ($contacts);
416         
417         $lastname    =   $opts{'lastname'}     if (defined ($opts{'lastname'}));
418         $firstname   =   $opts{'firstname'}    if (defined ($opts{'firstname'}));
419         for (@MultiFields)
420         {
421                 my $field = $_;
422                 @{$contacts->{$field}} = @{$opts{$field}} if (defined ($opts{$field}));
423         }
424
425         if ($cn)
426         {
427                 print "\t\t<h2>Edit contact $cn</h2>\n";
428         }
429         else
430         {
431                 print "\t\t<h2>Create new contact</h2>\n";
432         }
433
434         print <<EOF;
435                 <form action="$MySelf" method="post">
436                 <input type="hidden" name="action" value="save" />
437                 <input type="hidden" name="cn" value="$cn" />
438                 <table class="edit">
439                         <tr>
440                                 <th>Lastname</th>
441 EOF
442         if ($UserID)
443         {
444                 print qq(\t\t\t\t<td><input type="text" name="lastname" value="$lastname" /></td>\n);
445         }
446         else
447         {
448                 print qq(\t\t\t\t<td>$lastname</td>\n);
449         }
450         print <<EOF;
451                         </tr>
452                         <tr>
453                                 <th>Firstname</th>
454 EOF
455         if ($UserID)
456         {
457                 print qq(\t\t\t\t<td><input type="text" name="firstname" value="$firstname" /></td>\n);
458         }
459         else
460         {
461                 print qq(\t\t\t\t<td>$firstname</td>\n);
462         }
463         
464         print "\t\t\t</tr>\n";
465
466         for (@MultiFields)
467         {
468                 my $field = $_;
469                 my $print = defined ($FieldNames{$field}) ? $FieldNames{$field} : $field;
470                 my @values = @{$contacts->{$field}};
471
472                 push (@values, '');
473                 
474                 for (@values)
475                 {
476                         my $value = $_;
477
478                         print <<EOF;
479                         <tr>
480                                 <th>$print</th>
481                                 <td><input type="text" name="$field" value="$value" /></td>
482                         </tr>
483 EOF
484                 }
485         }
486
487         print <<EOF;
488                         <tr>
489                                 <th colspan="2" class="menu">
490 EOF
491         if ($UserID)
492         {
493                 print <<EOF;
494                                         <input type="submit" name="button" value="Cancel" />
495                                         <input type="submit" name="button" value="Apply" />
496 EOF
497         }
498         print <<EOF;
499                                         <input type="submit" name="button" value="Save" />
500                                 </th>
501                         </tr>
502                 </table>
503                 </form>
504 EOF
505 }
506
507 sub action_save
508 {
509         my $cn = $UserID ? param ('cn') : $UserCN;
510
511         if (verify_fields ())
512         {
513                 action_edit (cn => $cn);
514                 return;
515         }
516
517         if ($cn)
518         {
519                 action_update ();
520                 return;
521         }
522
523         die unless ($UserID);
524
525         my $button = lc (param ('button'));
526         $button ||= 'save';
527
528         if ($button eq 'cancel')
529         {
530                 action_browse ();
531                 return;
532         }
533
534         if (!param ('lastname') or !param ('firstname'))
535         {
536                 print qq(\t<div class="error">You have to give both, first and lastname, to identify this record.</div>\n);
537                 action_edit (cn => '');
538                 return;
539         }
540
541         my $lastname  = param ('lastname');
542         my $firstname = param ('firstname');
543
544         my $contacts = get_contacts ();
545
546         my $person = Person->create (lastname => $lastname, firstname => $firstname, %$contacts);
547
548         if (!$person)
549         {
550                 print qq(\t<div class="error">Unable to save entry. Sorry.</div>\n);
551                 return;
552         }
553         
554         $cn = $person->name ();
555
556         if ($button eq 'apply')
557         {
558                 action_edit (cn => $cn);
559         }
560         else
561         {
562                 action_detail ($cn);
563         }
564 }
565
566 sub action_update
567 {
568         my $cn = $UserID ? param ('cn') : $UserCN;
569         my $person = Person->load ($cn);
570
571         die unless ($person);
572
573         my $button = lc (param ('button'));
574         $button ||= 'save';
575
576         if ($UserID and $button eq 'cancel')
577         {
578                 action_detail ($cn);
579                 return;
580         }
581
582         if ($UserID)
583         {
584                 my $lastname  = param ('lastname');
585                 my $firstname = param ('firstname');
586
587                 $person->lastname  ($lastname)  if ($lastname  and $lastname  ne $person->lastname ());
588                 $person->firstname ($firstname) if ($firstname and $firstname ne $person->firstname ());
589
590                 $cn = $person->name ();
591         }
592
593         my $contacts = get_contacts ();
594
595         for (@MultiFields)
596         {
597                 my $field = $_;
598                 
599                 if (defined ($contacts->{$field}))
600                 {
601                         my $values = $contacts->{$field};
602                         $person->set ($field, $values);
603                 }
604                 else
605                 {
606                         $person->set ($field, []);
607                 }
608         }
609
610         if ($button eq 'apply' or !$UserID)
611         {
612                 action_edit (cn => $cn);
613         }
614         else
615         {
616                 action_detail ($cn);
617         }
618 }
619
620 sub action_vcard
621 {
622         my $cn = param ('cn');
623         $cn = shift if (@_);
624         die unless ($cn);
625
626         my $person = Person->load ($cn);
627         die unless ($person);
628
629         my %vcard_types =
630         (
631                 homephone       => 'TEL;TYPE=home,voice',
632                 cellphone       => 'TEL;TYPE=cell',
633                 officephone     => 'TEL;TYPE=work,voice',
634                 fax             => 'TEL;TYPE=fax',
635                 mail            => 'EMAIL',
636                 uri             => 'URL',
637                 group           => 'ORG'
638         );
639
640         my $sn = $person->lastname ();
641         my $gn = $person->firstname ();
642         my $cn_esc = uri_escape ($cn);
643
644         print <<EOF;
645 Content-Type: text/x-vcard
646 Content-Disposition: attachment; filename="$cn.vcf"
647
648 BEGIN:VCARD
649 VERSION:3.0
650 FN: $cn
651 N: $sn;$gn
652 EOF
653
654         for (@MultiFields)
655         {
656                 my $field = $_;
657                 my $vc_fld = $vcard_types{$field};
658                 my $values = $person->get ($field);
659
660                 for (@$values)
661                 {
662                         my $value = $_;
663                         print "$vc_fld:$value\n";
664                 }
665         }
666         print "END:VCARD\n";
667 }
668
669 sub action_verify
670 {
671         my $cn = param ('cn');
672         $cn = shift if (@_);
673         die unless ($cn);
674
675         my $person = Person->load ($cn);
676         die unless ($person);
677
678         my ($mail) = $person->get ('mail');
679         $mail ||= '';
680
681         my $message;
682         my $password = $person->password ();
683
684         if (!$password)
685         {
686                 $password = pwgen ();
687                 $person->password ($password);
688         }
689
690         $message = qq(The password for the record &quot;$cn&quot; is &quot;$password&quot;.);
691
692         if ($mail)
693         {
694                 if (action_verify_send_mail ($person))
695                 {
696                         $message .= qq( A request for verification has been sent to $mail.);
697                 }
698         }
699         else
700         {
701                 $message .= q( There was no e-mail address, thus no verification request could be sent.);
702         }
703
704         print qq(\t\t<div class="message">$message</div>\n);
705
706         action_detail ($cn);
707 }
708
709 sub action_verify_send_mail
710 {
711         my $person = shift;
712         my $owner = Person->load ($UserCN);
713         my $smh;
714
715         my ($owner_mail) = $owner->get ('mail');
716         if (!$owner_mail)
717         {
718                 my $cn = uri_escape ($UserCN);
719                 print qq(\t\t<div class="error">You have no email set in your own profile. <a href="$MySelf?action=edit&cn=$cn">Edit it now</a>!</div>\n);
720                 return (0);
721         }
722
723         my $max_width = 0;
724         for (keys %FieldNames)
725         {
726                 $max_width = length $FieldNames{$_} if ($max_width < length $FieldNames{$_});
727         }
728         $max_width++;
729
730         my $person_name = $person->name ();
731         my ($person_mail) = $person->get ('mail');
732         my $person_gn = $person->firstname ();
733         my $password = $person->password ();
734
735         my $host = $ENV{'HTTP_HOST'};
736         my $url = 'http://' . $host . $MySelf;
737         
738         open ($smh, "| /usr/sbin/sendmail -t -f $owner_mail") or die ("open pipe to sendmail: $!");
739         print $smh <<EOM;
740 To: $person_name <$person_mail>
741 From: $UserCN <$owner_mail>
742 Subject: Please verify our entry in my address book
743
744 Hello $person_gn,
745
746 the following is your entry in my address book:
747 EOM
748         for (@MultiFields)
749         {
750                 my $field = $_;
751                 my $print = defined ($FieldNames{$field}) ? $FieldNames{$field} : $field;
752                 my @values = $person->get ($field);
753
754                 for (@values)
755                 {
756                         printf $smh ('%'.$max_width."s: %-s\n", $print, $_);
757                 }
758         }
759         print $smh <<EOM;
760
761 If this entry is outdated or incomplete, please take a minute and correct it.
762   Address:  $url
763  Username: $person_name
764  Password: $password
765
766 Thank you very much :) Regards,
767 $UserCN
768 EOM
769         close ($smh);
770
771         return (1);
772 }
773
774 sub html_start
775 {
776         my $title = shift;
777         $title = q(Lightweight Contact Manager) unless ($title);
778
779         print <<EOF;
780 Content-Type: text/html; charset=UTF-8
781
782 <html>
783         <head>
784                 <title>$title</title>
785                 <style type="text/css">
786                 <!--
787                 \@media screen
788                 {
789                         a
790                         {
791                                 color: blue;
792                                 background-color: inherit;
793                                 text-decoration: none;
794                         }
795
796                         a:hover
797                         {
798                                 text-decoration: underline;
799                         }
800
801                         a:visited
802                         {
803                                 color: navy;
804                                 background-color: inherit;
805                         }
806
807                         body
808                         {
809                                 color: black;
810                                 background-color: white;
811                         }
812
813                         div.error
814                         {
815                                 color: red;
816                                 background-color: yellow;
817
818                                 font-weight: bold;
819                                 padding: 1ex;
820                                 border: 2px solid red;
821                         }
822
823                         div.foot
824                         {
825                                 color: gray;
826                                 background-color: white;
827
828                                 position: fixed;
829                                 top: auto;
830                                 right: 0px;
831                                 bottom: 0px;
832                                 left: 0px;
833
834                                 font-size: x-small;
835                                 text-align: right;
836                                 border-top: 1px solid black;
837                                 width: 100%;
838                         }
839
840                         div.foot a
841                         {
842                                 color: black;
843                                 background-color: inherit;
844                                 text-decoration: none;
845                         }
846
847                         div.foot a:hover
848                         {
849                                 text-decoration: underline;
850                         }
851
852                         div.menu
853                         {
854                                 border-top: 1px solid black;
855                                 margin-top: 1ex;
856                                 font-weight: bold;
857                         }
858
859                         div.menu a
860                         {
861                                 color: blue;
862                                 background-color: transparent;
863                         }
864
865                         div.topmenu
866                         {
867                                 margin-bottom: 1ex;
868                                 padding-bottom: 1ex;
869                                 border-bottom: 1px solid black;
870                         }
871
872                         div.topmenu form
873                         {
874                                 display: inline;
875                                 margin-right: 5ex;
876                         }
877
878                         h1
879                         {
880                                 position: absolute;
881                                 top: 1ex;
882                                 right: 1ex;
883                                 bottom: auto;
884                                 left: auto;
885
886                                 font-size: 100%;
887                                 font-weight: bold;
888                         }
889
890                         img
891                         {
892                                 border: none;
893                         }
894
895                         table.list
896                         {
897                                 width: 100%;
898                         }
899
900                         table.list td
901                         {
902                                 empty-cells: show;
903                         }
904
905                         td
906                         {
907                                 color: black;
908                                 background-color: #cccccc;
909                                 vertical-align: top;
910                         }
911
912                         th
913                         {
914                                 color: black;
915                                 background-color: #999999;
916                                 padding: 0.3ex;
917                                 text-align: left;
918                                 vertical-align: top;
919                         }
920                 }
921
922                 \@media print
923                 {
924                         a
925                         {
926                                 color: inherit;
927                                 background-color: inherit;
928                                 text-decoration: underline;
929                         }
930                         
931                         div.topmenu, div.menu
932                         {
933                                 display: none;
934                         }
935
936                         div.foot
937                         {
938                                 font-size: 50%;
939                                 text-align: right;
940                         }
941
942                         h1
943                         {
944                                 display: none;
945                         }
946
947                         h2
948                         {
949                                 font-size: 100%;
950                         }
951
952                         table
953                         {
954                                 border-collapse: collapse;
955                         }
956
957                         table.list
958                         {
959                                 width: 100%;
960                         }
961
962                         table.list td
963                         {
964                                 empty-cells: show;
965                         }
966
967                         table.list th
968                         {
969                                 border-bottom-width: 2px;
970                         }
971
972                         td, th
973                         {
974                                 border: 1px solid black;
975                                 vertical-align: top;
976                         }
977
978                         th
979                         {
980                                 font-weight: bold;
981                                 text-align: center;
982                         }
983                 }
984                 //-->
985                 </style>
986         </head>
987
988         <body>
989 EOF
990         if ($UserID)
991         {
992                 my $search = param ('search') || '';
993                 print <<EOF;
994                 <div class="topmenu">
995                         <form action="$MySelf" method="post">
996                                 <input type="hidden" name="action" value="browse" />
997                                 <input type="submit" name="button" value="Browse" />
998                         </form>
999                         <form action="$MySelf" method="post">
1000                                 <input type="hidden" name="action" value="search" />
1001                                 <input type="text" name="search" value="$search" />
1002                                 <input type="submit" name="button" value="Search" />
1003                         </form>
1004                         <form action="$MySelf" method="post">
1005                                 <input type="hidden" name="action" value="edit" />
1006                                 <input type="hidden" name="dn" value="" />
1007                                 <input type="submit" name="button" value="Add New" />
1008                         </form>
1009                 </div>
1010 EOF
1011         }
1012         print "\t\t<h1>$title</h1>\n";
1013 }
1014
1015 sub html_end
1016 {
1017         print <<EOF;
1018                 <div class="foot">
1019                         &quot;Lightweight Contact Manager&quot;,
1020                         written 2005 by <a href="http://verplant.org/">Florian octo Forster</a>
1021                         &lt;octo at verplant.org&gt;
1022                 </div>
1023         </body>
1024 </html>
1025 EOF
1026 }
1027
1028 sub read_config
1029 {
1030         my $file = '/var/www/html/cgi.verplant.org/address/book.conf';
1031         my $fh;
1032
1033         open ($fh, "< $file") or die ("open ($file): $!");
1034         for (<$fh>)
1035         {
1036                 chomp;
1037                 my $line = $_;
1038
1039                 if ($line =~ m/^(\w+):\s*"(.+)"\s*$/)
1040                 {
1041                         my $key = lc ($1);
1042                         my $val = $2;
1043
1044                         $Config{$key} = $val;
1045                 }
1046         }
1047
1048         close ($fh);
1049
1050         for (qw(uri bind_dn password))
1051         {
1052                 die ("Not defined: $_") unless (defined ($Config{$_}));
1053         }
1054 }
1055
1056 sub pwgen
1057 {
1058         my $len = @_ ? shift : 6;
1059         my $retval = '';
1060
1061         while (!$retval)
1062         {
1063                 my $numbers = 0;
1064                 my $lchars  = 0;
1065                 my $uchars  = 0;
1066                 
1067                 while (length ($retval) < $len)
1068                 {
1069                         my $chr = int (rand (128));
1070
1071                         if ($chr >= 48 and $chr < 58)
1072                         {
1073                                 $numbers++;
1074                         }
1075                         elsif ($chr >= 65 and $chr < 91)
1076                         {
1077                                 $uchars++;
1078                         }
1079                         elsif ($chr >= 97 and $chr < 123)
1080                         {
1081                                 $lchars++;
1082                         }
1083                         else
1084                         {
1085                                 next;
1086                         }
1087                         $retval .= chr ($chr);
1088                 }
1089
1090                 $retval = '' if (!$numbers or !$lchars or !$uchars);
1091         }
1092
1093         return ($retval);
1094 }
1095
1096 sub verify_fields
1097 {
1098         my @errors = ();
1099         for (param ('uri'))
1100         {
1101                 my $val = $_;
1102                 next unless ($val);
1103
1104                 if ($val !~ m#^[a-zA-Z]+://#)
1105                 {
1106                         push (@errors, 'URIs have to begin with a protocol, e.g. &quot;http://&quot;, &quot;ftp://&quot; etc.');
1107                         last;
1108                 }
1109         }
1110
1111         for (param ('homephone'), param ('cellphone'), param ('officephone'), param ('fax'))
1112         {
1113                 my $number = $_;
1114                 next unless ($number);
1115
1116                 if ($number !~ m/^\+/)
1117                 {
1118                         push (@errors, 'Telephone numbers have to begin with the country code, e.g. &quot;+49 911 123456&quot;');
1119                         last;
1120                 }
1121         }
1122
1123         print qq(\t\t<div class="error">\n) if (@errors);
1124         for (my $i = 0; $i < scalar (@errors); $i++)
1125         {
1126                 my $e = $errors[$i];
1127
1128                 print "<br />\n" if ($i);
1129                 print "\t\t\t$e";
1130         }
1131         print qq(\n\t\t</div>\n\n) if (@errors);
1132
1133         return (scalar (@errors));
1134 }
1135
1136 sub get_contacts
1137 {
1138         my $contacts = @_ ? shift : {};
1139
1140         for (@MultiFields)
1141         {
1142                 my $field = $_;
1143                 my @values = grep { $_ } (param ($field));
1144
1145                 next unless (@values);
1146
1147                 if ($field eq 'homephone' or $field eq 'cellphone' or $field eq 'officephone' or $field eq 'fax')
1148                 {
1149                         for (@values)
1150                         {
1151                                 $_ =~ s/\D//g;
1152                                 $_ = '+' . $_;
1153                         }
1154                 }
1155                 
1156                 $contacts->{$field} = [@values] if (@values);
1157         }
1158
1159         return ($contacts);
1160 }