/** * Check the username / password against the PAM system */ function SQUID_PAM_check($username, $password) { global $c; $script = $c->authenticate_hook['config']['script']; if (empty($script)) { $script = $c->authenticate_hook['config']['path']; } $cmd = sprintf('echo %s %s | %s -n common-auth', escapeshellarg($username), escapeshellarg($password), $script); $auth_result = exec($cmd); if ($auth_result == "OK") { dbg_error_log('pwauth', 'User %s successfully authenticated', $username); $principal = new Principal('username', $username); if (!$principal->Exists()) { dbg_error_log('pwauth', 'User %s does not exist in local db, creating', $username); $pwent = posix_getpwnam($username); $gecos = explode(',', $pwent['gecos']); $fullname = $gecos[0]; $principal->Create(array('username' => $username, 'user_active' => 't', 'email' => sprintf('%s@%s', $username, $email_base), 'fullname' => $fullname)); if (!$principal->Exists()) { dbg_error_log("PAM", "Unable to create local principal for '%s'", $username); return false; } CreateHomeCalendar($username); } return $principal; } else { dbg_error_log("PAM", "User %s is not a valid username (or password was wrong)", $username); return false; } }
/** * Check the username / password against the PAM system */ function SQUID_PAM_check($username, $password) { global $c; /** * @todo Think of the children! This is a horribly insecure use of unvalidated user input! Probably it should be done with a popen or something, and it seems remarkably dodgy to expect that naively quoted strings will work in any way reliably. * Meanwhile, I've quickly hacked something basic in place to improve the situation. No quotes/backslashes in passwords for YOU! */ $username = str_replace("'", "", str_replace('"', "", str_replace('\\', "", $username))); $password = str_replace("'", "", str_replace('"', "", str_replace('\\', "", $password))); $cmd = "echo '" . $username . "' '" . $password . "' | " . $c->authenticate_hook['config']['script'] . " -n common-auth"; $auth_result = exec($cmd); if ($auth_result == "OK") { if ($usr = getUserByName($username)) { return $usr; } else { dbg_error_log("PAM", "user %s doesn't exist in local DB, we need to create it", $username); $fullname = exec('getent passwd "' . $username . '"'); $fullname = preg_replace('{^[^:]+:[^:]+:\\d+:\\d+:([^:,]+)(,?[^:]*):.*$}', '$1', $fullname); $usr = (object) array('user_no' => 0, 'username' => $username, 'active' => 't', 'email' => $username . "@" . $c->authenticate_hook['config']['email_base'], 'updated' => date(), 'fullname' => $fullname); UpdateUserFromExternal($usr); return $usr; } } else { dbg_error_log("PAM", "User %s is not a valid username (or password was wrong)", $username); return false; } }
/** * CheckPassword does all of the password checking and * returns a user record object, or false if it all ends in tears. */ function CheckPassword($username, $password) { $qry = new PgQuery("SELECT * FROM usr WHERE lower(username) = ? ", $username); if ($qry->Exec('BasicAuthSession', __LINE, __FILE__) && $qry->rows == 1) { $usr = $qry->Fetch(); dbg_error_log("BasicAuthSession", "Name:%s, Pass:%s, File:%s", $username, $password, $usr->password); if (session_validate_password($password, $usr->password)) { return $usr; } } return false; }
function delete_collection($id) { $params = array(':collection_id' => $id); $qry = new AwlQuery('SELECT child.collection_id AS child_id FROM collection child JOIN collection parent ON (parent.dav_name = child.parent_container) WHERE parent.collection_id = :collection_id', $params); if ($qry->Exec('DELETE', __LINE__, __FILE__) && $qry->rows() > 0) { while ($row = $qry->Fetch()) { delete_collection($row->child_id); } } if ($qry->QDo("SELECT write_sync_change(collection_id, 404, caldav_data.dav_name) FROM caldav_data WHERE collection_id = :collection_id", $params) && $qry->QDo("DELETE FROM property WHERE dav_name LIKE (SELECT dav_name FROM collection WHERE collection_id = :collection_id) || '%'", $params) && $qry->QDo("DELETE FROM locks WHERE dav_name LIKE (SELECT dav_name FROM collection WHERE collection_id = :collection_id) || '%'", $params) && $qry->QDo("DELETE FROM caldav_data WHERE collection_id = :collection_id", $params) && $qry->QDo("DELETE FROM collection WHERE collection_id = :collection_id", $params)) { @dbg_error_log("DELETE", "DELETE (collection): User: %d, ETag: %s, Path: %s", $session->user_no, $request->etag_if_match, $request->path); return true; } return false; }
/** * Check the username / password against the PAM system */ function IMAP_PAM_check($username, $password) { global $c; $imap_username = $username; if (function_exists('mb_convert_encoding')) { $imap_username = mb_convert_encoding($imap_username, "UTF7-IMAP", mb_detect_encoding($imap_username)); } else { $imap_username = imap_utf7_encode($imap_username); } //$imap_url = '{localhost:143/imap/notls}'; //$imap_url = '{localhost:993/imap/ssl/novalidate-cert}'; $imap_url = $c->authenticate_hook['config']['imap_url']; $auth_result = "ERR"; $imap_stream = @imap_open($imap_url, $imap_username, $password, OP_HALFOPEN); //print_r(imap_errors()); if ($imap_stream) { // disconnect imap_close($imap_stream); // login ok $auth_result = "OK"; } if ($auth_result == "OK") { $principal = new Principal('username', $username); if (!$principal->Exists()) { dbg_error_log("PAM", "Principal '%s' doesn't exist in local DB, we need to create it", $username); $cmd = "getent passwd '{$username}'"; $getent_res = exec($cmd); $getent_arr = explode(":", $getent_res); $fullname = $getent_arr[4]; if (empty($fullname)) { $fullname = $username; } $principal->Create(array('username' => $username, 'user_active' => true, 'email' => $username . "@" . $c->authenticate_hook['config']['email_base'], 'modified' => date('c'), 'fullname' => $fullname)); if (!$principal->Exists()) { dbg_error_log("PAM", "Unable to create local principal for '%s'", $username); return false; } CreateHomeCalendar($username); } return $principal; } else { dbg_error_log("PAM", "User %s is not a valid username (or password was wrong)", $username); return false; } }
function logRequestHeaders() { global $c; /** Log the request headers */ $lines = apache_request_headers(); dbg_error_log("LOG ", "***************** Request Header ****************"); dbg_error_log("LOG ", "%s %s", $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']); foreach ($lines as $k => $v) { if ($k != 'Authorization' || isset($c->dbg['password']) && $c->dbg['password']) { dbg_error_log("LOG headers", "-->%s: %s", $k, $v); } else { dbg_error_log("LOG headers", "-->%s: %s", $k, 'Delicious tasty password eaten by debugging monster!'); } } dbg_error_log("LOG ", "******************** Request ********************"); // Log the request in all it's gory detail. $lines = preg_split('#[\\r\\n]+#', $c->raw_post); foreach ($lines as $v) { dbg_error_log("LOG request", "-->%s", $v); } unset($lines); }
public function __construct($in_dtz = null) { $this->tz_defined = false; if (!isset($in_dtz)) { return; } $olson = olson_from_tzstring($in_dtz); if (isset($olson)) { try { parent::__construct($olson); $this->tz_defined = $olson; } catch (Exception $e) { dbg_error_log('ERROR', 'Could not handle timezone "%s" (%s) - will use floating time', $in_dtz, $olson); parent::__construct('UTC'); $this->tz_defined = false; } } else { dbg_error_log('ERROR', 'Could not recognize timezone "%s" - will use floating time', $in_dtz); parent::__construct('UTC'); $this->tz_defined = false; } }
/** * Constructor * @param string $ticket_id */ function __construct($ticket_id) { global $c; $this->dav_name = null; $this->target_collection_id = null; $this->target_resource_id = null; $this->expiry = null; $this->expired = true; $this->dav_owner_id = null; $this->ticket_id = $ticket_id; $this->privileges = 0; $this->grantor_collection_privileges = 0; $qry = new AwlQuery('SELECT access_ticket.*, collection.dav_name, (access_ticket.expires < current_timestamp) AS expired, path_privs(access_ticket.dav_owner_id,collection.dav_name,:scan_depth) AS grantor_collection_privileges FROM access_ticket JOIN collection ON (target_collection_id = collection_id) WHERE ticket_id = :ticket_id::text', array(':ticket_id' => $ticket_id, ':scan_depth' => $c->permission_scan_depth)); if ($qry->Exec('DAVTicket', __LINE__, __FILE__) && $qry->rows() == 1 && ($t = $qry->Fetch())) { if (!$t->expired) { foreach ($t as $k => $v) { $this->{$k} = $v; } $this->expired = false; $this->privileges = bindec($this->privileges); $this->grantor_collection_privileges = bindec($this->grantor_collection_privileges); dbg_error_log('DAVTicket', 'Found a current ticket for "%s"', implode(', ', bits_to_privilege($this->privileges()))); } else { dbg_error_log('DAVTicket', 'Found an expired ticket: %s - %s', $ticket_id, $t->expires); } } if (isset($this->target_resource_id)) { $qry = new AwlQuery('SELECT dav_name FROM caldav_data WHERE dav_id = :dav_id', array(':dav_id' => $this->target_resource_id)); if ($qry->Exec('DAVTicket', __LINE__, __FILE__) && $qry->rows() == 1 && ($r = $qry->Fetch())) { $this->dav_name = $r->dav_name; } } }
/** * Get XML response for items in the collection * If '/' is requested, a list of visible users is given, otherwise * a list of calendars for the user which are parented by this path. */ function get_collection_contents($depth, $collection, $parent_path = null) { global $c, $session, $request, $reply, $property_list; $bound_from = $collection->bound_from(); $bound_to = $collection->dav_name(); if (!isset($parent_path)) { $parent_path = $collection->dav_name(); } dbg_error_log('PROPFIND', 'Getting collection contents: Depth %d, Path: %s, Bound from: %s, Bound to: %s', $depth, $collection->dav_name(), $bound_from, $bound_to); $date_format = AwlDatabase::HttpDateFormat; $responses = array(); if (!$collection->IsCalendar() && !$collection->IsAddressbook()) { /** * Calendar/Addressbook collections may not contain collections, so we are only looking in the other ones */ $params = array(':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth); if ($bound_from == '/') { $sql = "SELECT usr.*, '/' || username || '/' AS dav_name, md5(username || updated::text) AS dav_etag, "; $sql .= "to_char(joined at time zone 'GMT',{$date_format}) AS created, "; $sql .= "to_char(updated at time zone 'GMT',{$date_format}) AS modified, "; $sql .= 'FALSE AS is_calendar, TRUE AS is_principal, FALSE AS is_addressbook, \'principal\' AS type, '; $sql .= 'principal_id AS collection_id, '; $sql .= 'principal.* '; $sql .= 'FROM usr JOIN principal USING (user_no) '; $sql .= "WHERE (pprivs(:session_principal::int8,principal.principal_id,:scan_depth::int) & 1::BIT(24))::INT4::BOOLEAN "; $sql .= 'ORDER BY usr.user_no'; } else { $qry = new AwlQuery('SELECT * FROM dav_binding WHERE dav_binding.parent_container = :this_dav_name ORDER BY bind_id', array(':this_dav_name' => $bound_from)); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($binding = $qry->Fetch()) { $resource = new DAVResource($binding->dav_name); if ($resource->IsExternal()) { require_once "external-fetch.php"; update_external($resource); } if ($resource->HavePrivilegeTo('DAV::read', false)) { $resource->set_bind_location(str_replace($bound_from, $bound_to, $binding->dav_name)); $responses[] = $resource->RenderAsXML($property_list, $reply); if ($depth > 0) { $responses = array_merge($responses, get_collection_contents($depth - 1, $resource, $binding->dav_name)); } } } } $sql = 'SELECT principal.*, collection.*, \'collection\' AS type '; $sql .= 'FROM collection LEFT JOIN principal USING (user_no) '; $sql .= 'WHERE parent_container = :this_dav_name '; $sql .= ' ORDER BY collection_id'; $params[':this_dav_name'] = $bound_from; unset($params[':session_principal']); unset($params[':scan_depth']); } $qry = new AwlQuery($sql, $params); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($subcollection = $qry->Fetch()) { $resource = new DAVResource($subcollection); if (!$resource->HavePrivilegeTo('DAV::read')) { continue; } $resource->set_bind_location(str_replace($bound_from, $bound_to, $subcollection->dav_name)); $responses[] = $resource->RenderAsXML($property_list, $reply); if ($depth > 0) { $responses = array_merge($responses, get_collection_contents($depth - 1, $resource, str_replace($resource->parent_path(), $parent_path, $resource->dav_name()))); } } } if ((!isset($c->disable_caldav_proxy) || $c->disable_caldav_proxy == false) && $collection->IsPrincipal()) { // Caldav Proxy: 5.1 par. 2: Add child resources calendar-proxy-(read|write) dbg_error_log('PROPFIND', 'Adding calendar-proxy-read and write. Path: %s', $bound_from); $response = add_proxy_response('read', $bound_from); if (isset($response)) { $responses[] = $response; } $response = add_proxy_response('write', $bound_from); if (isset($response)) { $responses[] = $response; } } } /** * freebusy permission is not allowed to see the items in a collection. Must have at least read permission. */ if ($collection->HavePrivilegeTo('DAV::read', false)) { dbg_error_log('PROPFIND', 'Getting collection items: Depth %d, Path: %s', $depth, $bound_from); $privacy_clause = ' '; $todo_clause = ' '; $time_limit_clause = ' '; if ($collection->IsCalendar()) { if (!$collection->HavePrivilegeTo('all', false)) { $privacy_clause = " AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) "; } if (isset($c->hide_TODO) && ($c->hide_TODO === true || is_string($c->hide_TODO) && preg_match($c->hide_TODO, $_SERVER['HTTP_USER_AGENT'])) && !$collection->HavePrivilegeTo('all')) { $todo_clause = " AND caldav_data.caldav_type NOT IN ('VTODO') "; } if (isset($c->hide_older_than) && intval($c->hide_older_than > 0)) { $time_limit_clause = " AND (CASE WHEN caldav_data.caldav_type<>'VEVENT' OR calendar_item.dtstart IS NULL THEN true ELSE calendar_item.dtstart > (now() - interval '" . intval($c->hide_older_than) . " days') END) "; } } $sql = 'SELECT collection.*, principal.*, calendar_item.*, caldav_data.*, '; $sql .= "to_char(coalesce(calendar_item.created, caldav_data.created) at time zone 'GMT',{$date_format}) AS created, "; $sql .= "to_char(coalesce(calendar_item.last_modified, caldav_data.modified) at time zone 'GMT',{$date_format}) AS modified, "; $sql .= 'summary AS dav_displayname '; $sql .= 'FROM caldav_data LEFT JOIN calendar_item USING( dav_id, user_no, dav_name, collection_id) '; $sql .= 'LEFT JOIN collection USING(collection_id,user_no) LEFT JOIN principal USING(user_no) '; $sql .= 'WHERE collection.dav_name = :collection_dav_name ' . $time_limit_clause . ' ' . $todo_clause . ' ' . $privacy_clause; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= " ORDER BY caldav_data.dav_id"; } $qry = new AwlQuery($sql, array(':collection_dav_name' => $bound_from)); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($item = $qry->Fetch()) { if ($bound_from != $bound_to) { $item->bound_from = $item->dav_name; $item->dav_name = str_replace($bound_from, $bound_to, $item->dav_name); } $resource = new DAVResource($item); $responses[] = $resource->RenderAsXML($property_list, $reply, $parent_path); } } } return $responses; }
<?php /** * CalDAV Server - handle PUT method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Catalyst .Net Ltd, Morphoss Ltd * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log("PUT", "method handler"); require_once 'DAVResource.php'; $dav_resource = new DAVResource($request->path); if (!$dav_resource->HavePrivilegeTo('DAV::write-content')) { $request->DoResponse(403); } if (!$dav_resource->Exists() && !$dav_resource->HavePrivilegeTo('DAV::bind')) { $request->DoResponse(403); } if (!ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['put']) && $c->dbg['put'])) { $fh = fopen('/tmp/PUT.txt', 'w'); if ($fh) { fwrite($fh, $request->raw_post); fclose($fh); } } include_once 'caldav-PUT-functions.php'; controlRequestContainer($dav_resource->GetProperty('username'), $dav_resource->GetProperty('user_no'), $dav_resource->bound_from(), true); $lock_opener = $request->FailIfLocked(); if ($dav_resource->IsCollection()) {
/** * Returns the footer we always use at the finish of our iCalendar resources * * @deprecated This function is deprecated and will be removed eventually. * @todo Remove this function. */ function iCalFooter() { dbg_error_log("LOG", " iCalendar: Call to deprecated method '%s'", 'iCalFooter'); return "END:VCALENDAR\r\n"; }
function ticket_row_editor() { global $c, $id, $editor, $can_write_principal, $privilege_names; $ticketrow = new Editor("Tickets", "access_ticket"); $ticketrow->SetSubmitName('ticketrow'); dbg_error_log("ERROR", "Creating ticketrow editor: %s - %s", $can_write_principal, $ticketrow->IsSubmit()); if ($can_write_principal && $ticketrow->IsSubmit()) { $username = $editor->Value('username'); $ugly_path = $_POST['target']; if ($ugly_path == '/' . $username || $ugly_path == '/' . $username . '/') { $target_collection = $id; } else { $username_len = strlen($username) + 2; $sql = "SELECT collection_id FROM collection WHERE dav_name = :exact_name"; $sql .= " AND substring(dav_name FROM 1 FOR {$username_len}) = '/{$username}/'"; $params = array(':exact_name' => $ugly_path); if (!preg_match('#/$#', $ugly_path)) { $sql .= " OR dav_name = :truncated_name OR dav_name = :trailing_slash_name"; $params[':truncated_name'] = preg_replace('#[^/]*$#', '', $ugly_path); $params[':trailing_slash_name'] = $ugly_path . "/"; } $sql .= " ORDER BY LENGTH(dav_name) DESC LIMIT 1"; $qry = new AwlQuery($sql, $params); if ($qry->Exec() && $qry->rows() > 0) { $row = $qry->Fetch(); $target_collection = $row->collection_id; } else { $c->messages[] = translate('Can only add tickets for existing collection paths which you own'); return $ticketrow; } } $_POST['dav_owner_id'] = $id; $_POST['target_collection_id'] = $target_collection; $ticket_id = clean_by_regex($_POST['ticket_id'], '/[A-Za-z0-9]+/'); $ticketrow->SetWhere('dav_owner_id=' . $id . ' AND ticket_id=' . AwlQuery::quote($ticket_id)); if (isset($_POST['ticket_privileges'])) { $privilege_bitpos = array_flip($privilege_names); $priv_names = array_keys($_POST['ticket_privileges']); $privs_dec = privilege_to_bits($priv_names); $_POST['privileges'] = sprintf('%024s', decbin($privs_dec)); $ticketrow->Assign('privileges', $privs_dec); } $c->messages[] = translate('Creating new ticket granting privileges to this Principal'); $ticketrow->Write(); } return $ticketrow; }
function check_string($ics) { $ics_file = explode("\n", $ics); foreach ($ics_file as $line => $str) { if (false === utf8ToUnicode($str)) { $error[] = $line; } } if (isset($error) && is_array($error)) { foreach ($error as $line) { dbg_error_log("LOG check_string", "error on lines % invalid character in string %s", $line + 1, $ics_file[$line]); return false; } } else { dbg_error_log("LOG check_string", "the string is UTF8 compliant"); return true; } }
/** * Update the local cache of the remote user details * @param object $usr The user details we read from the remote. */ function UpdateUserFromExternal(&$usr) { global $c; /** * When we're doing the create we will usually need to generate a user number */ if (!isset($usr->user_no) || intval($usr->user_no) == 0) { $qry = new AwlQuery("SELECT nextval('usr_user_no_seq');"); $qry->Exec('Login', __LINE__, __FILE__); $sequence_value = $qry->Fetch(true); // Fetch as an array $usr->user_no = $sequence_value[0]; } $qry = new AwlQuery('SELECT * FROM usr WHERE user_no = :user_no', array(':user_no' => $usr->user_no)); if ($qry->Exec('Login', __LINE__, __FILE__) && $qry->rows() == 1) { $type = "UPDATE"; if ($old = $qry->Fetch()) { $changes = false; foreach ($usr as $k => $v) { if ($old->{$k} != $v) { $changes = true; dbg_error_log("Login", "User '%s' field '%s' changed from '%s' to '%s'", $usr->username, $k, $old->{$k}, $v); break; } } if (!$changes) { dbg_error_log("Login", "No changes to user record for '%s' - leaving as-is.", $usr->username); if (isset($usr->active) && $usr->active == 'f') { return false; } return; // Normal case, if there are no changes } else { dbg_error_log("Login", "Changes to user record for '%s' - updating.", $usr->username); } } } else { $type = "INSERT"; } $params = array(); if ($type != 'INSERT') { $params[':user_no'] = $usr->user_no; } $qry = new AwlQuery(sql_from_object($usr, $type, 'usr', 'WHERE user_no= :user_no'), $params); $qry->Exec('Login', __LINE__, __FILE__); /** * We disallow login by inactive users _after_ we have updated the local copy */ if (isset($usr->active) && ($usr->active === 'f' || $usr->active === false)) { return false; } if ($type == 'INSERT') { $qry = new AwlQuery('INSERT INTO principal( type_id, user_no, displayname, default_privileges) SELECT 1, user_no, fullname, :privs::INT::BIT(24) FROM usr WHERE username=:username', array(':privs' => privilege_to_bits($c->default_privileges), ':username' => $usr->username)); $qry->Exec('Login', __LINE__, __FILE__); CreateHomeCalendar($usr->username); } else { if ($usr->fullname != $old->{'fullname'}) { // Also update the displayname if the fullname has been updated. $qry->QDo('UPDATE principal SET displayname=:new_display WHERE user_no=:user_no', array(':new_display' => $usr->fullname, ':user_no' => $usr->user_no)); } } }
* * Why are we using a defunct RFC? Well, we want to support some kind of system * for providing a URI to people to give out for granting privileged access * without requiring logins. Using a defunct proposed spec seems better than * inventing our own. As well as Xythos, Cosmo follows this specification, * with some documented variations, which we will also follow. In particular * we use the xmlns="http://www.xythos.com/namespaces/StorageServer" rather * than the DAV: namespace. * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Morphoss Ltd - http://www.morphoss.com/ * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log('MKTICKET', 'method handler'); require_once 'DAVResource.php'; $request->NeedPrivilege('DAV::bind'); require_once 'XMLDocument.php'; $reply = new XMLDocument(array('DAV:' => '', 'http://www.xythos.com/namespaces/StorageServer' => 'T')); $target = new DAVResource($request->path); if (!$target->Exists()) { $request->XMLResponse(404, new XMLElement('error', new XMLElement('resource-must-not-be-null'), $reply->GetXmlNsArray())); } if (!isset($request->xml_tags)) { $request->XMLResponse(400, new XMLElement('error', new XMLElement('missing-xml-for-request'), $reply->GetXmlNsArray())); } $xmltree = BuildXMLTree($request->xml_tags, $position); if ($xmltree->GetTag() != 'http://www.xythos.com/namespaces/StorageServer:ticketinfo' && $xmltree->GetTag() != 'DAV::ticketinfo') { $request->XMLResponse(400, new XMLElement('error', new XMLElement('invalid-xml-for-request'), $reply->GetXmlNsArray())); }
} else { $sync_level = 1; } } if ($sync_level == DEPTH_INFINITY) { $request->PreconditionFailed(403, 'DAV::sync-traversal-supported', 'This server does not support sync-traversal'); } $sync_tokens = $xmltree->GetPath('/DAV::sync-collection/DAV::sync-token'); if (isset($sync_tokens[0])) { $sync_token = $sync_tokens[0]->GetContent(); } if (!isset($sync_token)) { $sync_token = 0; } $sync_token = intval(str_ireplace('data:,', '', $sync_token)); dbg_error_log('sync', " sync-token: %s", $sync_token); $proplist = array(); $props = $xmltree->GetPath('/DAV::sync-collection/DAV::prop/*'); if (!empty($props)) { foreach ($props as $k => $v) { $proplist[] = $v->GetNSTag(); } } function display_status($status_code) { return sprintf('HTTP/1.1 %03d %s', intval($status_code), getStatusMessage($status_code)); } $collection = new DAVResource($request->path); if (!$collection->Exists()) { $request->DoResponse(404); }
<?php /** * CalDAV Server - handle GET method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Catalyst .Net Ltd, Morphoss Ltd <http://www.morphoss.com/> * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log("get", "GET method handler"); require_once "iCalendar.php"; require_once "DAVResource.php"; $dav_resource = new DAVResource($request->path); $dav_resource->NeedPrivilege(array('urn:ietf:params:xml:ns:caldav:read-free-busy', 'DAV::read')); if (!$dav_resource->Exists()) { $request->DoResponse(404, translate("Resource Not Found.")); } function obfuscated_event($icalendar) { // The user is not admin / owner of this calendar looking at his calendar and can not admin the other cal, // or maybe they don't have *read* access but they got here, so they must at least have free/busy access // so we will present an obfuscated version of the event that just says "Busy" (translated :-) $confidential = new iCalComponent(); $confidential->SetType($icalendar->GetType()); $confidential->AddProperty('SUMMARY', translate('Busy')); $confidential->AddProperty('CLASS', 'CONFIDENTIAL'); $confidential->SetProperties($icalendar->GetProperties('DTSTART'), 'DTSTART'); $confidential->SetProperties($icalendar->GetProperties('RRULE'), 'RRULE'); $confidential->SetProperties($icalendar->GetProperties('DURATION'), 'DURATION');
/** * The constructor takes a start date and an RRULE definition. Both of these * follow the iCalendar standard. */ function RRule($start, $rrule) { $this->_first = new iCalDate($start); $this->_finished = false; $this->_started = false; $this->_dates = array(); $this->_current = -1; $this->_rule = preg_replace('/\\s/m', '', $rrule); if (substr($this->_rule, 0, 6) == 'RRULE:') { $this->_rule = substr($this->_rule, 6); } dbg_error_log("RRule", " new RRule: Start: %s, RRULE: %s", $start->Render(), $this->_rule); $parts = explode(';', $this->_rule); $this->_part = array('INTERVAL' => 1); foreach ($parts as $k => $v) { list($type, $value) = explode('=', $v, 2); // dbg_error_log( "RRule", " Parts of %s explode into %s and %s", $v, $type, $value ); $this->_part[$type] = $value; } // A little bit of validation if (!isset($this->_part['FREQ'])) { dbg_error_log("ERROR", " RRULE MUST have FREQ=value (%s)", $rrule); } if (isset($this->_part['COUNT']) && isset($this->_part['UNTIL'])) { dbg_error_log("ERROR", " RRULE MUST NOT have both COUNT=value and UNTIL=value (%s)", $rrule); } if (isset($this->_part['COUNT']) && intval($this->_part['COUNT']) < 1) { dbg_error_log("ERROR", " RRULE MUST NOT have both COUNT=value and UNTIL=value (%s)", $rrule); } if (!preg_match('/(YEAR|MONTH|WEEK|DAI)LY/', $this->_part['FREQ'])) { dbg_error_log("ERROR", " RRULE Only FREQ=(YEARLY|MONTHLY|WEEKLY|DAILY) are supported at present (%s)", $rrule); } if ($this->_part['FREQ'] == "YEARLY") { $this->_part['INTERVAL'] *= 12; $this->_part['FREQ'] = "MONTHLY"; } }
function handle_subaction($subaction) { global $session, $c, $id, $editor; global $delete_collection_confirmation_required; global $delete_principal_confirmation_required; global $delete_ticket_confirmation_required; global $delete_bind_in_confirmation_required; global $delete_binding_confirmation_required; dbg_error_log('admin-principal-edit', ':handle_action: Action %s', $subaction); switch ($subaction) { case 'delete_collection': dbg_error_log('admin-principal-edit', ':handle_action: Deleting collection %s for principal %d', $_GET['dav_name'], $id); if ($session->AllowedTo('Admin') || $id > 0 && $session->principal_id == $id) { if ($session->CheckConfirmationHash('GET', 'confirm')) { dbg_error_log('admin-principal-edit', ':handle_action: Allowed to delete collection %s for principal %d', $_GET['dav_name'], $id); $qry = new AwlQuery('DELETE FROM collection WHERE dav_name=?;', $_GET['dav_name']); if ($qry->Exec()) { $c->messages[] = i18n('Collection deleted'); return true; } else { $c->messages[] = i18n('There was an error writing to the database.'); return false; } } else { $c->messages[] = i18n('Please confirm deletion of collection - see below'); $delete_collection_confirmation_required = $session->BuildConfirmationHash('GET', 'confirm'); return false; } } break; case 'delete_principal': dbg_error_log('admin-principal-edit', ':handle_action: Deleting principal %d', $id); if ($session->AllowedTo('Admin')) { if (isset($id) && $id > 1 && $session->CheckConfirmationHash('GET', 'confirm')) { dbg_error_log('admin-principal-edit', ':handle_action: Allowed to delete principal %d -%s', $id); $qry = new AwlQuery('DELETE FROM dav_principal WHERE principal_id=?', $id); if ($qry->Exec()) { $c->messages[] = i18n('Principal deleted'); return true; } else { $c->messages[] = i18n('There was an error writing to the database.'); return false; } } else { $c->messages[] = i18n('Please confirm deletion of the principal'); $delete_principal_confirmation_required = $session->BuildConfirmationHash('GET', 'confirm'); return false; } } break; case 'delete_ticket': dbg_error_log('admin-principal-edit', ':handle_action: Deleting ticket "%s" for principal %d', $_GET['ticket_id'], $id); if ($session->AllowedTo('Admin') || $id > 0 && $session->principal_id == $id) { if ($session->CheckConfirmationHash('GET', 'confirm')) { dbg_error_log('admin-principal-edit', ':handle_action: Allowed to delete ticket "%s" for principal %d', $_GET['ticket_id'], $id); $qry = new AwlQuery('DELETE FROM access_ticket WHERE ticket_id=?;', $_GET['ticket_id']); if ($qry->Exec()) { $c->messages[] = i18n('Access ticket deleted'); return true; } else { $c->messages[] = i18n('There was an error writing to the database.'); return false; } } else { $c->messages[] = i18n('Please confirm deletion of access ticket - see below'); $delete_ticket_confirmation_required = $session->BuildConfirmationHash('GET', 'confirm'); return false; } } break; case 'delete_bind_in': case 'delete_binding': dbg_error_log('admin-principal-edit', ':handle_action: Deleting binding "%s" for principal %d', $_GET['bind_id'], $id); if ($session->AllowedTo('Admin') || $id > 0 && $session->principal_id == $id) { if ($session->CheckConfirmationHash('GET', 'confirm')) { dbg_error_log('admin-principal-edit', ':handle_action: Allowed to delete ticket "%s" for principal %d', $_GET['bind_id'], $id); $qry = new AwlQuery('DELETE FROM dav_binding WHERE bind_id=?;', $_GET['bind_id']); if ($qry->Exec()) { $c->messages[] = i18n('Binding deleted'); return true; } else { $c->messages[] = i18n('There was an error writing to the database.'); return false; } } else { $c->messages[] = i18n('Please confirm deletion of binding - see below'); if ($subaction == 'delete_bind_in') { $delete_bind_in_confirmation_required = $session->BuildConfirmationHash('GET', 'confirm'); } else { $delete_binding_confirmation_required = $session->BuildConfirmationHash('GET', 'confirm'); } return false; } } break; default: return false; } return false; }
function SqlFilterCardDAV($filter, $components, $property = null, $parameter = null) { global $need_post_filter, $target_collection, $matchnum; $sql = ""; $params = array(); if (!is_array($filter)) { dbg_error_log("cardquery", "Filter is of type '%s', but should be an array of XML Tags.", gettype($filter)); } foreach ($filter as $k => $v) { $tag = $v->GetTag(); dbg_error_log("cardquery", "Processing {$tag} into SQL - %d, '%s', %d\n", count($components), $property, isset($parameter)); $not_defined = ""; switch ($tag) { case 'urn:ietf:params:xml:ns:carddav:text-match': $search = $v->GetContent(); $negate = $v->GetAttribute("negate-condition"); $collation = $v->GetAttribute("collation"); switch (strtolower($collation)) { case 'i;octet': $comparison = 'LIKE'; break; case 'i;ascii-casemap': case 'i;unicode-casemap': default: $comparison = 'ILIKE'; break; } $pname = ':text_match_' . $matchnum++; $params[$pname] = '%' . $search . '%'; dbg_error_log("cardquery", " text-match: (%s%s %s '%s') ", isset($negate) && strtolower($negate) == "yes" ? "NOT " : "", $property, $comparison, $params[$pname]); $sql .= sprintf("AND (%s%s %s {$pname}) ", isset($negate) && strtolower($negate) == "yes" ? "NOT " : "", $property, $comparison); break; case 'urn:ietf:params:xml:ns:carddav:prop-filter': $propertyname = $v->GetAttribute("name"); switch ($propertyname) { case 'VERSION': case 'UID': case 'NICKNAME': case 'FN': case 'NOTE': case 'ORG': case 'URL': case 'FBURL': case 'CALADRURI': case 'CALURI': $property = strtolower($propertyname); break; case 'N': $property = 'name'; break; default: $need_post_filter = true; dbg_error_log("cardquery", "Could not handle 'prop-filter' on %s in SQL", $propertyname); continue; } $subfilter = $v->GetContent(); $success = SqlFilterCardDAV($subfilter, $components, $property, $parameter); if ($success === false) { continue; } else { $sql .= $success['sql']; $params = array_merge($params, $success['params']); } break; case 'urn:ietf:params:xml:ns:carddav:param-filter': $need_post_filter = true; return false; /** Figure out how to handle PARAM-FILTER conditions in the SQL */ /* $parameter = $v->GetAttribute("name"); $subfilter = $v->GetContent(); $success = SqlFilterCardDAV( $subfilter, $components, $property, $parameter ); if ( $success === false ) continue; else { $sql .= $success['sql']; $params = array_merge( $params, $success['params'] ); } break; */ /** Figure out how to handle PARAM-FILTER conditions in the SQL */ /* $parameter = $v->GetAttribute("name"); $subfilter = $v->GetContent(); $success = SqlFilterCardDAV( $subfilter, $components, $property, $parameter ); if ( $success === false ) continue; else { $sql .= $success['sql']; $params = array_merge( $params, $success['params'] ); } break; */ default: dbg_error_log("cardquery", "Could not handle unknown tag '%s' in calendar query report", $tag); break; } } dbg_error_log("cardquery", "Generated SQL was '%s'", $sql); return array('sql' => $sql, 'params' => $params); }
/** * @DEPRECATED: This class will be removed soon. * Applies the filter conditions, possibly recursively, to the value which will be either * a single property, or an array of lines of the component under test. * * @todo Eventually we need to handle all of these possibilities, which will mean writing * several routines: * - Get Property from Component * - Get Parameter from Property * - Test TimeRange * For the moment we will leave these, until there is a perceived need. * * @param array $filter An array of XMLElement defining the filter(s) * @param mixed $value Either a string which is the single property, or an array of lines, for the component. * @return boolean Whether the filter passed / failed. */ function ApplyFilter($filter, $value) { deprecated('iCalendar::ApplyFilter'); foreach ($filter as $k => $v) { $tag = $v->GetTag(); $value_type = gettype($value); $value_defined = isset($value) && $value_type == 'string' || $value_type == 'array' && count($value) > 0; if ($tag == 'urn:ietf:params:xml:ns:caldav:is-not-defined' && $value_defined) { dbg_error_log('iCalendar', ":ApplyFilter: Value is set ('%s'), want unset, for filter %s", count($value), $tag); return false; } elseif ($tag == 'urn:ietf:params:xml:ns:caldav:is-defined' && !$value_defined) { dbg_error_log('iCalendar', ":ApplyFilter: Want value, but it is not set for filter %s", $tag); return false; } else { switch ($tag) { case 'urn:ietf:params:xml:ns:caldav:time-range': /** todo:: While this is unimplemented here at present, most time-range tests should occur at the SQL level. */ break; case 'urn:ietf:params:xml:ns:caldav:text-match': $search = $v->GetContent(); // In this case $value will either be a string, or an array of iCalProp objects // since TEXT-MATCH does not apply to COMPONENT level - only property/parameter if (gettype($value) != 'string') { if (gettype($value) == 'array') { $match = false; foreach ($value as $k1 => $v1) { // $v1 could be an iCalProp object if ($match = $v1->TextMatch($search)) { break; } } } else { dbg_error_log('iCalendar', ":ApplyFilter: TEXT-MATCH will only work on strings or arrays of iCalProp. %s unsupported", gettype($value)); return true; // We return _true_ in this case, so the client sees the item } } else { $match = strstr($value, $search[0]); } $negate = $v->GetAttribute("negate-condition"); if (isset($negate) && strtolower($negate) == "yes" && $match) { dbg_error_log('iCalendar', ":ApplyFilter: TEXT-MATCH of %s'%s' against '%s'", isset($negate) && strtolower($negate) == "yes" ? '!' : '', $search, $value); return false; } break; case 'urn:ietf:params:xml:ns:caldav:comp-filter': $subfilter = $v->GetContent(); $component = $this->ExtractSubComponent($value, $v->GetAttribute("name")); if (!$this->ApplyFilter($subfilter, $component)) { return false; } break; case 'urn:ietf:params:xml:ns:caldav:prop-filter': $subfilter = $v->GetContent(); $properties = $this->ExtractProperty($value, $v->GetAttribute("name")); if (!$this->ApplyFilter($subfilter, $properties)) { return false; } break; case 'urn:ietf:params:xml:ns:caldav:param-filter': $subfilter = $v->GetContent(); $parameter = $this->ExtractParameter($value, $v->GetAttribute("NAME")); if (!$this->ApplyFilter($subfilter, $parameter)) { return false; } break; } } } return true; }
if ($c->enable_scheduling != true) { $request->DoResponse(404, translate('The application program does not understand that request.')); // Does not return } dbg_log_array('well-known', 'method:' . $request->method); switch ($request->method) { case 'GET': ischedule_get(); break; case 'POST': include 'iSchedule-POST.php'; break; default: dbg_error_log('well-known', 'Unhandled request method >>%s<<', $request->method); dbg_log_array('well-known', '_SERVER', $_SERVER, true); dbg_error_log('well-known', 'RAW: %s', str_replace("\n", '', str_replace("\r", '', $request->raw_post))); } $request->DoResponse(500, translate('The application program does not understand that request.')); function ischedule_get() { global $request, $c; if ($request->path != '/.well-known/ischedule' || $_GET['query'] != 'capabilities') { $request->DoResponse(404, translate('The application program does not understand that request.' . $request->path)); return false; } header('iSchedule-Version: 1.0'); header('Content-Type: application/xml; charset=utf-8'); echo '<?xml version="1.0" encoding="utf-8" ?>'; echo <<<RESPONSE <query-result xmlns="urn:ietf:params:xml:ns:ischedule"> <capability-set>
/** * Checks whether a user is allowed to do something. * * The check is performed to see if the user has that role. * * @param string $whatever The role we want to know if the user has. * @return boolean Whether or not the user has the specified role. */ function AllowedTo($whatever) { dbg_error_log('session', 'Checking whether "Public" is allowed to "%s"', $whatever); return isset($this->roles[$whatever]) && $this->roles[$whatever]; }
/** * Writes the data to a member in the collection and returns the segment_name of the resource in our internal namespace. * @param $data iCalendar The resource to be written. * @param $create_resource boolean True if this is a new resource. * @param $segment_name The name of the resource within the collection. */ function WriteCalendarMember($data, $create_resource, $segment_name = null) { if (!$this->IsSchedulingCollection() && !$this->IsCalendar()) { return false; } // function write_resource( $user_no, $path, $caldav_data, $collection_id, $author, $etag, $ic, $put_action_type, $caldav_context, $log_action=true, $weak_etag=null ) { global $tz_regex; $resources = $ic->GetComponents('VTIMEZONE', false); // Not matching VTIMEZONE if (!isset($resources[0])) { $resource_type = 'Unknown'; /** @TODO: Handle writing non-calendar resources, like address book entries or random file data */ rollback_on_error($caldav_context, $user_no, $path, translate('No calendar content'), 412); return false; } else { $first = $resources[0]; $resource_type = $first->GetType(); } $qry = new AwlQuery(); $qry->Begin(); $params = array(':dav_name' => $path, ':user_no' => $user_no, ':etag' => $etag, ':dav_data' => $caldav_data, ':caldav_type' => $resource_type, ':session_user' => $author, ':weak_etag' => $weak_etag); if ($put_action_type == 'INSERT') { create_scheduling_requests($vcal); $sql = 'INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id, weak_etag ) VALUES( :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, current_timestamp, current_timestamp, :collection_id, :weak_etag )'; $params[':collection_id'] = $collection_id; } else { update_scheduling_requests($vcal); $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user, modified=current_timestamp, weak_etag=:weak_etag WHERE user_no=:user_no AND dav_name=:dav_name'; } if (!$qry->QDo($sql, $params)) { rollback_on_error($caldav_context, $user_no, $path); return false; } $qry->QDo('SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $path)); if ($qry->rows() == 1 && ($row = $qry->Fetch())) { $dav_id = $row->dav_id; } $calitem_params = array(':dav_name' => $path, ':user_no' => $user_no, ':etag' => $etag); $dtstart = $first->GetPValue('DTSTART'); $calitem_params[':dtstart'] = $dtstart; if ((!isset($dtstart) || $dtstart == '') && $first->GetPValue('DUE') != '') { $dtstart = $first->GetPValue('DUE'); } $dtend = $first->GetPValue('DTEND'); if (isset($dtend) && $dtend != '') { dbg_error_log('PUT', ' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue('DURATION')); $calitem_params[':dtend'] = $dtend; $dtend = ':dtend'; } else { $dtend = 'NULL'; if ($first->GetPValue('DURATION') != '' and $dtstart != '') { $duration = preg_replace('#[PT]#', ' ', $first->GetPValue('DURATION')); $dtend = '(:dtstart::timestamp with time zone + :duration::interval)'; $calitem_params[':duration'] = $duration; } elseif ($first->GetType() == 'VEVENT') { /** * From RFC2445 4.6.1: * For cases where a "VEVENT" calendar component specifies a "DTSTART" * property with a DATE data type but no "DTEND" property, the events * non-inclusive end is the end of the calendar date specified by the * "DTSTART" property. For cases where a "VEVENT" calendar component specifies * a "DTSTART" property with a DATE-TIME data type but no "DTEND" property, * the event ends on the same calendar date and time of day specified by the * "DTSTART" property. * * So we're looking for 'VALUE=DATE', to identify the duration, effectively. * */ $value_type = $first->GetPParamValue('DTSTART', 'VALUE'); dbg_error_log('PUT', 'DTSTART without DTEND. DTSTART value type is %s', $value_type); if (isset($value_type) && $value_type == 'DATE') { $dtend = '(:dtstart::timestamp with time zone::date + \'1 day\'::interval)'; } else { $dtend = ':dtstart'; } } } $last_modified = $first->GetPValue('LAST-MODIFIED'); if (!isset($last_modified) || $last_modified == '') { $last_modified = gmdate('Ymd\\THis\\Z'); } $calitem_params[':modified'] = $last_modified; $dtstamp = $first->GetPValue('DTSTAMP'); if (!isset($dtstamp) || $dtstamp == '') { $dtstamp = $last_modified; } $calitem_params[':dtstamp'] = $dtstamp; $class = $first->GetPValue('CLASS'); /* Check and see if we should over ride the class. */ /** @TODO: is there some way we can move this out of this function? Or at least get rid of the need for the SQL query here. */ if (public_events_only($user_no, $path)) { $class = 'PUBLIC'; } /* * It seems that some calendar clients don't set a class... * RFC2445, 4.8.1.3: * Default is PUBLIC */ if (!isset($class) || $class == '') { $class = 'PUBLIC'; } $calitem_params[':class'] = $class; /** Calculate what timezone to set, first, if possible */ $last_tz_locn = 'Turkmenikikamukau'; // I really hope this location doesn't exist! $tzid = $first->GetPParamValue('DTSTART', 'TZID'); if (!isset($tzid) || $tzid == '') { $tzid = $first->GetPParamValue('DUE', 'TZID'); } $timezones = $ic->GetComponents('VTIMEZONE'); foreach ($timezones as $k => $tz) { if ($tz->GetPValue('TZID') != $tzid) { /** * We'll pretend they didn't forget to give us a TZID and that they * really hope the server is running in the timezone they supplied... but be noisy about it. */ dbg_error_log('ERROR', ' Event includes TZID[%s] but uses TZID[%s]!', $tz->GetPValue('TZID'), $tzid); $tzid = $tz->GetPValue('TZID'); } // This is the one $tz_locn = $tz->GetPValue('X-LIC-LOCATION'); if (!isset($tz_locn)) { if (preg_match('#([^/]+/[^/]+)$#', $tzid, $matches)) { $tz_locn = $matches[1]; } else { if (isset($tzid) && $tzid != '') { dbg_error_log('ERROR', ' Couldn\'t guess Olsen TZ from TZID[%s]. This may end in tears...', $tzid); } } } else { if (!preg_match($tz_regex, $tz_locn)) { if (preg_match('#([^/]+/[^/]+)$#', $tzid, $matches)) { $tz_locn = $matches[1]; } } } dbg_error_log('PUT', ' Using TZID[%s] and location of [%s]', $tzid, isset($tz_locn) ? $tz_locn : ''); if (isset($tz_locn) && $tz_locn != $last_tz_locn && preg_match($tz_regex, $tz_locn)) { dbg_error_log('PUT', ' Setting timezone to %s', $tz_locn); if ($tz_locn != '') { $qry->QDo('SET TIMEZONE TO \'' . $tz_locn . "'"); } $last_tz_locn = $tz_locn; } $params = array(':tzid' => $tzid); $qry = new AwlQuery('SELECT tz_locn FROM time_zone WHERE tz_id = :tzid', $params); if ($qry->Exec('PUT', __LINE__, __FILE__) && $qry->rows() == 0) { $params[':tzlocn'] = $tz_locn; $params[':tzspec'] = isset($tz) ? $tz->Render() : null; $qry->QDo('INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES(:tzid,:tzlocn,:tzspec)', $params); } if (!isset($tz_locn) || $tz_locn == '') { $tz_locn = $tzid; } } $created = $first->GetPValue('CREATED'); if ($created == '00001231T000000Z') { $created = '20001231T000000Z'; } $calitem_params[':created'] = $created; $calitem_params[':tzid'] = $tzid; $calitem_params[':uid'] = $first->GetPValue('UID'); $calitem_params[':summary'] = $first->GetPValue('SUMMARY'); $calitem_params[':location'] = $first->GetPValue('LOCATION'); $calitem_params[':transp'] = $first->GetPValue('TRANSP'); $calitem_params[':description'] = $first->GetPValue('DESCRIPTION'); $calitem_params[':rrule'] = $first->GetPValue('RRULE'); $calitem_params[':url'] = $first->GetPValue('URL'); $calitem_params[':priority'] = $first->GetPValue('PRIORITY'); $calitem_params[':due'] = $first->GetPValue('DUE'); $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE'); $calitem_params[':status'] = $first->GetPValue('STATUS'); if ($put_action_type == 'INSERT') { $sql = <<<EOSQL INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp, description, rrule, tz_id, last_modified, url, priority, created, due, percent_complete, status, collection_id ) VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp, :dtstart, {$dtend}, :summary, :location, :class, :transp, :description, :rrule, :tzid, :modified, :url, :priority, :created, :due, :percent_complete, :status, {$collection_id} ) EOSQL; $sync_change = 201; } else { $sql = <<<EOSQL UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp, dtstart=:dtstart, dtend={$dtend}, summary=:summary, location=:location, class=:class, transp=:transp, description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority, created=:created, due=:due, percent_complete=:percent_complete, status=:status WHERE user_no=:user_no AND dav_name=:dav_name EOSQL; $sync_change = 200; } write_alarms($dav_id, $first); write_attendees($dav_id, $first); if ($log_action && function_exists('log_caldav_action')) { log_caldav_action($put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } else { if ($log_action) { dbg_error_log('PUT', 'No log_caldav_action( %s, %s, %s, %s, %s) can be called.', $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } } $qry = new AwlQuery($sql, $calitem_params); if (!$qry->Exec('PUT', __LINE__, __FILE__)) { rollback_on_error($caldav_context, $user_no, $path); return false; } $qry->QDo("SELECT write_sync_change( {$collection_id}, {$sync_change}, :dav_name)", array(':dav_name' => $path)); $qry->Commit(); dbg_error_log('PUT', 'User: %d, ETag: %s, Path: %s', $author, $etag, $path); return $segment_name; }
/** * Return XML for a single component from the DB * * @param array $properties The properties for this component * @param string $item The DB row data for this component * * @return string An XML document which is the response for the component */ function component_to_xml($properties, $item) { global $session, $c, $request, $reply; dbg_error_log("REPORT", "Building XML Response for item '%s'", $item->dav_name); $denied = array(); $unsupported = array(); $caldav_data = $item->caldav_data; $displayname = preg_replace('{^.*/}', '', $item->dav_name); $type = 'unknown'; $contenttype = 'text/plain'; switch ($item->caldav_type) { case 'VJOURNAL': case 'VEVENT': case 'VTODO': $displayname = $item->summary; $type = 'calendar'; $contenttype = 'text/calendar'; break; case 'VCARD': $displayname = $item->fn; $type = 'vcard'; $contenttype = 'text/vcard'; break; } if (isset($properties['calendar-data']) || isset($properties['displayname'])) { if (!$request->AllowedTo('all') && $session->user_no != $item->user_no) { // the user is not admin / owner of this calendarlooking at his calendar and can not admin the other cal /** @todo We should examine the ORGANIZER and ATTENDEE fields in the event. If this person is there then they should see this */ if ($type == 'calendar' && $item->class == 'CONFIDENTIAL' || !$request->AllowedTo('read')) { $ical = new iCalComponent($caldav_data); $resources = $ical->GetComponents('VTIMEZONE', false); $first = $resources[0]; // if the event is confidential we fake one that just says "Busy" $confidential = new iCalComponent(); $confidential->SetType($first->GetType()); $confidential->AddProperty('SUMMARY', translate('Busy')); $confidential->AddProperty('CLASS', 'CONFIDENTIAL'); $confidential->SetProperties($first->GetProperties('DTSTART'), 'DTSTART'); $confidential->SetProperties($first->GetProperties('RRULE'), 'RRULE'); $confidential->SetProperties($first->GetProperties('DURATION'), 'DURATION'); $confidential->SetProperties($first->GetProperties('DTEND'), 'DTEND'); $confidential->SetProperties($first->GetProperties('UID'), 'UID'); $ical->SetComponents(array($confidential), $confidential->GetType()); $caldav_data = $ical->Render(); $displayname = translate('Busy'); } } } $url = ConstructURL($item->dav_name); $prop = new XMLElement("prop"); foreach ($properties as $k => $v) { switch ($k) { case 'getcontentlength': $contentlength = strlen($caldav_data); $prop->NewElement($k, $contentlength); break; case 'getlastmodified': $prop->NewElement($k, ISODateToHTTPDate($item->modified)); break; case 'calendar-data': if ($type == 'calendar') { $reply->CalDAVElement($prop, $k, $caldav_data); } else { $unsupported[] = $k; } break; case 'address-data': if ($type == 'vcard') { $reply->CardDAVElement($prop, $k, $caldav_data); } else { $unsupported[] = $k; } break; case 'getcontenttype': $prop->NewElement($k, $contenttype); break; case 'current-user-principal': $prop->NewElement("current-user-principal", $request->current_user_principal_xml); break; case 'displayname': $prop->NewElement($k, $displayname); break; case 'resourcetype': $prop->NewElement($k); // Just an empty resourcetype for a non-collection. break; case 'getetag': $prop->NewElement($k, '"' . $item->dav_etag . '"'); break; case '"current-user-privilege-set"': $prop->NewElement($k, privileges($request->permissions)); break; case 'SOME-DENIED-PROPERTY': /** indicating the style for future expansion */ $denied[] = $k; break; default: dbg_error_log('REPORT', "Request for unsupported property '%s' of calendar item.", $v); $unsupported[] = $k; } } $status = new XMLElement("status", "HTTP/1.1 200 OK"); $propstat = new XMLElement("propstat", array($prop, $status)); $href = new XMLElement("href", $url); $elements = array($href, $propstat); if (count($denied) > 0) { $status = new XMLElement("status", "HTTP/1.1 403 Forbidden"); $noprop = new XMLElement("prop"); foreach ($denied as $k => $v) { $noprop->NewElement(strtolower($v)); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } if (count($unsupported) > 0) { $status = new XMLElement("status", "HTTP/1.1 404 Not Found"); $noprop = new XMLElement("prop"); foreach ($unsupported as $k => $v) { $noprop->NewElement(strtolower($v)); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } $response = new XMLElement("response", $elements); return $response; }
/** * Utility function we call when we have a simple status-based response to * return to the client. Possibly * * @param int $status The HTTP status code to send. * @param string $message The friendly text message to send with the response. */ function DoResponse($status, $message = "", $content_type = "text/plain; charset=\"utf-8\"") { global $session, $c; if (!headers_sent()) { @header(sprintf("HTTP/1.1 %d %s", $status, getStatusMessage($status))); } if (!headers_sent()) { @header(sprintf("X-DAViCal-Version: DAViCal/%d.%d.%d; DB/%d.%d.%d", $c->code_major, $c->code_minor, $c->code_patch, $c->schema_major, $c->schema_minor, $c->schema_patch)); } if (!headers_sent()) { header("Content-type: " . $content_type); } if (isset($c->dbg['ALL']) && $c->dbg['ALL'] || isset($c->dbg['response']) && $c->dbg['response'] || $status == 400 || $status == 402 || $status == 403 || $status > 404) { @dbg_error_log("LOG ", 'Response status %03d for %s %s', $status, $this->method, $_SERVER['REQUEST_URI']); $lines = headers_list(); dbg_error_log("LOG ", "***************** Response Header ****************"); foreach ($lines as $v) { dbg_error_log("LOG headers", "-->%s", $v); } dbg_error_log("LOG ", "******************** Response ********************"); // Log the request in all it's gory detail. $lines = preg_split('#[\\r\\n]+#', $message); foreach ($lines as $v) { dbg_error_log("LOG response", "-->%s", $v); } } if ($message != '') { if (!headers_sent()) { header("Content-Length: " . strlen($message)); } echo $message; } if (isset($c->dbg['caldav']) && $c->dbg['caldav']) { if (strlen($message) > 100 || strstr($message, "\n")) { $message = substr(preg_replace("#\\s+#m", ' ', $message), 0, 100) . (strlen($message) > 100 ? "..." : ""); } dbg_error_log("caldav", "Status: %d, Message: %s, User: %d, Path: %s", $status, $message, $session->principal->user_no(), $this->path); } if (isset($c->dbg['statistics']) && $c->dbg['statistics']) { $script_time = microtime(true) - $c->script_start_time; $memory = ''; if (function_exists('memory_get_usage')) { $memory = sprintf(', Memory: %dk, Peak: %dk', memory_get_usage() / 1024, memory_get_peak_usage(true) / 1024); } @dbg_error_log("statistics", "Method: %s, Status: %d, Script: %5.3lfs, Queries: %5.3lfs, URL: %s%s", $this->method, $status, $script_time, $c->total_query_time, $this->path, $memory); } try { @ob_flush(); // Seems like it should be better to do the following but is problematic on PHP5.3 at least: while ( ob_get_level() > 0 ) ob_end_flush(); } catch (Exception $ignored) { } if (isset($c->exit_after_memory_exceeds) && function_exists('memory_get_peak_usage') && memory_get_peak_usage(true) > $c->exit_after_memory_exceeds) { // 64M @dbg_error_log("statistics", "Peak memory use exceeds %d bytes (%d) - killing process %d", $c->exit_after_memory_exceeds, memory_get_peak_usage(true), getmypid()); register_shutdown_function('CalDAVRequest::kill_on_exit'); } exit(0); }
$match = preg_replace('{^.*/caldav.php/([^/]+)(/.*)?$}', '\\1', $match); $match = preg_replace('{^mailto:}', '', $match); $subwhere .= ' (email ILIKE :user_address_match OR username ILIKE :user_address_match) '; $params[':user_address_match'] = '%' . $match . '%'; break; case 'urn:ietf:params:xml:ns:caldav:calendar-home-set': $match = preg_replace('{^.*/caldav.php}', '', $match); $subwhere .= ' dav_name LIKE :calendar_home_match '; $params[':calendar_home_match'] = $match . '%'; break; default: /** * @todo We should handle a lot more properties here. principal-URL seems a likely one to be used. * @todo We should catch the unsupported properties in the query and fire back an error indicating so. */ dbg_error_log("principal", "Unhandled tag '%s' to match '%s'\n", $v1->GetNSTag(), $match); } } if ($subwhere != "") { $where .= sprintf("%s(%s)", $where == "" ? "" : $clause_joiner, $subwhere); } } if ($where != "") { $where = "WHERE {$where}"; } $sql = "SELECT * FROM dav_principal {$where} ORDER BY principal_id LIMIT 100"; $qry = new AwlQuery($sql, $params); $get_props = $xmltree->GetPath('/DAV::principal-property-search/DAV::prop/*'); $properties = array(); foreach ($get_props as $k1 => $v1) { $properties[] = $v1->GetNSTag();
<?php /** * CalDAV Server - handle MOVE method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Morphoss Ltd * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 */ dbg_error_log("MOVE", "method handler"); require_once 'DAVResource.php'; $request->NeedPrivilege('DAV::unbind'); if (!ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['move']) && $c->dbg['move'])) { $fh = fopen('/var/log/davical/MOVE.debug', 'w'); if ($fh) { fwrite($fh, $request->raw_post); fclose($fh); } } $lock_opener = $request->FailIfLocked(); $dest = new DAVResource($request->destination); if ($dest->dav_name() == '/' || $dest->IsPrincipal()) { $dest->NeedPrivilege('DAV::bind'); } if (!$dest->ContainerExists()) { $request->DoResponse(409, translate('Destination collection does not exist')); } if (!$request->overwrite && $dest->Exists()) { $request->DoResponse(412, translate('Not overwriting existing destination resource'));
* A fresh lock */ $lock_token = uuid(); $sql = 'INSERT INTO locks ( dav_name, opaquelocktoken, type, scope, depth, owner, timeout, start ) VALUES( :dav_name, :lock_token, :type, :scope, :request_depth, :owner, :timeout::interval, current_timestamp )'; $params = array(':dav_name' => $request->path, ':lock_token' => $lock_token, ':type' => $lockinfo['type'], ':scope' => $lockinfo['scope'], ':request_depth' => $request->depth, ':owner' => $lockinfo['owner'], ':timeout' => $request->timeout . ' seconds'); header("Lock-Token: <opaquelocktoken:{$lock_token}>"); } $qry = new AwlQuery($sql, $params); $qry->Exec("LOCK", __LINE__, __FILE__); $lock_row = $request->GetLockRow($lock_token); $activelock = array(new XMLElement('locktype', new XMLElement($lock_row->type)), new XMLElement('lockscope', new XMLElement($lock_row->scope)), new XMLElement('depth', $request->GetDepthName()), new XMLElement('owner', new XMLElement('href', $lock_row->owner)), new XMLElement('timeout', 'Second-' . $request->timeout), new XMLElement('locktoken', new XMLElement('href', 'opaquelocktoken:' . $lock_token))); $response = new XMLElement("lockdiscovery", new XMLElement("activelock", $activelock), array("xmlns" => "DAV:")); } elseif ($request->method == "UNLOCK") { /** * @TODO: respond with preconditionfailed(409,'lock-token-matches-request-uri') if * there is no lock to be deleted. */ dbg_error_log("LOCK", "Attempting to unlock resource '%s'", $request->path); if ($lock_token = $request->IsLocked()) { // NOTE Assignment in if() is expected here. $sql = 'DELETE FROM locks WHERE opaquelocktoken = :lock_token'; $qry = new AwlQuery($sql, array(':lock_token' => $lock_token)); $qry->Exec("LOCK", __LINE__, __FILE__); } $request->DoResponse(204); } $prop = new XMLElement("prop", $response, array('xmlns' => 'DAV:')); // dbg_log_array( "LOCK", "XML", $response, true ); $xmldoc = $prop->Render(0, '<?xml version="1.0" encoding="utf-8" ?>'); $request->DoResponse(200, $xmldoc, 'text/xml; charset="utf-8"');
/** * Returns the result of the LDAP query * * @param string $filter The filter used to search entries * @param array $attributes Attributes to be returned * @param string $passwd password to check * @return array Contains selected attributes from all entries corresponding to the given filter */ function requestUser($filter, $attributes = NULL, $username, $passwd) { global $c; $entry = NULL; // We get the DN of the USER $query = $this->ldap_query_one; foreach ($this->baseDNUsers as $baseDNUsers) { $entry = $query($this->connect, $baseDNUsers, $filter, $attributes); if (ldap_first_entry($this->connect, $entry)) { break; } dbg_error_log("LDAP", "drivers_ldap : Failed to find user with baseDN: %s", $baseDNUsers); } if (!ldap_first_entry($this->connect, $entry)) { dbg_error_log("ERROR", "drivers_ldap : Unable to find the user with filter %s", $filter); return false; } else { dbg_error_log("LDAP", "drivers_ldap : Found a user using filter %s", $filter); } $dnUser = ldap_get_dn($this->connect, ldap_first_entry($this->connect, $entry)); if (isset($c->authenticate_hook['config']['i_use_mode_kerberos']) && $c->authenticate_hook['config']['i_use_mode_kerberos'] == "i_know_what_i_am_doing") { dbg_error_log("LOG", "drivers_ldap : Skipping password Check for user %s which should be the same as %s", $username, $_SERVER["REMOTE_USER"]); if ($username != $_SERVER["REMOTE_USER"]) { return false; } } else { if (!@ldap_bind($this->connect, $dnUser, $passwd)) { dbg_error_log("LDAP", "drivers_ldap : Failed to bind to user %s using password %s", $dnUser, $passwd); return false; } } dbg_error_log("LDAP", "drivers_ldap : Bound to user %s using password %s", $dnUser, $passwd); $i = ldap_first_entry($this->connect, $entry); $arr = ldap_get_attributes($this->connect, $i); for ($i = 0; $i < $arr['count']; $i++) { $ret[$arr[$i]] = $arr[$arr[$i]][0]; } return $ret; }