Update default backend configuration
[regano.git] / db_api.sql
blob6a366846b9c02ec9a311b31a87bb24f6458d662a
1 -- Regano database function definitions
2 --
3 -- Uses PostgreSQL extensions.
4 --
5 -- These functions are intended to be called from the Web UI or other frontend.
6 --
7 --  Regano is a domain registration system for OpenNIC TLDs written in
8 --  Perl.  This file is part of Regano.
9 --
10 --  Regano may be distributed under the same terms as Perl itself.  Of
11 --  particular importance, note that while regano is distributed in the
12 --  hope that it will be useful, there is NO WARRANTY OF ANY KIND
13 --  WHATSOEVER WHETHER EXPLICIT OR IMPLIED.
16 -- The type definitions in db_types.sql must already be installed.
17 -- The table definitions in db_tables.sql must already be installed.
18 -- The function definitions in db_functions.sql must already be installed.
19 -- The configuration in db_config.sql must be loaded for these to actually work.
21 -- Inquire about the status of a domain.
22 CREATE OR REPLACE FUNCTION regano_api.domain_status
23         (regano.dns_fqdn)
24         RETURNS regano.domain_status AS $$
25 DECLARE
26     name                ALIAS FOR $1;
28     domain_term         CONSTANT interval NOT NULL
29                             := (regano.config_get('domain/term')).interval;
31     max_expired_age     CONSTANT interval NOT NULL
32                             := (regano.config_get('domain/grace_period')).interval;
33     max_pending_age     CONSTANT interval NOT NULL
34                             := (regano.config_get('domain/pend_term')).interval;
36     active_domain       regano.domains%ROWTYPE;
38     primary_label       regano.dns_label;
39     tail                regano.dns_fqdn;
40 BEGIN
41     PERFORM *
42         FROM regano.bailiwicks
43         WHERE lower(domain_tail) = lower(name)
44             OR lower(domain_tail) = '.'||lower(name);
45     IF FOUND THEN
46         RETURN 'BAILIWICK';
47     END IF;
49     primary_label := substring(name from '^([^.]+)[.]');
50     tail := substring(name from '^[^.]+([.].+[.])$');
52     PERFORM * FROM regano.bailiwicks WHERE lower(domain_tail) = lower(tail);
53     IF NOT FOUND THEN
54         RETURN 'ELSEWHERE';
55     END IF;
57     PERFORM * FROM regano.reserved_domains
58                 WHERE domain_name = lower(primary_label);
59     IF FOUND THEN
60         RETURN 'RESERVED';
61     END IF;
63     -- clean up pending domains, then check if the requested domain is pending
64     DELETE FROM regano.pending_domains WHERE start < (now() - max_pending_age);
65     PERFORM * FROM regano.pending_domains
66                 WHERE lower(domain_name) = lower(primary_label)
67                     AND lower(domain_tail) = lower(tail);
68     IF FOUND THEN
69         RETURN 'PENDING';
70     END IF;
72     -- automatically renew system domains
73     UPDATE regano.domains
74         SET expiration = now() + domain_term
75         WHERE domain_name IN ('@', '@@')
76             AND expiration < now();
78     -- clean up expired domains, then check if the requested domain is active
79     DELETE FROM regano.domains WHERE expiration < (now() - max_expired_age);
80     SELECT * INTO active_domain
81         FROM regano.domains
82         WHERE (lower(primary_label) = lower(domain_name))
83             AND (lower(tail) = lower(domain_tail));
85     IF FOUND THEN
86         IF now() < active_domain.expiration THEN
87             RETURN 'REGISTERED';
88         ELSE
89             RETURN 'EXPIRED';
90         END IF;
91     END IF;
93     RETURN 'AVAILABLE';
94 END;
95 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
96 ALTER FUNCTION regano_api.domain_status (regano.dns_fqdn)
97         OWNER TO regano;
99 -- Inquire why a domain is reserved.
100 CREATE OR REPLACE FUNCTION regano_api.domain_reservation_reason
101         (regano.dns_fqdn)
102         RETURNS text AS $$
103 SELECT CASE WHEN regano_api.domain_status($1) <> 'RESERVED' THEN NULL
104             ELSE reason END
105         FROM regano.reserved_domains
106         WHERE domain_name = substring($1 from '^([^.]+)[.]')
107 $$ LANGUAGE SQL STABLE STRICT SECURITY DEFINER;
108 ALTER FUNCTION regano_api.domain_reservation_reason (regano.dns_fqdn)
109         OWNER TO regano;
111 -- Inquire how a domain is handled.
112 CREATE OR REPLACE FUNCTION regano_api.domain_mode
113         (regano.dns_fqdn)
114         RETURNS regano.domain_mode AS $$
115 DECLARE
116     name                ALIAS FOR $1;
118     active_domain       regano.domains%ROWTYPE;
119 BEGIN
120     SELECT * INTO active_domain
121         FROM regano.domains
122         WHERE lower(name) = lower(domain_name||domain_tail);
123     IF NOT FOUND OR now() > active_domain.expiration THEN
124         RETURN NULL;
125     END IF;
127     PERFORM * FROM regano.domain_records
128         WHERE domain_id = active_domain.id AND seq_no = 0 AND type = 'SOA';
129     IF FOUND THEN
130         RETURN 'HOSTED';
131     END IF;
133     PERFORM * FROM regano.domain_records r
134         WHERE domain_id = active_domain.id AND r.name = '@' AND type = 'NS';
135     IF FOUND THEN
136         RETURN 'DELEGATED';
137     END IF;
139     RETURN 'INLINE';
140 END;
141 $$ LANGUAGE plpgsql STABLE STRICT SECURITY DEFINER;
142 ALTER FUNCTION regano_api.domain_mode (regano.dns_fqdn)
143         OWNER TO regano;
145 -- Inquire when a domain was last updated.
146 CREATE OR REPLACE FUNCTION regano_api.domain_last_update
147         (regano.dns_fqdn)
148         RETURNS timestamp with time zone AS $$
149 DECLARE
150     name                ALIAS FOR $1;
152     timestamp           timestamp with time zone;
153 BEGIN
154     SELECT last_update INTO timestamp
155         FROM regano.domains
156         WHERE lower(domain_name||domain_tail) = lower(name);
157     IF FOUND THEN
158         RETURN timestamp;
159     END IF;
161     PERFORM *
162         FROM regano.bailiwicks
163         WHERE lower(domain_tail) = lower(name)
164             OR lower(domain_tail) = '.'||lower(name);
165     IF FOUND THEN
166         SELECT MAX(last_update) INTO timestamp
167             FROM regano.domains
168             WHERE lower(domain_tail) = lower(name)
169                 OR lower(domain_tail) = '.'||lower(name);
170         RETURN timestamp;
171     END IF;
173     RETURN NULL;
175 $$ LANGUAGE plpgsql STABLE STRICT SECURITY DEFINER;
176 ALTER FUNCTION regano_api.domain_last_update (regano.dns_fqdn)
177         OWNER TO regano;
180 -- Create a new user account.
181 CREATE OR REPLACE FUNCTION regano_api.user_register
182         (text, regano.password, text, text)
183         RETURNS void AS $$
184 DECLARE
185     username_           ALIAS FOR $1;
186     password_           ALIAS FOR $2;
187     contact_name        ALIAS FOR $3;
188     contact_email       ALIAS FOR $4;
190     crypt_alg           CONSTANT text NOT NULL
191                             := (regano.config_get('auth/crypt')).text;
192     crypt_iter          CONSTANT integer NOT NULL
193                             := (regano.config_get('auth/crypt')).number;
195     new_user_id         bigint; -- row ID of new user record
196 BEGIN
197     INSERT INTO users (username, password, contact_id)
198         VALUES (username_, ROW(password_.xdigest, password_.xsalt,
199                                 crypt(password_.digest,
200                                       gen_salt(crypt_alg, crypt_iter))), 1)
201         RETURNING id INTO STRICT new_user_id;
202     INSERT INTO contacts (owner_id, id, name, email)
203         VALUES (new_user_id, 1, contact_name, contact_email);
204 END;
205 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
206 ALTER FUNCTION regano_api.user_register (text, regano.password, text, text)
207         OWNER TO regano;
209 -- Get the external digest algorithm and salt for a user.
210 CREATE OR REPLACE FUNCTION regano_api.user_get_salt_info (text)
211         RETURNS regano.password AS $$
212 DECLARE
213     username_   ALIAS FOR $1;
215     password_   regano.password;
216 BEGIN
217     SELECT (password).xdigest, (password).xsalt INTO password_
218         FROM regano.users WHERE (username = username_);
219     IF NOT FOUND THEN
220         -- return an unspecified record to impede timing attacks
221         SELECT (password).xdigest, (password).xsalt INTO password_
222             FROM regano.users FETCH FIRST 1 ROW ONLY;
223     END IF;
225     RETURN password_;
226 END;
227 $$ LANGUAGE plpgsql STABLE STRICT SECURITY DEFINER;
228 ALTER FUNCTION regano_api.user_get_salt_info (text)
229         OWNER TO regano;
231 -- Begin a session for a user.
232 CREATE OR REPLACE FUNCTION regano_api.user_login
233         (text, regano.password)
234         RETURNS uuid AS $$
235 <<var>>
236 DECLARE
237     username    ALIAS FOR $1;
238     password    ALIAS FOR $2;
240     crypt_alg   CONSTANT text NOT NULL
241                     := (regano.config_get('auth/crypt')).text;
242     crypt_iter  CONSTANT integer NOT NULL
243                     := (regano.config_get('auth/crypt')).number;
244     max_age     CONSTANT interval NOT NULL
245                     := (regano.config_get('session/max_age')).interval;
247     user_id     bigint; -- row ID of user record
248     stored_pw   text;   -- password hash from database
249     session_id  uuid;   -- session ID
250 BEGIN
251     SELECT id, (regano.users.password).digest INTO user_id, stored_pw
252         FROM regano.users WHERE (regano.users.username = var.username);
253     IF NOT FOUND OR stored_pw = '!' THEN
254         -- fake a stored password to impede timing attacks
255         stored_pw := gen_salt(crypt_alg, crypt_iter);
256     END IF;
257     -- clean up expired sessions
258     DELETE FROM regano.sessions WHERE start < (CURRENT_TIMESTAMP - max_age);
259     -- verify password; note that a bare salt cannot match any hash
260     IF crypt(password.digest, stored_pw) = stored_pw THEN
261         -- login successful
262         INSERT INTO regano.sessions (id, user_id)
263             VALUES (gen_random_uuid(), user_id)
264             RETURNING id INTO STRICT session_id;
265         RETURN session_id;
266     ELSE
267         -- login failed
268         RETURN NULL;
269     END IF;
270 END;
271 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
272 ALTER FUNCTION regano_api.user_login (text, regano.password)
273         OWNER TO regano;
275 -- Change a logged-in user's password.
276 CREATE OR REPLACE FUNCTION regano_api.user_change_password
277         (uuid, regano.password, regano.password)
278         RETURNS boolean AS $$
279 DECLARE
280     session_id  ALIAS FOR $1;
281     old_pw      ALIAS FOR $2;
282     new_pw      ALIAS FOR $3;
284     crypt_alg   CONSTANT text NOT NULL
285                     := (regano.config_get('auth/crypt')).text;
286     crypt_iter  CONSTANT integer NOT NULL
287                     := (regano.config_get('auth/crypt')).number;
289     user_id     bigint; -- row ID of user record
290 BEGIN
291     SELECT regano.sessions.user_id INTO user_id
292         FROM regano.sessions WHERE id = session_id;
293     IF NOT FOUND THEN
294         RETURN FALSE;
295     END IF;
297     new_pw.digest := crypt(new_pw.digest, gen_salt(crypt_alg, crypt_iter));
299     UPDATE regano.users
300         SET password = new_pw
301         WHERE ((id = user_id) AND
302             (crypt(old_pw.digest, (regano.users.password).digest) =
303              (regano.users.password).digest));
304     RETURN FOUND;
305 END;
306 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
307 ALTER FUNCTION regano_api.user_change_password
308         (uuid, regano.password, regano.password)
309         OWNER TO regano;
311 -- Recover a lost password and open a session.
312 CREATE OR REPLACE FUNCTION regano_api.user_recover_password
313         (uuid, uuid, regano.password)
314         RETURNS uuid AS $$
315 DECLARE
316     verification_id     ALIAS FOR $1;
317     verification_key    ALIAS FOR $2;
318     new_pw              ALIAS FOR $3;
320     crypt_alg   CONSTANT text NOT NULL
321                     := (regano.config_get('auth/crypt')).text;
322     crypt_iter  CONSTANT integer NOT NULL
323                     := (regano.config_get('auth/crypt')).number;
325     verification        regano.contact_verifications%ROWTYPE;
326     session_id          uuid;
327 BEGIN
328     SELECT * INTO verification
329         FROM regano.contact_verifications
330         WHERE type = 'account_recovery'
331                 AND id = verification_id AND key = verification_key;
332     IF NOT FOUND THEN
333         RETURN NULL;
334     END IF;
336     new_pw.digest := crypt(new_pw.digest, gen_salt(crypt_alg, crypt_iter));
338     UPDATE regano.users
339         SET password = new_pw
340         WHERE id = verification.user_id;
341     UPDATE regano.contacts
342         SET email_verified = TRUE
343         WHERE owner_id = verification.user_id AND id = verification.contact_id;
345     INSERT INTO regano.sessions (id, user_id)
346         VALUES (gen_random_uuid(), verification.user_id)
347         RETURNING id INTO STRICT session_id;
349     DELETE
350         FROM regano.contact_verifications
351         WHERE id = verification_id;
352     RETURN session_id;
353 END;
354 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
355 ALTER FUNCTION regano_api.user_recover_password
356         (uuid, uuid, regano.password)
357         OWNER TO regano;
359 -- End a session.
360 CREATE OR REPLACE FUNCTION regano_api.session_logout (uuid) RETURNS void AS $$
361 DELETE FROM regano.sessions WHERE id = $1
362 $$ LANGUAGE SQL VOLATILE STRICT SECURITY DEFINER;
363 ALTER FUNCTION regano_api.session_logout (uuid)
364         OWNER TO regano;
366 -- Retrieve username for a session, update session activity timestamp, and
367 -- perform auto-logout if the session has expired.
368 CREATE OR REPLACE FUNCTION regano_api.session_check (uuid)
369         RETURNS text AS $$
370 <<var>>
371 DECLARE
372     id          ALIAS FOR $1;
374     max_age     CONSTANT interval NOT NULL
375                     := (regano.config_get('session/max_age')).interval;
376     max_idle    CONSTANT interval NOT NULL
377                     := (regano.config_get('session/max_idle')).interval;
379     session     regano.sessions%ROWTYPE;
380 BEGIN
381     SELECT * INTO session
382         FROM regano.sessions WHERE regano.sessions.id = var.id;
383     IF FOUND THEN
384         IF ((CURRENT_TIMESTAMP - session.activity) > max_idle) OR
385            ((CURRENT_TIMESTAMP - session.start) > max_age) THEN
386             -- session is expired
387             PERFORM regano_api.session_logout(session.id);
388             RETURN NULL;
389         ELSIF ((CURRENT_TIMESTAMP - session.activity) * 4 > max_idle) THEN
390             -- update activity timestamp
391             UPDATE regano.sessions
392                 SET activity = CURRENT_TIMESTAMP
393                 WHERE regano.sessions.id = var.id;
394         END IF;
395     ELSE
396         -- no such session exists
397         RETURN NULL;
398     END IF;
399     RETURN regano.username(session);
400 END;
401 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
402 ALTER FUNCTION regano_api.session_check (uuid)
403         OWNER TO regano;
405 -- Retrieve the current user's user record, sans password.
406 CREATE OR REPLACE FUNCTION regano_api.user_info
407         (session_id uuid)
408         RETURNS regano.users AS $$
409 DECLARE
410     user        regano.users%ROWTYPE;
411 BEGIN
412     SELECT * INTO STRICT user
413         FROM regano.users WHERE id = regano.session_user_id(session_id);
414     user.password := NULL;
415     RETURN user;
416 END;
417 $$ LANGUAGE plpgsql STABLE STRICT SECURITY DEFINER;
418 ALTER FUNCTION regano_api.user_info (uuid)
419         OWNER TO regano;
421 -- Retrieve the ID of the current user's primary contact record.
422 CREATE OR REPLACE FUNCTION regano_api.contact_primary_id
423         (session_id uuid)
424         RETURNS integer AS $$
425 SELECT contact_id FROM regano.users WHERE id = regano.session_user_id($1)
426 $$ LANGUAGE SQL STABLE STRICT SECURITY DEFINER;
427 ALTER FUNCTION regano_api.contact_primary_id (uuid)
428         OWNER TO regano;
430 -- Change the primary contact for the current user.
431 CREATE OR REPLACE FUNCTION regano_api.user_set_primary_contact
432         (session_id uuid, contact_id integer)
433         RETURNS void AS $$
434 DECLARE
435     domain_term         CONSTANT interval NOT NULL
436                             := (regano.config_get('domain/term')).interval;
438     contact             regano.contacts%ROWTYPE;
439     session             regano.sessions%ROWTYPE;
440     pending_domain      regano.pending_domains%ROWTYPE;
441 BEGIN
442     SELECT * INTO STRICT session FROM regano.sessions WHERE id = session_id;
443     SELECT * INTO STRICT contact FROM regano.contacts
444         WHERE owner_id = session.user_id AND id = contact_id;
446     IF NOT contact.email_verified THEN
447         RAISE EXCEPTION
448         'Only a verified email address may be set as primary contact.';
449     END IF;
450     -- update user record
451     UPDATE regano.users
452         SET contact_id = contact.id
453         WHERE id = session.user_id;
454     -- check for a pending domain
455     SELECT * INTO pending_domain
456         FROM regano.pending_domains
457         WHERE pending_domains.user_id = session.user_id;
458     IF FOUND THEN
459         -- register the pending domain
460         DELETE
461             FROM regano.pending_domains
462             WHERE domain_name = pending_domain.domain_name
463                 AND domain_tail = pending_domain.domain_tail;
464         INSERT INTO regano.domains
465             (domain_name, domain_tail, owner_id, expiration)
466             VALUES (pending_domain.domain_name, pending_domain.domain_tail,
467                     session.user_id, now() + domain_term);
468     END IF;
469 END;
470 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
471 ALTER FUNCTION regano_api.user_set_primary_contact (uuid, integer)
472         OWNER TO regano;
474 -- Retrieve all contact records belonging to the current user.
475 CREATE OR REPLACE FUNCTION regano_api.contact_list
476         (session_id uuid)
477         RETURNS SETOF regano.contacts AS $$
478 SELECT * FROM regano.contacts WHERE owner_id = regano.session_user_id($1)
479 $$ LANGUAGE SQL STABLE STRICT SECURITY DEFINER;
480 ALTER FUNCTION regano_api.contact_list (uuid)
481         OWNER TO regano;
483 -- Add a contact record for the current user.
484 CREATE OR REPLACE FUNCTION regano_api.contact_add
485         (session_id uuid, name text, email text)
486         RETURNS integer AS $$
487 INSERT INTO regano.contacts (owner_id, id, name, email)
488     VALUES (regano.session_user_id($1),
489             regano.contact_next_id(regano.session_user_id($1)), $2, $3)
490     RETURNING id;
491 $$ LANGUAGE SQL VOLATILE STRICT SECURITY DEFINER;
492 ALTER FUNCTION regano_api.contact_add (uuid, text, text)
493         OWNER TO regano;
495 -- Remove a contact record for the current user.
496 CREATE OR REPLACE FUNCTION regano_api.contact_remove
497         (session_id uuid, contact_id integer)
498         RETURNS void AS $$
499 DECLARE
500     user_id     CONSTANT bigint NOT NULL
501                     := regano.session_user_id(session_id);
502     renumbering CURSOR (user_id bigint)
503                     FOR SELECT id
504                             FROM regano.contacts
505                             WHERE owner_id = user_id
506                             ORDER BY id
507                             FOR UPDATE;
508     i           integer := 0;
509 BEGIN
510     DELETE
511         FROM regano.contacts
512         WHERE owner_id = user_id AND id = contact_id;
513     FOR contact IN renumbering (user_id) LOOP
514         i := i + 1;
515         UPDATE regano.contacts
516             SET id = i
517             WHERE CURRENT OF renumbering;
518     END LOOP;
520 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
521 ALTER FUNCTION regano_api.contact_remove (uuid, integer)
522         OWNER TO regano;
524 -- Update the name field of a contact record.
525 CREATE OR REPLACE FUNCTION regano_api.contact_update_name
526         (session_id uuid, contact_id integer, value text)
527         RETURNS void AS $$
528 DECLARE
529     contact     regano.contacts%ROWTYPE;
530     session     regano.sessions%ROWTYPE;
531 BEGIN
532     SELECT * INTO STRICT session FROM regano.sessions WHERE id = session_id;
533     SELECT * INTO STRICT contact FROM regano.contacts
534         WHERE owner_id = session.user_id AND id = contact_id
535         FOR UPDATE;
537     UPDATE regano.contacts
538         SET name = value
539         WHERE owner_id = session.user_id AND id = contact_id;
540 END;
541 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
542 ALTER FUNCTION regano_api.contact_update_name (uuid, integer, text)
543         OWNER TO regano;
545 -- Update the email address field of a contact record.
546 CREATE OR REPLACE FUNCTION regano_api.contact_update_email
547         (session_id uuid, contact_id integer, value text)
548         RETURNS void AS $$
549 DECLARE
550     contact             regano.contacts%ROWTYPE;
551     session             regano.sessions%ROWTYPE;
553     primary_contact_id  integer;
554 BEGIN
555     SELECT * INTO STRICT session FROM regano.sessions WHERE id = session_id;
556     SELECT users.contact_id INTO STRICT primary_contact_id
557         FROM regano.users WHERE id = session.user_id;
558     SELECT * INTO STRICT contact FROM regano.contacts
559         WHERE owner_id = session.user_id AND id = contact_id
560         FOR UPDATE;
562     IF contact_id = primary_contact_id AND contact.email_verified THEN
563         RAISE EXCEPTION
564         'Verified email address (%) for primary contact (%) may not be changed.',
565             contact.email, contact_id;
566     END IF;
568     -- cancel any in-progress address verification
569     DELETE FROM regano.contact_verifications
570         WHERE contact_verifications.user_id = session.user_id
571           AND contact_verifications.contact_id = contact.id;
572     -- change the stored email address
573     UPDATE regano.contacts
574         SET email_verified = FALSE, email = value
575         WHERE owner_id = session.user_id AND id = contact_id;
576 END;
577 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
578 ALTER FUNCTION regano_api.contact_update_email (uuid, integer, text)
579         OWNER TO regano;
581 -- Update the PGP key field of a contact record
582 CREATE OR REPLACE FUNCTION regano_api.contact_update_pgp_key
583         (session_id uuid, contact_id integer, value text)
584         RETURNS void AS $$
585 DECLARE
586     contact     regano.contacts%ROWTYPE;
587     session     regano.sessions%ROWTYPE;
588 BEGIN
589     SELECT * INTO STRICT session FROM regano.sessions WHERE id = session_id;
590     SELECT * INTO STRICT contact FROM regano.contacts
591         WHERE owner_id = session.user_id AND id = contact_id
592         FOR UPDATE;
595     IF char_length(value) > 0 THEN
596         UPDATE regano.contacts
597             SET pgp_key = value, pgp_key_id = pgp_key_id(dearmor(value))
598             WHERE owner_id = session.user_id AND id = contact_id;
599     ELSIF value IS NULL OR char_length(value) = 0 THEN
600         UPDATE regano.contacts
601             SET pgp_key = NULL, pgp_key_id = NULL
602             WHERE owner_id = session.user_id AND id = contact_id;
603     END IF;
604 END;
605 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
606 ALTER FUNCTION regano_api.contact_update_pgp_key (uuid, integer, text)
607         OWNER TO regano;
610 -- Begin the process of verifying a contact record.
611 CREATE OR REPLACE FUNCTION regano_api.contact_verify_begin
612         (session_id uuid, contact_id integer)
613         RETURNS void AS $$
614 DECLARE
615     contact     regano.contacts%ROWTYPE;
616     session     regano.sessions%ROWTYPE;
617 BEGIN
618     SELECT * INTO STRICT session FROM regano.sessions WHERE id = session_id;
619     SELECT * INTO STRICT contact FROM regano.contacts
620         WHERE owner_id = session.user_id AND id = contact_id;
622     DELETE FROM regano.contact_verifications
623         WHERE contact_verifications.contact_id = contact_verify_begin.contact_id
624           AND contact_verifications.user_id = session.user_id;
625     INSERT INTO regano.contact_verifications (id, key, type, user_id, contact_id)
626         VALUES (gen_random_uuid(), gen_random_uuid(), 'email_address',
627                 session.user_id, contact_id);
628     NOTIFY regano__contact_verifications;
629 END;
630 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
631 ALTER FUNCTION regano_api.contact_verify_begin (uuid, integer)
632         OWNER TO regano;
634 -- Complete verification of a contact record.
635 CREATE OR REPLACE FUNCTION regano_api.contact_verify_complete
636         (verification_id uuid, key uuid)
637         RETURNS boolean AS $$
638 <<var>>
639 DECLARE
640     domain_term         CONSTANT interval NOT NULL
641                             := (regano.config_get('domain/term')).interval;
642     max_age             CONSTANT interval NOT NULL
643                             := (regano.config_get('verify/max_age')).interval;
645     pending_domain      regano.pending_domains%ROWTYPE;
646     verification        regano.contact_verifications%ROWTYPE;
648     is_primary_contact  boolean;
649 BEGIN
650     -- clean up expired verifications
651     DELETE
652         FROM regano.contact_verifications
653         WHERE start < (CURRENT_TIMESTAMP - max_age);
654     -- look up the provided verification ID
655     SELECT * INTO verification
656         FROM regano.contact_verifications
657         WHERE (type = 'email_address') AND (id = verification_id) AND
658               (contact_verifications.key = contact_verify_complete.key);
659     IF NOT FOUND THEN
660         RETURN FALSE;
661     END IF;
662     -- mark email address as verified
663     UPDATE regano.contacts
664         SET email_verified = TRUE
665         WHERE owner_id = verification.user_id AND id = verification.contact_id;
666     -- check if a primary contact was verified
667     SELECT users.contact_id = verification.contact_id
668         INTO STRICT is_primary_contact
669         FROM regano.users
670         WHERE id = verification.user_id;
671     -- check for a pending domain
672     SELECT * INTO pending_domain
673         FROM regano.pending_domains
674         WHERE pending_domains.user_id = verification.user_id;
675     IF FOUND AND is_primary_contact THEN
676         -- register the pending domain
677         DELETE
678             FROM regano.pending_domains
679             WHERE domain_name = pending_domain.domain_name
680                 AND domain_tail = pending_domain.domain_tail;
681         INSERT INTO regano.domains
682             (domain_name, domain_tail, owner_id, expiration)
683             VALUES (pending_domain.domain_name, pending_domain.domain_tail,
684                     verification.user_id, now() + domain_term);
685     END IF;
686     -- clean up the successful verification
687     DELETE
688         FROM regano.contact_verifications
689         WHERE id = verification_id;
690     RETURN TRUE;
691 END;
692 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
693 ALTER FUNCTION regano_api.contact_verify_complete (uuid, uuid)
694         OWNER TO regano;
697 -- Retrieve information about a pending domain belonging to the current user.
698 CREATE OR REPLACE FUNCTION regano_api.domain_check_pending
699         (uuid)
700         RETURNS regano.pending_domain AS $$
701 SELECT  domain_name||domain_tail AS name,
702         start, start + regano.config_get('domain/pend_term') AS expire
703     FROM regano.pending_domains WHERE user_id = regano.session_user_id($1)
704 $$ LANGUAGE SQL STABLE STRICT SECURITY DEFINER;
705 ALTER FUNCTION regano_api.domain_check_pending (uuid)
706         OWNER TO regano;
708 -- Retrieve all domains belonging to the current user.
709 -- The domain table is public; this is for the account overview page.
710 CREATE OR REPLACE FUNCTION regano_api.domain_list
711         (uuid)
712         RETURNS SETOF regano.domain AS $$
713 SELECT domain_name||domain_tail AS name, registered, expiration, last_update,
714         CASE WHEN now() < expiration
715              THEN 'REGISTERED'::regano.domain_status
716              ELSE 'EXPIRED'::regano.domain_status
717         END AS status
718     FROM regano.domains WHERE owner_id = regano.session_user_id($1)
719 $$ LANGUAGE SQL STABLE STRICT SECURITY DEFINER;
720 ALTER FUNCTION regano_api.domain_list (uuid)
721         OWNER TO regano;
723 -- Register an available domain.
724 CREATE OR REPLACE FUNCTION regano_api.domain_register
725         (uuid, regano.dns_fqdn)
726         RETURNS regano.domain_status AS $$
727 <<var>>
728 DECLARE
729     session_id          ALIAS FOR $1;
730     name                ALIAS FOR $2;
732     domain_term         CONSTANT interval NOT NULL
733                             := (regano.config_get('domain/term')).interval;
735     user_id             CONSTANT bigint NOT NULL
736                             := regano.session_user_id(session_id);
738     verified            boolean;        -- verified email address on file?
740     primary_label       regano.dns_label;
741     tail                regano.dns_fqdn;
742 BEGIN
743     primary_label := substring(name from '^([^.]+)[.]');
744     tail := substring(name from '^[^.]+([.].+[.])$');
746     IF regano_api.domain_status(name) <> 'AVAILABLE' THEN
747         RETURN regano_api.domain_status(name);
748     END IF;
750     SELECT email_verified INTO STRICT verified
751         FROM regano.users JOIN regano.contacts
752             ON owner_id = user_id AND contact_id = contacts.id
753         WHERE regano.users.id = user_id;
755     IF verified THEN
756         -- user has a verified email address; register the domain now
757         INSERT INTO regano.domains
758             (domain_name, domain_tail, owner_id, expiration)
759             VALUES (primary_label, tail, user_id, now() + domain_term);
760         RETURN 'REGISTERED';
761     ELSE
762         -- no verified email address on file; registration will be pending
763         INSERT INTO regano.pending_domains
764             (domain_name, domain_tail, user_id)
765             VALUES (primary_label, tail, user_id);
766         RETURN 'PENDING';
767     END IF;
768 END;
769 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
770 ALTER FUNCTION regano_api.domain_register (uuid, regano.dns_fqdn)
771         OWNER TO regano;
773 -- Renew a domain
774 CREATE OR REPLACE FUNCTION regano_api.domain_renew
775         (uuid, regano.dns_fqdn)
776         RETURNS timestamp with time zone AS $$
777 DECLARE
778     session_id          ALIAS FOR $1;
779     name                ALIAS FOR $2;
781     domain_term         CONSTANT interval NOT NULL
782                             := (regano.config_get('domain/term')).interval;
784     user_id             CONSTANT bigint NOT NULL
785                             := regano.session_user_id(session_id);
787     primary_label       regano.dns_label;
788     tail                regano.dns_fqdn;
790     domain              regano.domains%ROWTYPE;
791     result              timestamp with time zone;
792 BEGIN
793     primary_label := substring(name from '^([^.]+)[.]');
794     tail := substring(name from '^[^.]+([.].+[.])$');
796     SELECT * INTO STRICT domain
797         FROM regano.domains
798         WHERE (lower(primary_label) = lower(domain_name))
799             AND (lower(tail) = lower(domain_tail))
800         FOR UPDATE;
802     IF user_id <> domain.owner_id THEN
803         RAISE EXCEPTION
804         'attempt made to renew domain (%) not belonging to current user (%)',
805             name, regano.username(session_id);
806     END IF;
808     UPDATE regano.domains
809         SET expiration = now() + domain_term,
810             last_update = now()
811         WHERE id = domain.id
812         RETURNING expiration INTO STRICT result;
813     RETURN result;
815 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
816 ALTER FUNCTION regano_api.domain_renew (uuid, regano.dns_fqdn)
817         OWNER TO regano;
819 -- Immediately expire a domain
820 CREATE OR REPLACE FUNCTION regano_api.domain_release
821         (uuid, regano.dns_fqdn)
822         RETURNS void AS $$
823 DECLARE
824     session_id          ALIAS FOR $1;
825     name                ALIAS FOR $2;
827     user_id             CONSTANT bigint NOT NULL
828                             := regano.session_user_id(session_id);
830     primary_label       regano.dns_label;
831     tail                regano.dns_fqdn;
833     domain              regano.domains%ROWTYPE;
834 BEGIN
835     primary_label := substring(name from '^([^.]+)[.]');
836     tail := substring(name from '^[^.]+([.].+[.])$');
838     PERFORM *
839         FROM regano.reserved_domains
840         WHERE (domain_name = lower(primary_label));
841     IF FOUND THEN
842         RETURN;
843     END IF;
845     SELECT * INTO STRICT domain
846         FROM regano.domains
847         WHERE (lower(primary_label) = lower(domain_name))
848             AND (lower(tail) = lower(domain_tail))
849         FOR UPDATE;
851     IF user_id <> domain.owner_id THEN
852         RAISE EXCEPTION
853         'attempt made to release domain (%) not belonging to current user (%)',
854             name, regano.username(session_id);
855     END IF;
857     UPDATE regano.domains
858         SET expiration = now(),
859             last_update = now()
860         WHERE id = domain.id;
862 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
863 ALTER FUNCTION regano_api.domain_release (uuid, regano.dns_fqdn)
864         OWNER TO regano;
866 -- Set default TTL for records in a domain
867 CREATE OR REPLACE FUNCTION regano_api.domain_set_default_ttl
868         (uuid, regano.dns_fqdn, regano.dns_interval)
869         RETURNS void AS $$
870 DECLARE
871     session_id          ALIAS FOR $1;
872     name                ALIAS FOR $2;
873     new_ttl             ALIAS FOR $3;
875     user_id             CONSTANT bigint NOT NULL
876                             := regano.session_user_id(session_id);
878     primary_label       regano.dns_label;
879     tail                regano.dns_fqdn;
881     domain              regano.domains%ROWTYPE;
882 BEGIN
883     primary_label := substring(name from '^([^.]+)[.]');
884     tail := substring(name from '^[^.]+([.].+[.])$');
886     SELECT * INTO STRICT domain
887         FROM regano.domains
888         WHERE (lower(primary_label) = lower(domain_name))
889             AND (lower(tail) = lower(domain_tail))
890         FOR UPDATE;
892     IF user_id <> domain.owner_id THEN
893         RAISE EXCEPTION
894         'attempt made to set TTL for domain (%) not belonging to current user (%)',
895             name, regano.username(session_id);
896     END IF;
898     UPDATE regano.domains
899         SET default_ttl = new_ttl,
900             last_update = now()
901         WHERE id = domain.id;
903 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
904 ALTER FUNCTION regano_api.domain_set_default_ttl
905         (uuid, regano.dns_fqdn, regano.dns_interval)
906         OWNER TO regano;
908 -- Updating domain records is done in multiple steps, all in a single
909 -- database transaction.  First, the existing records for the domain are
910 -- removed.  Second, new records are inserted in order.  Third, the
911 -- database transaction is committed.
913 -- Remove existing records for a domain
914 CREATE OR REPLACE FUNCTION regano_api.zone_clear
915         (session_id     uuid,
916          zone_name      regano.dns_fqdn)
917         RETURNS void AS $$
918 DECLARE
919     domain              regano.domains%ROWTYPE;
920 BEGIN
921     domain := regano.zone_verify_access(session_id, zone_name, 'clear zone');
923     DELETE
924         FROM regano.domain_records
925         WHERE domain_id = domain.id;
926     UPDATE regano.domains
927         SET last_update = now()
928         WHERE id = domain.id;
929     NOTIFY regano__domain_records;
931 $$ LANGUAGE plpgsql VOLATILE STRICT SECURITY DEFINER;
932 ALTER FUNCTION regano_api.zone_clear (uuid, regano.dns_fqdn)
933         OWNER TO regano;
935 -- Add an SOA record for a domain
936 -- NOTE: A domain may only have one SOA record, at the domain root, with
937 -- sequence number zero.
938 CREATE OR REPLACE FUNCTION regano_api.zone_add_SOA
939         (session_id     uuid,
940          zone_name      regano.dns_fqdn,
941          rec_ttl        regano.dns_interval,
942          SOA_master     regano.dns_fqdn,
943          SOA_mbox       regano.dns_email,
944          SOA_refresh    regano.dns_interval,
945          SOA_retry      regano.dns_interval,
946          SOA_expire     regano.dns_interval,
947          SOA_minimum    regano.dns_interval)
948         RETURNS void AS $$
949 DECLARE
950     domain              regano.domains%ROWTYPE;
951 BEGIN
952     domain := regano.zone_verify_access(session_id, zone_name, 'add SOA');
954     INSERT INTO regano.domain_records
955         (domain_id, seq_no, type, name, ttl, data_RR_SOA)
956         VALUES (domain.id, 0, 'SOA', '@', rec_ttl,
957                 ROW(SOA_master, SOA_mbox, SOA_refresh, SOA_retry,
958                     SOA_expire, SOA_minimum));
959     UPDATE regano.domains
960         SET last_update = now()
961         WHERE id = domain.id;
963 $$ LANGUAGE plpgsql VOLATILE CALLED ON NULL INPUT SECURITY DEFINER;
964 ALTER FUNCTION regano_api.zone_add_SOA
965         (uuid, regano.dns_fqdn, regano.dns_interval, regano.dns_fqdn,
966          regano.dns_email, regano.dns_interval, regano.dns_interval,
967          regano.dns_interval, regano.dns_interval)
968         OWNER TO regano;
970 -- Add a record that stores another DNS name
971 CREATE OR REPLACE FUNCTION regano_api.zone_add_name
972         (session_id     uuid,
973          zone_name      regano.dns_fqdn,
974          rec_ttl        regano.dns_interval,
975          rec_name       regano.dns_name,
976          rec_type       regano.dns_record_type,
977          rec_data       regano.dns_name)
978         RETURNS void AS $$
979 DECLARE
980     domain              regano.domains%ROWTYPE;
981     new_seq_no          bigint;
982     rec_name_c          CONSTANT regano.dns_name NOT NULL
983                             := regano.canonicalize_record_name(rec_name,
984                                                                zone_name);
985 BEGIN
986     domain := regano.zone_verify_access(session_id, zone_name, 'add name');
987     new_seq_no := regano.zone_next_seq_no(domain.id);
989     INSERT INTO regano.domain_records
990         (domain_id, seq_no, type, name, ttl, data_name)
991         VALUES (domain.id, new_seq_no, rec_type, rec_name_c, rec_ttl, rec_data);
992     UPDATE regano.domains
993         SET last_update = now()
994         WHERE id = domain.id;
996 $$ LANGUAGE plpgsql VOLATILE CALLED ON NULL INPUT SECURITY DEFINER;
997 ALTER FUNCTION regano_api.zone_add_name
998         (uuid, regano.dns_fqdn, regano.dns_interval, regano.dns_name,
999          regano.dns_record_type, regano.dns_name)
1000         OWNER TO regano;
1002 -- Add a record that stores free-form text
1003 CREATE OR REPLACE FUNCTION regano_api.zone_add_text
1004         (session_id     uuid,
1005          zone_name      regano.dns_fqdn,
1006          rec_ttl        regano.dns_interval,
1007          rec_name       regano.dns_name,
1008          rec_type       regano.dns_record_type,
1009          rec_data       text)
1010         RETURNS void AS $$
1011 DECLARE
1012     domain              regano.domains%ROWTYPE;
1013     new_seq_no          bigint;
1014     rec_name_c          CONSTANT regano.dns_name NOT NULL
1015                             := regano.canonicalize_record_name(rec_name,
1016                                                                zone_name);
1017 BEGIN
1018     domain := regano.zone_verify_access(session_id, zone_name, 'add text');
1019     new_seq_no := regano.zone_next_seq_no(domain.id);
1021     INSERT INTO regano.domain_records
1022         (domain_id, seq_no, type, name, ttl, data_text)
1023         VALUES (domain.id, new_seq_no, rec_type, rec_name_c, rec_ttl, rec_data);
1024     UPDATE regano.domains
1025         SET last_update = now()
1026         WHERE id = domain.id;
1028 $$ LANGUAGE plpgsql VOLATILE CALLED ON NULL INPUT SECURITY DEFINER;
1029 ALTER FUNCTION regano_api.zone_add_text
1030         (uuid, regano.dns_fqdn, regano.dns_interval, regano.dns_name,
1031          regano.dns_record_type, text)
1032         OWNER TO regano;
1034 -- Add an A record
1035 CREATE OR REPLACE FUNCTION regano_api.zone_add_A
1036         (session_id     uuid,
1037          zone_name      regano.dns_fqdn,
1038          rec_ttl        regano.dns_interval,
1039          rec_name       regano.dns_name,
1040          rec_data       regano.dns_RR_A)
1041         RETURNS void AS $$
1042 DECLARE
1043     domain              regano.domains%ROWTYPE;
1044     new_seq_no          bigint;
1045     rec_name_c          CONSTANT regano.dns_name NOT NULL
1046                             := regano.canonicalize_record_name(rec_name,
1047                                                                zone_name);
1048 BEGIN
1049     domain := regano.zone_verify_access(session_id, zone_name, 'add A');
1050     new_seq_no := regano.zone_next_seq_no(domain.id);
1052     INSERT INTO regano.domain_records
1053         (domain_id, seq_no, type, name, ttl, data_RR_A)
1054         VALUES (domain.id, new_seq_no, 'A', rec_name_c, rec_ttl, rec_data);
1055     UPDATE regano.domains
1056         SET last_update = now()
1057         WHERE id = domain.id;
1059 $$ LANGUAGE plpgsql VOLATILE CALLED ON NULL INPUT SECURITY DEFINER;
1060 ALTER FUNCTION regano_api.zone_add_A
1061         (uuid, regano.dns_fqdn, regano.dns_interval, regano.dns_name,
1062          regano.dns_RR_A)
1063         OWNER TO regano;
1065 -- Add an AAAA record
1066 CREATE OR REPLACE FUNCTION regano_api.zone_add_AAAA
1067         (session_id     uuid,
1068          zone_name      regano.dns_fqdn,
1069          rec_ttl        regano.dns_interval,
1070          rec_name       regano.dns_name,
1071          rec_data       regano.dns_RR_AAAA)
1072         RETURNS void AS $$
1073 DECLARE
1074     domain              regano.domains%ROWTYPE;
1075     new_seq_no          bigint;
1076     rec_name_c          CONSTANT regano.dns_name NOT NULL
1077                             := regano.canonicalize_record_name(rec_name,
1078                                                                zone_name);
1079 BEGIN
1080     domain := regano.zone_verify_access(session_id, zone_name, 'add AAAA');
1081     new_seq_no := regano.zone_next_seq_no(domain.id);
1083     INSERT INTO regano.domain_records
1084         (domain_id, seq_no, type, name, ttl, data_RR_AAAA)
1085         VALUES (domain.id, new_seq_no, 'AAAA', rec_name_c, rec_ttl, rec_data);
1086     UPDATE regano.domains
1087         SET last_update = now()
1088         WHERE id = domain.id;
1090 $$ LANGUAGE plpgsql VOLATILE CALLED ON NULL INPUT SECURITY DEFINER;
1091 ALTER FUNCTION regano_api.zone_add_AAAA
1092         (uuid, regano.dns_fqdn, regano.dns_interval, regano.dns_name,
1093          regano.dns_RR_AAAA)
1094         OWNER TO regano;
1096 -- Add a DS record
1097 CREATE OR REPLACE FUNCTION regano_api.zone_add_DS
1098         (session_id     uuid,
1099          zone_name      regano.dns_fqdn,
1100          rec_ttl        regano.dns_interval,
1101          rec_name       regano.dns_name,
1102          DS_key_tag     regano.uint16bit,
1103          DS_algorithm   regano.uint8bit,
1104          DS_digest_type regano.uint8bit,
1105          DS_digest      regano.hexstring)
1106         RETURNS void AS $$
1107 DECLARE
1108     domain              regano.domains%ROWTYPE;
1109     new_seq_no          bigint;
1110     rec_name_c          CONSTANT regano.dns_name NOT NULL
1111                             := regano.canonicalize_record_name(rec_name,
1112                                                                zone_name);
1113 BEGIN
1114     domain := regano.zone_verify_access(session_id, zone_name, 'add DS');
1115     new_seq_no := regano.zone_next_seq_no(domain.id);
1117     INSERT INTO regano.domain_records
1118         (domain_id, seq_no, type, name, ttl, data_RR_DS)
1119         VALUES (domain.id, new_seq_no, 'DS', rec_name_c, rec_ttl,
1120                 ROW(DS_key_tag, DS_algorithm, DS_digest_type, DS_digest));
1121     UPDATE regano.domains
1122         SET last_update = now()
1123         WHERE id = domain.id;
1125 $$ LANGUAGE plpgsql VOLATILE CALLED ON NULL INPUT SECURITY DEFINER;
1126 ALTER FUNCTION regano_api.zone_add_DS
1127         (uuid, regano.dns_fqdn, regano.dns_interval, regano.dns_name,
1128          regano.uint16bit, regano.uint8bit, regano.uint8bit,
1129          regano.hexstring)
1130         OWNER TO regano;
1132 -- Add an MX record
1133 CREATE OR REPLACE FUNCTION regano_api.zone_add_MX
1134         (session_id     uuid,
1135          zone_name      regano.dns_fqdn,
1136          rec_ttl        regano.dns_interval,
1137          rec_name       regano.dns_name,
1138          MX_preference  regano.uint16bit,
1139          MX_exchange    regano.dns_name)
1140         RETURNS void AS $$
1141 DECLARE
1142     domain              regano.domains%ROWTYPE;
1143     new_seq_no          bigint;
1144     rec_name_c          CONSTANT regano.dns_name NOT NULL
1145                             := regano.canonicalize_record_name(rec_name,
1146                                                                zone_name);
1147 BEGIN
1148     domain := regano.zone_verify_access(session_id, zone_name, 'add MX');
1149     new_seq_no := regano.zone_next_seq_no(domain.id);
1151     INSERT INTO regano.domain_records
1152         (domain_id, seq_no, type, name, ttl, data_RR_MX)
1153         VALUES (domain.id, new_seq_no, 'MX', rec_name_c, rec_ttl,
1154                 ROW(MX_preference, MX_exchange));
1155     UPDATE regano.domains
1156         SET last_update = now()
1157         WHERE id = domain.id;
1159 $$ LANGUAGE plpgsql VOLATILE CALLED ON NULL INPUT SECURITY DEFINER;
1160 ALTER FUNCTION regano_api.zone_add_MX
1161         (uuid, regano.dns_fqdn, regano.dns_interval, regano.dns_name,
1162          regano.uint16bit, regano.dns_name)
1163         OWNER TO regano;
1165 -- Add a SRV record
1166 CREATE OR REPLACE FUNCTION regano_api.zone_add_SRV
1167         (session_id     uuid,
1168          zone_name      regano.dns_fqdn,
1169          rec_ttl        regano.dns_interval,
1170          rec_name       regano.dns_name,
1171          SRV_priority   regano.uint16bit,
1172          SRV_weight     regano.uint16bit,
1173          SRV_port       regano.uint16bit,
1174          SRV_target     regano.dns_fqdn)
1175         RETURNS void AS $$
1176 DECLARE
1177     domain              regano.domains%ROWTYPE;
1178     new_seq_no          bigint;
1179     rec_name_c          CONSTANT regano.dns_name NOT NULL
1180                             := regano.canonicalize_record_name(rec_name,
1181                                                                zone_name);
1182 BEGIN
1183     domain := regano.zone_verify_access(session_id, zone_name, 'add SRV');
1184     new_seq_no := regano.zone_next_seq_no(domain.id);
1186     INSERT INTO regano.domain_records
1187         (domain_id, seq_no, type, name, ttl, data_RR_SRV)
1188         VALUES (domain.id, new_seq_no, 'SRV', rec_name_c, rec_ttl,
1189                 ROW(SRV_priority, SRV_weight, SRV_port, SRV_target));
1190     UPDATE regano.domains
1191         SET last_update = now()
1192         WHERE id = domain.id;
1194 $$ LANGUAGE plpgsql VOLATILE CALLED ON NULL INPUT SECURITY DEFINER;
1195 ALTER FUNCTION regano_api.zone_add_SRV
1196         (uuid, regano.dns_fqdn, regano.dns_interval, regano.dns_name,
1197          regano.uint16bit, regano.uint16bit, regano.uint16bit,
1198          regano.dns_fqdn)
1199         OWNER TO regano;