1<?php
2/***********************************************
3 * File      :   syslog.php
4 * Project   :   Z-Push
5 * Descr     :   Logging functionalities
6 *
7 * Created   :   13.11.2015
8 *
9 * Copyright 2007 - 2016 Zarafa Deutschland GmbH
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Affero General Public License, version 3,
13 * as published by the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
19 *
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 *
23 * Consult LICENSE file for details
24 ************************************************/
25
26class Syslog extends Log {
27
28    protected $program_name = '';
29
30    protected $host;
31
32    protected $port;
33
34    /**
35     * @access public
36     * @return string
37     */
38    public function GetProgramName() {
39        return $this->program_name;
40    }
41
42    /**
43     * @param string $value
44     *
45     * @access public
46     */
47    public function SetProgramName($value) {
48        $this->program_name = $value;
49    }
50
51    /**
52     * @access public
53     * @return string
54     */
55    public function GetHost() {
56        return $this->host;
57    }
58
59    /**
60     * @param string $value
61     *
62     * @access public
63     */
64    public function SetHost($value) {
65        $this->host = $value;
66    }
67
68    /**
69     * @access public
70     * @return int
71     */
72    public function GetPort() {
73        return $this->port;
74    }
75
76    /**
77     * @param int $value
78     *
79     * @access public
80     */
81    public function SetPort($value) {
82        if (is_numeric($value)) {
83            $this->port = (int)$value;
84        }
85    }
86
87    /**
88     * Constructor.
89     * Sets configured values if no parameters are given.
90     *
91     * @param string $program_name
92     * @param string $host
93     * @param string $port
94     */
95    public function __construct($program_name = null, $host = null, $port = null) {
96        parent::__construct();
97
98        if (is_null($program_name)) $program_name = LOG_SYSLOG_PROGRAM;
99        if (is_null($host)) $host = LOG_SYSLOG_HOST;
100        if (is_null($port)) $port = LOG_SYSLOG_PORT;
101
102        $this->SetProgramName($program_name);
103        $this->SetHost($host);
104        $this->SetPort($port);
105    }
106
107    /**
108     * Return the full program name for syslog.
109     * The name can be z-push/core or z-push/{backend} where backend is the backend that initiated the log.
110     *
111     * @access protected
112     * @return string
113     */
114    protected function GenerateProgramName() {
115        // @TODO Use another mechanism than debug_backtrace to determine to origin of the log
116        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
117        // Shift the "syslog.php" entry.
118        array_shift($backtrace);
119        foreach ($backtrace as $trace) {
120            if (!isset($trace['file'])) {
121                continue;
122            }
123            if (strpos($trace['file'], REAL_BASE_PATH . 'backend/') !== false) {
124                preg_match('/\/backend\/([a-zA-Z]*)/', $trace['file'], $match);
125                if (isset($match[1])) {
126                    return $this->GetProgramName() . '/' . $match[1];
127                }
128            } elseif (basename($trace['file'], '.php') != 'zlog') {
129                return $this->GetProgramName() . '/core';
130            }
131        }
132
133        return $this->GetProgramName() . '/core';
134    }
135
136    /**
137     * Maps the z-push loglevel with those of syslog.
138     *
139     * @access protected
140     * @param int $loglevel
141     * @return int One of many LOG_* syslog level.
142     */
143    protected function GetZpushLogLevelToSyslogLogLevel($loglevel) {
144        switch ($loglevel) {
145            case LOGLEVEL_FATAL: return LOG_ALERT; break;
146            case LOGLEVEL_ERROR: return LOG_ERR; break;
147            case LOGLEVEL_WARN:    return LOG_WARNING; break;
148            case LOGLEVEL_INFO:    return LOG_INFO; break;
149            case LOGLEVEL_DEBUG: return LOG_DEBUG; break;
150            case LOGLEVEL_WBXML: return LOG_DEBUG; break;
151            case LOGLEVEL_DEVICEID: return LOG_DEBUG; break;
152            case LOGLEVEL_WBXMLSTACK: return LOG_DEBUG; break;
153        }
154        return null;
155    }
156
157    /**
158     * Build the log string for syslog.
159     *
160     * @param int       $loglevel
161     * @param string    $message
162     * @param boolean   $includeUserDevice  puts username and device in the string, default: true
163     *
164     * @access public
165     * @return string
166     */
167    public function BuildLogString($loglevel, $message, $includeUserDevice = true) {
168        $log = $this->GetLogLevelString($loglevel); // Never pad syslog log because syslog log are usually read with a software.
169        // when the users differ, we need to log both
170        if (strcasecmp($this->GetAuthUser(), $this->GetUser()) == 0) {
171            $log .= ' ['. $this->GetUser() .']';
172        }
173        else {
174            $log .= ' ['. $this->GetAuthUser() . Request::IMPERSONATE_DELIM . $this->GetUser() .']';
175        }
176        if ($loglevel >= LOGLEVEL_DEVICEID) {
177            $log .= '['. $this->GetDevid() .']';
178        }
179        $log .= ' ' . $message;
180        return $log;
181    }
182
183    //
184    // Implementation of Log
185    //
186
187    /**
188     * Writes a log message to the general log.
189     *
190     * @param int $loglevel
191     * @param string $message
192     *
193     * @access protected
194     * @return void
195     */
196    protected function Write($loglevel, $message) {
197        if ($this->GetHost() && $this->GetPort()) {
198            $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
199            $facility = 1; // user level
200            $pri = ($facility * 8) + $loglevel; // multiplying the Facility number by 8 + adding the level
201            $data = $this->BuildLogString($loglevel, $message);
202            if (strlen(trim($data)) > 0) {
203                $syslog_message = "<{$pri}>" . date('M d H:i:s ') . '[' . $this->GetProgramName() . ']: ' . $data;
204                socket_sendto($sock, $syslog_message, strlen($syslog_message), 0, $this->GetHost(), $this->GetPort());
205            }
206            socket_close($sock);
207        } else {
208            openlog($this->GenerateProgramName(), LOG_PID, LOG_SYSLOG_FACILITY);
209            syslog(
210                $this->GetZpushLogLevelToSyslogLogLevel($loglevel),
211                $this->BuildLogString($loglevel, $message)
212            );
213        }
214    }
215
216    /**
217     * This function is used as an event for log implementer.
218     * It happens when the a call to the Log function is finished.
219     *
220     * @access public
221     * @return void
222     */
223    public function WriteForUser($loglevel, $message) {
224        $this->Write(LOGLEVEL_DEBUG, $message); // Always pass the logleveldebug so it uses syslog level LOG_DEBUG
225    }
226}