create_subscriptions(
    cupsd_client_t  *con,		/* I - Client connection */
    ipp_attribute_t *uri)		/* I - Printer URI */
{
  http_status_t	status;			/* Policy status */
  int			i;		/* Looping var */
  ipp_attribute_t	*attr;		/* Current attribute */
  cups_ptype_t		dtype;		/* Destination type (printer/class) */
  char			scheme[HTTP_MAX_URI],
					/* Scheme portion of URI */
			userpass[HTTP_MAX_URI],
					/* Username portion of URI */
			host[HTTP_MAX_URI],
					/* Host portion of URI */
			resource[HTTP_MAX_URI];
					/* Resource portion of URI */
  int			port;		/* Port portion of URI */
  cupsd_printer_t	*printer;	/* Printer/class */
  cupsd_job_t		*job;		/* Job */
  int			jobid;		/* Job ID */
  cupsd_subscription_t	*sub;		/* Subscription object */
  const char		*username,	/* requesting-user-name or
					   authenticated username */
			*recipient,	/* notify-recipient-uri */
			*pullmethod;	/* notify-pull-method */
  ipp_attribute_t	*user_data;	/* notify-user-data */
  int			interval,	/* notify-time-interval */
			lease;		/* notify-lease-duration */
  unsigned		mask;		/* notify-events */
  ipp_attribute_t	*notify_events,/* notify-events(-default) */
			*notify_lease;	/* notify-lease-duration(-default) */


#ifdef DEBUG
  for (attr = con->request->attrs; attr; attr = attr->next)
  {
    if (attr->group_tag != IPP_TAG_ZERO)
      cupsdLogMessage(CUPSD_LOG_DEBUG2, "g%04x v%04x %s", attr->group_tag,
                      attr->value_tag, attr->name);
    else
      cupsdLogMessage(CUPSD_LOG_DEBUG2, "----SEP----");
  }
#endif /* DEBUG */

 /*
  * Is the destination valid?
  */

  cupsdLogMessage(CUPSD_LOG_DEBUG, "create_subscriptions(con=%p(%d), uri=\"%s\")", con, con->number, uri->values[0].string.text);

  httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme,
                  sizeof(scheme), userpass, sizeof(userpass), host,
		  sizeof(host), &port, resource, sizeof(resource));

  if (!strcmp(resource, "/"))
  {
    dtype   = (cups_ptype_t)0;
    printer = NULL;
  }
  else if (!strncmp(resource, "/printers", 9) && strlen(resource) <= 10)
  {
    dtype   = (cups_ptype_t)0;
    printer = NULL;
  }
  else if (!strncmp(resource, "/classes", 8) && strlen(resource) <= 9)
  {
    dtype   = CUPS_PRINTER_CLASS;
    printer = NULL;
  }
  else if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer))
  {
   /*
    * Bad URI...
    */

    send_ipp_status(con, IPP_NOT_FOUND,
                    _("The printer or class does not exist."));
    return;
  }

 /*
  * Check policy...
  */

  if (printer)
  {
    if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con,
                                   NULL)) != HTTP_OK)
    {
      send_http_error(con, status, printer);
      return;
    }
  }
  else if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK)
  {
    send_http_error(con, status, NULL);
    return;
  }

 /*
  * Get the user that is requesting the subscription...
  */

  username = get_username(con);

 /*
  * Find the first subscription group attribute; return if we have
  * none...
  */

  for (attr = con->request->attrs; attr; attr = attr->next)
    if (attr->group_tag == IPP_TAG_SUBSCRIPTION)
      break;

  if (!attr)
  {
    send_ipp_status(con, IPP_BAD_REQUEST,
                    _("No subscription attributes in request."));
    return;
  }

 /*
  * Process the subscription attributes in the request...
  */

  con->response->request.status.status_code = IPP_BAD_REQUEST;

  while (attr)
  {
    recipient = NULL;
    pullmethod = NULL;
    user_data  = NULL;
    interval   = 0;
    lease      = DefaultLeaseDuration;
    jobid      = 0;
    mask       = CUPSD_EVENT_NONE;

    if (printer)
    {
      notify_events = ippFindAttribute(printer->attrs, "notify-events-default",
                                       IPP_TAG_KEYWORD);
      notify_lease  = ippFindAttribute(printer->attrs,
                                       "notify-lease-duration-default",
                                       IPP_TAG_INTEGER);

      if (notify_lease)
        lease = notify_lease->values[0].integer;
    }
    else
    {
      notify_events = NULL;
      notify_lease  = NULL;
    }

    while (attr && attr->group_tag != IPP_TAG_ZERO)
    {
      if (!strcmp(attr->name, "notify-recipient-uri") &&
          attr->value_tag == IPP_TAG_URI)
      {
       /*
        * Validate the recipient scheme against the ServerBin/notifier
	* directory...
	*/

	char	notifier[1024];		/* Notifier filename */


        recipient = attr->values[0].string.text;

	if (httpSeparateURI(HTTP_URI_CODING_ALL, recipient,
	                    scheme, sizeof(scheme), userpass, sizeof(userpass),
			    host, sizeof(host), &port,
			    resource, sizeof(resource)) < HTTP_URI_OK)
        {
          send_ipp_status(con, IPP_NOT_POSSIBLE,
	                  _("Bad notify-recipient-uri \"%s\"."), recipient);
	  ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM,
	                "notify-status-code", IPP_URI_SCHEME);
	  return;
	}

        snprintf(notifier, sizeof(notifier), "%s/notifier/%s", ServerBin,
	         scheme);
        if (access(notifier, X_OK))
	{
          send_ipp_status(con, IPP_NOT_POSSIBLE,
	                  _("notify-recipient-uri URI \"%s\" uses unknown "
			    "scheme."), recipient);
	  ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM,
	                "notify-status-code", IPP_URI_SCHEME);
	  return;
	}

        if (!strcmp(scheme, "rss") && !check_rss_recipient(recipient))
	{
          send_ipp_status(con, IPP_NOT_POSSIBLE,
	                  _("notify-recipient-uri URI \"%s\" is already used."),
			  recipient);
	  ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM,
	                "notify-status-code", IPP_ATTRIBUTES);
	  return;
	}
      }
      else if (!strcmp(attr->name, "notify-pull-method") &&
               attr->value_tag == IPP_TAG_KEYWORD)
      {
        pullmethod = attr->values[0].string.text;

        if (strcmp(pullmethod, "ippget"))
	{
          send_ipp_status(con, IPP_NOT_POSSIBLE,
	                  _("Bad notify-pull-method \"%s\"."), pullmethod);
	  ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM,
	                "notify-status-code", IPP_ATTRIBUTES);
	  return;
	}
      }
      else if (!strcmp(attr->name, "notify-charset") &&
               attr->value_tag == IPP_TAG_CHARSET &&
	       strcmp(attr->values[0].string.text, "us-ascii") &&
	       strcmp(attr->values[0].string.text, "utf-8"))
      {
        send_ipp_status(con, IPP_CHARSET,
	                _("Character set \"%s\" not supported."),
			attr->values[0].string.text);
	return;
      }
      else if (!strcmp(attr->name, "notify-natural-language") &&
               (attr->value_tag != IPP_TAG_LANGUAGE ||
	        strcmp(attr->values[0].string.text, DefaultLanguage)))
      {
        send_ipp_status(con, IPP_CHARSET,
	                _("Language \"%s\" not supported."),
			attr->values[0].string.text);
	return;
      }
      else if (!strcmp(attr->name, "notify-user-data") &&
               attr->value_tag == IPP_TAG_STRING)
      {
        if (attr->num_values > 1 || attr->values[0].unknown.length > 63)
	{
          send_ipp_status(con, IPP_REQUEST_VALUE,
	                  _("The notify-user-data value is too large "
			    "(%d > 63 octets)."),
			  attr->values[0].unknown.length);
	  return;
	}

        user_data = attr;
      }
      else if (!strcmp(attr->name, "notify-events") &&
               attr->value_tag == IPP_TAG_KEYWORD)
        notify_events = attr;
      else if (!strcmp(attr->name, "notify-lease-duration") &&
               attr->value_tag == IPP_TAG_INTEGER)
        lease = attr->values[0].integer;
      else if (!strcmp(attr->name, "notify-time-interval") &&
               attr->value_tag == IPP_TAG_INTEGER)
        interval = attr->values[0].integer;
      else if (!strcmp(attr->name, "notify-job-id") &&
               attr->value_tag == IPP_TAG_INTEGER)
        jobid = attr->values[0].integer;

      attr = attr->next;
    }

    if (notify_events)
    {
      for (i = 0; i < notify_events->num_values; i ++)
	mask |= cupsdEventValue(notify_events->values[i].string.text);
    }

    if (recipient)
      cupsdLogMessage(CUPSD_LOG_DEBUG, "recipient=\"%s\"", recipient);
    if (pullmethod)
      cupsdLogMessage(CUPSD_LOG_DEBUG, "pullmethod=\"%s\"", pullmethod);
    cupsdLogMessage(CUPSD_LOG_DEBUG, "notify-lease-duration=%d", lease);
    cupsdLogMessage(CUPSD_LOG_DEBUG, "notify-time-interval=%d", interval);

    if (!recipient && !pullmethod)
      break;

    if (mask == CUPSD_EVENT_NONE)
    {
      if (jobid)
        mask = CUPSD_EVENT_JOB_COMPLETED;
      else if (printer)
        mask = CUPSD_EVENT_PRINTER_STATE_CHANGED;
      else
      {
        send_ipp_status(con, IPP_BAD_REQUEST,
	                _("notify-events not specified."));
	return;
      }
    }

    if (MaxLeaseDuration && (lease == 0 || lease > MaxLeaseDuration))
    {
      cupsdLogMessage(CUPSD_LOG_INFO,
                      "create_subscriptions: Limiting notify-lease-duration to "
		      "%d seconds.",
		      MaxLeaseDuration);
      lease = MaxLeaseDuration;
    }

    if (jobid)
    {
      if ((job = cupsdFindJob(jobid)) == NULL)
      {
	send_ipp_status(con, IPP_NOT_FOUND, _("Job #%d does not exist."),
	                jobid);
	return;
      }
    }
    else
      job = NULL;

    if ((sub = cupsdAddSubscription(mask, printer, job, recipient, 0)) == NULL)
    {
      send_ipp_status(con, IPP_TOO_MANY_SUBSCRIPTIONS,
		      _("There are too many subscriptions."));
      return;
    }

    if (job)
      cupsdLogMessage(CUPSD_LOG_DEBUG, "Added subscription #%d for job %d.",
		      sub->id, job->id);
    else if (printer)
      cupsdLogMessage(CUPSD_LOG_DEBUG,
                      "Added subscription #%d for printer \"%s\".",
		      sub->id, printer->name);
    else
      cupsdLogMessage(CUPSD_LOG_DEBUG, "Added subscription #%d for server.",
		      sub->id);

    sub->interval = interval;
    sub->lease    = lease;
    sub->expire   = lease ? time(NULL) + lease : 0;

    cupsdSetString(&sub->owner, username);

    if (user_data)
    {
      sub->user_data_len = user_data->values[0].unknown.length;
      memcpy(sub->user_data, user_data->values[0].unknown.data,
             (size_t)sub->user_data_len);
    }

    ippAddSeparator(con->response);
    ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
                  "notify-subscription-id", sub->id);

    con->response->request.status.status_code = IPP_OK;

    if (attr)
      attr = attr->next;
  }

  cupsdMarkDirty(CUPSD_DIRTY_SUBSCRIPTIONS);
}