use Net::Whois;
[freeside.git] / site_perl / svc_domain.pm
1 package FS::svc_domain;
2
3 use strict;
4 use vars qw( @ISA $whois_hack $conf $mydomain $smtpmachine
5   $tech_contact $from $to @nameservers @nameserver_ips @template
6 );
7 use Carp;
8 use Mail::Internet;
9 use Mail::Header;
10 use Date::Format;
11 use Net::Whois; #0.24;
12 use FS::Record qw(fields qsearch qsearchs);
13 use FS::Conf;
14 use FS::svc_Common;
15 use FS::cust_svc;
16 use FS::svc_acct;
17 use FS::cust_pkg;
18 use FS::cust_main;
19
20 @ISA = qw( FS::svc_Common );
21
22 #ask FS::UID to run this stuff for us later
23 $FS::UID::callback{'FS::domain'} = sub { 
24   $conf = new FS::Conf;
25
26   $mydomain = $conf->config('domain');
27   $smtpmachine = $conf->config('smtpmachine');
28
29   my($internic)="/registries/internic";
30   $tech_contact = $conf->config("$internic/tech_contact");
31   $from = $conf->config("$internic/from");
32   $to = $conf->config("$internic/to");
33   my(@ns) = $conf->config("$internic/nameservers");
34   @nameservers=map {
35     /^\s*\d+\.\d+\.\d+\.\d+\s+([^\s]+)\s*$/
36       or die "Illegal line in $internic/nameservers";
37     $1;
38   } @ns;
39   @nameserver_ips=map {
40     /^\s*(\d+\.\d+\.\d+\.\d+)\s+([^\s]+)\s*$/
41       or die "Illegal line in $internic/nameservers!";
42     $1;
43   } @ns;
44   @template = map { $_. "\n" } $conf->config("$internic/template");
45
46 };
47
48 =head1 NAME
49
50 FS::svc_domain - Object methods for svc_domain records
51
52 =head1 SYNOPSIS
53
54   use FS::svc_domain;
55
56   $record = new FS::svc_domain \%hash;
57   $record = new FS::svc_domain { 'column' => 'value' };
58
59   $error = $record->insert;
60
61   $error = $new_record->replace($old_record);
62
63   $error = $record->delete;
64
65   $error = $record->check;
66
67   $error = $record->suspend;
68
69   $error = $record->unsuspend;
70
71   $error = $record->cancel;
72
73 =head1 DESCRIPTION
74
75 An FS::svc_domain object represents a domain.  FS::svc_domain inherits from
76 FS::svc_Common.  The following fields are currently supported:
77
78 =over 4
79
80 =item svcnum - primary key (assigned automatically for new accounts)
81
82 =item domain
83
84 =back
85
86 =head1 METHODS
87
88 =over 4
89
90 =item new HASHREF
91
92 Creates a new domain.  To add the domain to the database, see L<"insert">.
93
94 =cut
95
96 sub table { 'svc_domain'; }
97
98 =item insert
99
100 Adds this domain to the database.  If there is an error, returns the error,
101 otherwise returns false.
102
103 The additional fields I<pkgnum> and I<svcpart> (see L<FS::cust_svc>) should be 
104 defined.  An FS::cust_svc record will be created and inserted.
105
106 The additional field I<action> should be set to I<N> for new domains or I<M>
107 for transfers.
108
109 A registration or transfer email will be submitted unless
110 $FS::svc_domain::whois_hack is true.
111
112 The additional field I<email> can be used to manually set the admin contact
113 email address on this email.  Otherwise, the svc_acct records for this package 
114 (see L<FS::cust_pkg>) are searched.  If there is exactly one svc_acct record
115 in the same package, it is automatically used.  Otherwise an error is returned.
116
117 =cut
118
119 sub insert {
120   my $self = shift;
121   my $error;
122
123   local $SIG{HUP} = 'IGNORE';
124   local $SIG{INT} = 'IGNORE';
125   local $SIG{QUIT} = 'IGNORE';
126   local $SIG{TERM} = 'IGNORE';
127   local $SIG{TSTP} = 'IGNORE';
128   local $SIG{PIPE} = 'IGNORE';
129
130   $error = $self->check;
131   return $error if $error;
132
133   return "Domain in use (here)"
134     if qsearchs( 'svc_domain', { 'domain' => $self->domain } );
135
136   my $whois = $self->whois;
137   return "Domain in use (see whois)"
138     if ( $self->action eq "N" && $whois );
139   return "Domain not found (see whois)"
140     if ( $self->action eq "M" && ! $whois );
141
142   $error = $self->SUPER::insert;
143   return $error if $error;
144
145   $self->submit_internic unless $whois_hack;
146
147   ''; #no error
148 }
149
150 =item delete
151
152 Deletes this domain from the database.  If there is an error, returns the
153 error, otherwise returns false.
154
155 The corresponding FS::cust_svc record will be deleted as well.
156
157 =item replace OLD_RECORD
158
159 Replaces OLD_RECORD with this one in the database.  If there is an error,
160 returns the error, otherwise returns false.
161
162 =cut
163
164 sub replace {
165   my ( $new, $old ) = ( shift, shift );
166   my $error;
167
168   return "Can't change domain - reorder."
169     if $old->getfield('domain') ne $new->getfield('domain'); 
170
171   $new->SUPER::replace($old);
172
173 }
174
175 =item suspend
176
177 Just returns false (no error) for now.
178
179 Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
180
181 =item unsuspend
182
183 Just returns false (no error) for now.
184
185 Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
186
187 =item cancel
188
189 Just returns false (no error) for now.
190
191 Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
192
193 =item check
194
195 Checks all fields to make sure this is a valid domain.  If there is an error,
196 returns the error, otherwise returns false.  Called by the insert and replace
197 methods.
198
199 Sets any fixed values; see L<FS::part_svc>.
200
201 =cut
202
203 sub check {
204   my $self = shift;
205   my $error;
206
207   my $x = $self->setfixed;
208   return $x unless ref($x);
209   my $part_svc = $x;
210
211   #hmm
212   my $pkgnum;
213   if ( $self->svcnum ) {
214     my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $self->svcnum } );
215     $pkgnum = $cust_svc->pkgnum;
216   } else {
217     $pkgnum = $self->pkgnum;
218   }
219
220   my($recref) = $self->hashref;
221
222   unless ( $whois_hack ) {
223     unless ( $self->email ) { #find out an email address
224       my @svc_acct;
225       foreach ( qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } ) ) {
226         my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $_->svcnum } );
227         push @svc_acct, $svc_acct if $svc_acct;
228       }
229
230       if ( scalar(@svc_acct) == 0 ) {
231         return "Must order an account in package ". $pkgnum. " first";
232       } elsif ( scalar(@svc_acct) > 1 ) {
233         return "More than one account in package ". $pkgnum. ": specify admin contact email";
234       } else {
235         $self->email($svc_acct[0]->username. '@'. $mydomain);
236       }
237     }
238   }
239
240   #if ( $recref->{domain} =~ /^([\w\-\.]{1,22})\.(com|net|org|edu)$/ ) {
241   if ( $recref->{domain} =~ /^([\w\-]{1,22})\.(com|net|org|edu)$/ ) {
242     $recref->{domain} = "$1.$2";
243   # hmmmmmmmm.
244   } elsif ( $whois_hack && $recref->{domain} =~ /^([\w\-\.]+)$/ ) {
245     $recref->{domain} = $1;
246   } else {
247     return "Illegal domain ". $recref->{domain}.
248            " (or unknown registry - try \$whois_hack)";
249   }
250
251   $recref->{action} =~ /^(M|N)$/ or return "Illegal action";
252   $recref->{action} = $1;
253
254   $self->ut_textn('purpose');
255
256 }
257
258 =item whois
259
260 Returns the Net::Whois object corresponding to this domain, or undef if
261 the domain is not found in whois.
262
263 (If $FS::svc_domain::whois_hack is true, returns that in all cases instead.)
264
265 =cut
266
267 sub whois {
268   $whois_hack or new Net::Whois::Domain $_[0]->domain;
269 }
270
271 =item _whois
272
273 Depriciated.
274
275 =cut
276
277 sub _whois {
278   die "_whois depriciated";
279 }
280
281 =item submit_internic
282
283 Submits a registration email for this domain.
284
285 =cut
286
287 sub submit_internic {
288   my $self = shift;
289
290   my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
291   return unless $cust_pkg;
292   my $cust_main = qsearchs( 'cust_main', { 'custnum' => $cust_pkg->custnum } );
293   return unless $cust_main;
294
295   my %subs = (
296     'action'       => $self->action,
297     'purpose'      => $self->purpose,
298     'domain'       => $self->domain,
299     'company'      => $cust_main->company 
300                         || $cust_main->getfield('first'). ' '.
301                            $cust_main->getfield('last')
302                       ,
303     'city'         => $cust_main->city,
304     'state'        => $cust_main->state,
305     'zip'          => $cust_main->zip,
306     'country'      => $cust_main->country,
307     'last'         => $cust_main->getfield('last'),
308     'first'        => $cust_main->getfield('first'),
309     'daytime'      => $cust_main->daytime,
310     'fax'          => $cust_main->fax,
311     'email'        => $self->email,
312     'tech_contact' => $tech_contact,
313     'primary'      => shift @nameservers,
314     'primary_ip'   => shift @nameserver_ips,
315   );
316
317   #yuck
318   my @xtemplate = @template;
319   my @body;
320   my $line;
321   OLOOP: while ( defined( $line = shift @xtemplate ) ) {
322
323     if ( $line =~ /^###LOOP###$/ ) {
324       my(@buffer);
325       LOADBUF: while ( defined( $line = shift @xtemplate ) ) {
326         last LOADBUF if ( $line =~ /^###ENDLOOP###$/ );
327         push @buffer, $line;
328       }
329       my %lubs = (
330         'address'      => $cust_main->address2 
331                             ? [ $cust_main->address1, $cust_main->address2 ]
332                             : [ $cust_main->address1 ]
333                           ,
334         'secondary'    => [ @nameservers ],
335         'secondary_ip' => [ @nameserver_ips ],
336       );
337       LOOP: while (1) {
338         my @xbuffer = @buffer;
339         SUBLOOP: while ( defined( $line = shift @xbuffer ) ) {
340           if ( $line =~ /###(\w+)###/ ) {
341             #last LOOP unless my($lub)=shift@{$lubs{$1}};
342             next OLOOP unless my $lub = shift @{$lubs{$1}};
343             $line =~ s/###(\w+)###/$lub/e;
344             redo SUBLOOP;
345           } else {
346             push @body, $line;
347           }
348         } #SUBLOOP
349       } #LOOP
350
351     }
352
353     if ( $line =~ /###(\w+)###/ ) {
354       #$line =~ s/###(\w+)###/$subs{$1}/eg;
355       $line =~ s/###(\w+)###/$subs{$1}/e;
356       redo OLOOP;
357     } else {
358       push @body, $line;
359     }
360
361   } #OLOOP
362
363   my $subject;
364   if ( $self->action eq "M" ) {
365     $subject = "MODIFY DOMAIN ". $self->domain;
366   } elsif ( $self->action eq "N" ) { 
367     $subject = "NEW DOMAIN ". $self->domain;
368   } else {
369     croak "submit_internic called with action ". $self->action;
370   }
371
372   $ENV{SMTPHOSTS} = $smtpmachine;
373   $ENV{MAILADDRESS} = $from;
374   my $header = Mail::Header->new( [
375     "From: $from",
376     "To: $to",
377     "Sender: $from",
378     "Reply-To: $from",
379     "Date: ". time2str("%a, %d %b %Y %X %z", time),
380     "Subject: $subject",
381   ] );
382
383   my($msg)=Mail::Internet->new(
384     'Header' => $header,
385     'Body' => \@body,
386   );
387
388   $msg->smtpsend or die "Can't send registration email"; #die? warn?
389
390 }
391
392 =back
393
394 =head1 VERSION
395
396 $Id: svc_domain.pm,v 1.8 1999-08-03 07:43:33 ivan Exp $
397
398 =head1 BUGS
399
400 All BIND/DNS fields should be included (and exported).
401
402 Delete doesn't send a registration template.
403
404 All registries should be supported.
405
406 Should change action to a real field.
407
408 The $recref stuff in sub check should be cleaned up.
409
410 =head1 SEE ALSO
411
412 L<FS::svc_Common>, L<FS::Record>, L<FS::Conf>, L<FS::cust_svc>,
413 L<FS::part_svc>, L<FS::cust_pkg>, L<FS::SSH>, L<Net::Whois>, L<ssh>,
414 L<dot-qmail>, schema.html from the base documentation, config.html from the
415 base documentation.
416
417 =head1 HISTORY
418
419 ivan@voicenet.com 97-jul-21
420
421 rewrite ivan@sisd.com 98-mar-10
422
423 add internic bits ivan@sisd.com 98-mar-14
424
425 Changed 'day' to 'daytime' because Pg6.3 reserves the day word
426         bmccane@maxbaud.net     98-apr-3
427
428 /var/spool/freeside/conf/registries/internic/, Mail::Internet, etc.
429 ivan@sisd.com 98-jul-17-19
430
431 pod, some FS::Conf (not complete) ivan@sisd.com 98-sep-23
432
433 $Log: svc_domain.pm,v $
434 Revision 1.8  1999-08-03 07:43:33  ivan
435 use Net::Whois;
436
437 Revision 1.7  1999/04/07 14:40:15  ivan
438 use all stuff that's qsearch'ed to avoid warnings
439
440 Revision 1.6  1999/01/25 12:26:17  ivan
441 yet more mod_perl stuff
442
443 Revision 1.5  1998/12/30 00:30:47  ivan
444 svc_ stuff is more properly OO - has a common superclass FS::svc_Common
445
446 Revision 1.3  1998/11/13 09:56:57  ivan
447 change configuration file layout to support multiple distinct databases (with
448 own set of config files, export, etc.)
449
450 Revision 1.2  1998/10/14 08:18:21  ivan
451 More informative error messages and better doc for admin contact email stuff
452
453
454 =cut
455
456 1;
457
458