1<?php
2/**
3 * Copyright 2014-2016 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file COPYING for license information (LGPL). If you
6 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
7 *
8 * @category  Horde
9 * @copyright 2014-2016 Horde LLC
10 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
11 * @package   Mail_Autoconfig
12 */
13
14/**
15 * Perform RFC 6186 DNS SRV record lookups to determine mail configuration.
16 *
17 * @author    Michael Slusarz <slusarz@horde.org>
18 * @category  Horde
19 * @copyright 2014-2016 Horde LLC
20 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
21 * @package   Mail_Autoconfig
22 */
23class Horde_Mail_Autoconfig_Driver_Srv extends Horde_Mail_Autoconfig_Driver
24{
25    /**
26     * DNS resolver.
27     *
28     * @var Net_DNS2_Resolver
29     */
30    public $dns;
31
32    /**
33     * High priority: this is a standardized (RFC) method of determining
34     * configuration values.
35     */
36    public $priority = 10;
37
38    /**
39     */
40    public function msaSearch($domains, array $opts = array())
41    {
42        $queries = array('_submission');
43        return $this->_srvSearch($domains, $queries);
44    }
45
46    /**
47     */
48    public function mailSearch($domains, array $opts = array())
49    {
50        $queries = array();
51        if (empty($opts['no_imap'])) {
52            $queries[] = '_imap';
53            $queries[] = '_imaps';
54        }
55        if (empty($opts['no_pop3'])) {
56            $queries[] = '_pop3';
57            $queries[] = '_pop3s';
58        }
59
60        return $this->_srvSearch($domains, $queries);
61    }
62
63    /**
64     * Perform the SRV search.
65     *
66     * @param array $domains  List of domains to search.
67     * @param array $queries  The SRV queries to run.
68     *
69     * @return mixed  False if no servers found, or a list of server objects
70     *                in order of decreasing priority.
71     */
72    protected function _srvSearch($domains, $queries)
73    {
74        $obs = $out = array();
75
76        if (is_null($this->dns)) {
77            $this->dns = new Net_DNS2_Resolver();
78        }
79
80        foreach ($domains as $val) {
81            foreach ($queries as $val2) {
82                try {
83                    $res = $this->dns->query($val2 . '._tcp.' . $val, 'SRV');
84                    foreach ($res->answer as $val3) {
85                        if (strlen($val3->target)) {
86                            $val3->query = $val2;
87                            $obs[$val3->priority][] = $val3;
88                        }
89                    }
90                } catch (Net_DNS2_Exception $e) {
91                    // Not found; ignore.
92                }
93            }
94        }
95
96        if (empty($obs)) {
97            return false;
98        }
99
100        /* Sort via priority ranking. Lower value is higher priority. */
101        ksort($obs, SORT_NUMERIC);
102
103        foreach ($obs as $val) {
104            /* Do weight determination if a multiple servers have identical
105             * priorities. */
106            if (count($val) > 1) {
107                /* Weight determination algorithm is defined in RFC 2782.
108                 * First, move all entries with weight 0 to beginning of
109                 * list. */
110                $tmp = array();
111                foreach ($val as $key2 => $val2) {
112                    if (empty($val2->weight)) {
113                        $tmp[] = $val2;
114                        unset($val[$key2]);
115                    }
116                }
117                $tmp = array_merge($tmp, $val);
118
119                $val = array();
120
121                while (count($tmp) > 1) {
122                    $i = 0;
123
124                    /* Next, iterate over list and update the "running
125                     * sum": the incremental value of each entry's weight. */
126                    foreach ($tmp as $val2) {
127                        $i += $val2->weight;
128                        $val2->running = $i;
129                    }
130
131                    /* Finally, select a random number in the range of 0->$i.
132                     * The first entry in the list (sequentially) that has a
133                     * running total >= to this random number is the next
134                     * server in the priority list. */
135                    $rand = mt_rand(0, $i);
136                    foreach ($tmp as $key2 => $val2) {
137                        if ($val2->running >= $rand) {
138                            $val[] = $val2;
139                            /* Remove this server from the list. */
140                            unset($tmp[$key2]);
141                            break;
142                        }
143                    }
144
145                    /* Repeat until we have a single entry left in $tmp. */
146                }
147
148                /* One entry left in $tmp, so add to $val. */
149                $val[] = reset($tmp);
150            }
151
152            foreach ($val as $val2) {
153                switch ($val2->query) {
154                case '_imap':
155                    $tmp = new Horde_Mail_Autoconfig_Server_Imap();
156                    break;
157
158                case '_imaps':
159                    $tmp = new Horde_Mail_Autoconfig_Server_Imap();
160                    $tmp->tls = 'tls';
161                    break;
162
163                case '_pop3':
164                    $tmp = new Horde_Mail_Autoconfig_Server_Pop3();
165                    break;
166
167                case '_pop3s':
168                    $tmp = new Horde_Mail_Autoconfig_Server_Pop3();
169                    $tmp->tls = 'tls';
170                    break;
171
172                case '_submission':
173                    $tmp = new Horde_Mail_Autoconfig_Server_Msa();
174                    break;
175                }
176
177                $tmp->host = strval($val2->target);
178                $tmp->port = intval($val2->port);
179
180                $out[] = $tmp;
181            }
182        }
183
184        return $out;
185    }
186
187}
188