int x25_rx_call_request(struct sk_buff *skb, struct x25_neigh *nb,
			unsigned int lci)
{
	struct sock *sk;
	struct sock *make;
	struct x25_sock *makex25;
	struct x25_address source_addr, dest_addr;
	struct x25_facilities facilities;
	struct x25_dte_facilities dte_facilities;
	int len, addr_len, rc;

	/*
	 *	Remove the LCI and frame type.
	 */
	skb_pull(skb, X25_STD_MIN_LEN);

	/*
	 *	Extract the X.25 addresses and convert them to ASCII strings,
	 *	and remove them.
	 *
	 *	Address block is mandatory in call request packets
	 */
	addr_len = x25_parse_address_block(skb, &source_addr, &dest_addr);
	if (addr_len <= 0)
		goto out_clear_request;
	skb_pull(skb, addr_len);

	/*
	 *	Get the length of the facilities, skip past them for the moment
	 *	get the call user data because this is needed to determine
	 *	the correct listener
	 *
	 *	Facilities length is mandatory in call request packets
	 */
	if (!pskb_may_pull(skb, 1))
		goto out_clear_request;
	len = skb->data[0] + 1;
	if (!pskb_may_pull(skb, len))
		goto out_clear_request;
	skb_pull(skb,len);

	/*
	 *	Ensure that the amount of call user data is valid.
	 */
	if (skb->len > X25_MAX_CUD_LEN)
		goto out_clear_request;

	/*
	 *	Get all the call user data so it can be used in
	 *	x25_find_listener and skb_copy_from_linear_data up ahead.
	 */
	if (!pskb_may_pull(skb, skb->len))
		goto out_clear_request;

	/*
	 *	Find a listener for the particular address/cud pair.
	 */
	sk = x25_find_listener(&source_addr,skb);
	skb_push(skb,len);

	if (sk != NULL && sk_acceptq_is_full(sk)) {
		goto out_sock_put;
	}

	/*
	 *	We dont have any listeners for this incoming call.
	 *	Try forwarding it.
	 */
	if (sk == NULL) {
		skb_push(skb, addr_len + X25_STD_MIN_LEN);
		if (sysctl_x25_forward &&
				x25_forward_call(&dest_addr, nb, skb, lci) > 0)
		{
			/* Call was forwarded, dont process it any more */
			kfree_skb(skb);
			rc = 1;
			goto out;
		} else {
			/* No listeners, can't forward, clear the call */
			goto out_clear_request;
		}
	}

	/*
	 *	Try to reach a compromise on the requested facilities.
	 */
	len = x25_negotiate_facilities(skb, sk, &facilities, &dte_facilities);
	if (len == -1)
		goto out_sock_put;

	/*
	 * current neighbour/link might impose additional limits
	 * on certain facilities
	 */

	x25_limit_facilities(&facilities, nb);

	/*
	 *	Try to create a new socket.
	 */
	make = x25_make_new(sk);
	if (!make)
		goto out_sock_put;

	/*
	 *	Remove the facilities
	 */
	skb_pull(skb, len);

	skb->sk     = make;
	make->sk_state = TCP_ESTABLISHED;

	makex25 = x25_sk(make);
	makex25->lci           = lci;
	makex25->dest_addr     = dest_addr;
	makex25->source_addr   = source_addr;
	x25_neigh_hold(nb);
	makex25->neighbour     = nb;
	makex25->facilities    = facilities;
	makex25->dte_facilities= dte_facilities;
	makex25->vc_facil_mask = x25_sk(sk)->vc_facil_mask;
	/* ensure no reverse facil on accept */
	makex25->vc_facil_mask &= ~X25_MASK_REVERSE;
	/* ensure no calling address extension on accept */
	makex25->vc_facil_mask &= ~X25_MASK_CALLING_AE;
	makex25->cudmatchlength = x25_sk(sk)->cudmatchlength;

	/* Normally all calls are accepted immediately */
	if (test_bit(X25_ACCPT_APPRV_FLAG, &makex25->flags)) {
		x25_write_internal(make, X25_CALL_ACCEPTED);
		makex25->state = X25_STATE_3;
	} else {
		makex25->state = X25_STATE_5;
	}

	/*
	 *	Incoming Call User Data.
	 */
	skb_copy_from_linear_data(skb, makex25->calluserdata.cuddata, skb->len);
	makex25->calluserdata.cudlength = skb->len;

	sk_acceptq_added(sk);

	x25_insert_socket(make);

	skb_queue_head(&sk->sk_receive_queue, skb);

	x25_start_heartbeat(make);

	if (!sock_flag(sk, SOCK_DEAD))
		sk->sk_data_ready(sk);
	rc = 1;
	sock_put(sk);
out:
	return rc;
out_sock_put:
	sock_put(sk);
out_clear_request:
	rc = 0;
	x25_transmit_clear_request(nb, lci, 0x01);
	goto out;
}


net/x25: Fix null-ptr-deref caused by x25_disconnect

When the link layer is terminating, x25->neighbour will be set to NULL
in x25_disconnect(). As a result, it could cause null-ptr-deref bugs in
x25_sendmsg(),x25_recvmsg() and x25_connect(). One of the bugs is
shown below.

    (Thread 1)                 |  (Thread 2)
x25_link_terminated()          | x25_recvmsg()
 x25_kill_by_neigh()           |  ...
  x25_disconnect()             |  lock_sock(sk)
   ...                         |  ...
   x25->neighbour = NULL //(1) |
   ...                         |  x25->neighbour->extended //(2)

The code sets NULL to x25->neighbour in position (1) and dereferences
x25->neighbour in position (2), which could cause null-ptr-deref bug.

This patch adds lock_sock() in x25_kill_by_neigh() in order to synchronize
with x25_sendmsg(), x25_recvmsg() and x25_connect(). What`s more, the
sock held by lock_sock() is not NULL, because it is extracted from x25_list
and uses x25_list_lock to synchronize.

Fixes: 4becb7ee5b3d ("net/x25: Fix x25_neigh refcnt leak when x25 disconnect")
Signed-off-by: Duoming Zhou <duoming@zju.edu.cn>
Reviewed-by: Lin Ma <linma@zju.edu.cn>
Signed-off-by: David S. Miller <davem@davemloft.net>

static struct sock *x25_find_listener(struct x25_address *addr,
					struct sk_buff *skb)
{
	struct sock *s;
	struct sock *next_best;

	read_lock_bh(&x25_list_lock);
	next_best = NULL;

	sk_for_each(s, &x25_list)
		if ((!strcmp(addr->x25_addr,
			x25_sk(s)->source_addr.x25_addr) ||
				!strcmp(x25_sk(s)->source_addr.x25_addr,
					null_x25_address.x25_addr)) &&
					s->sk_state == TCP_LISTEN) {
			/*
			 * Found a listening socket, now check the incoming
			 * call user data vs this sockets call user data
			 */
			if (x25_sk(s)->cudmatchlength > 0 &&
				skb->len >= x25_sk(s)->cudmatchlength) {
				if((memcmp(x25_sk(s)->calluserdata.cuddata,
					skb->data,
					x25_sk(s)->cudmatchlength)) == 0) {
					sock_hold(s);
					goto found;
				 }
			} else
				next_best = s;
		}
	if (next_best) {
		s = next_best;
		sock_hold(s);
		goto found;
	}
	s = NULL;
found:
	read_unlock_bh(&x25_list_lock);
	return s;
}


static void __x25_destroy_socket(struct sock *sk)
{
	struct sk_buff *skb;

	x25_stop_heartbeat(sk);
	x25_stop_timer(sk);

	x25_remove_socket(sk);
	x25_clear_queues(sk);		/* Flush the queues */

	while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
		if (skb->sk != sk) {		/* A pending connection */
			/*
			 * Queue the unaccepted socket for death
			 */
			skb->sk->sk_state = TCP_LISTEN;
			sock_set_flag(skb->sk, SOCK_DEAD);
			x25_start_heartbeat(skb->sk);
			x25_sk(skb->sk)->state = X25_STATE_0;
		}

		kfree_skb(skb);
	}

	if (sk_has_allocations(sk)) {
		/* Defer: outstanding buffers */
		sk->sk_timer.expires  = jiffies + 10 * HZ;
		sk->sk_timer.function = x25_destroy_timer;
		add_timer(&sk->sk_timer);
	} else {
		/* drop last reference so sock_put will free */
		__sock_put(sk);
	}
}


static int x25_connect(struct socket *sock, struct sockaddr *uaddr,
		       int addr_len, int flags)
{
	struct sock *sk = sock->sk;
	struct x25_sock *x25 = x25_sk(sk);
	struct sockaddr_x25 *addr = (struct sockaddr_x25 *)uaddr;
	struct x25_route *rt;
	int rc = 0;

	lock_sock(sk);
	if (sk->sk_state == TCP_ESTABLISHED && sock->state == SS_CONNECTING) {
		sock->state = SS_CONNECTED;
		goto out; /* Connect completed during a ERESTARTSYS event */
	}

	rc = -ECONNREFUSED;
	if (sk->sk_state == TCP_CLOSE && sock->state == SS_CONNECTING) {
		sock->state = SS_UNCONNECTED;
		goto out;
	}

	rc = -EISCONN;	/* No reconnect on a seqpacket socket */
	if (sk->sk_state == TCP_ESTABLISHED)
		goto out;

	rc = -EALREADY;	/* Do nothing if call is already in progress */
	if (sk->sk_state == TCP_SYN_SENT)
		goto out;

	sk->sk_state   = TCP_CLOSE;
	sock->state = SS_UNCONNECTED;

	rc = -EINVAL;
	if (addr_len != sizeof(struct sockaddr_x25) ||
	    addr->sx25_family != AF_X25 ||
	    strnlen(addr->sx25_addr.x25_addr, X25_ADDR_LEN) == X25_ADDR_LEN)
		goto out;

	rc = -ENETUNREACH;
	rt = x25_get_route(&addr->sx25_addr);
	if (!rt)
		goto out;

	x25->neighbour = x25_get_neigh(rt->dev);
	if (!x25->neighbour)
		goto out_put_route;

	x25_limit_facilities(&x25->facilities, x25->neighbour);

	x25->lci = x25_new_lci(x25->neighbour);
	if (!x25->lci)
		goto out_put_neigh;

	rc = -EINVAL;
	if (sock_flag(sk, SOCK_ZAPPED)) /* Must bind first - autobinding does not work */
		goto out_put_neigh;

	if (!strcmp(x25->source_addr.x25_addr, null_x25_address.x25_addr))
		memset(&x25->source_addr, '\0', X25_ADDR_LEN);

	x25->dest_addr = addr->sx25_addr;

	/* Move to connecting socket, start sending Connect Requests */
	sock->state   = SS_CONNECTING;
	sk->sk_state  = TCP_SYN_SENT;

	x25->state = X25_STATE_1;

	x25_write_internal(sk, X25_CALL_REQUEST);

	x25_start_heartbeat(sk);
	x25_start_t21timer(sk);

	/* Now the loop */
	rc = -EINPROGRESS;
	if (sk->sk_state != TCP_ESTABLISHED && (flags & O_NONBLOCK))
		goto out;

	rc = x25_wait_for_connection_establishment(sk);
	if (rc)
		goto out_put_neigh;

	sock->state = SS_CONNECTED;
	rc = 0;
out_put_neigh:
	if (rc && x25->neighbour) {
		read_lock_bh(&x25_list_lock);
		x25_neigh_put(x25->neighbour);
		x25->neighbour = NULL;
		read_unlock_bh(&x25_list_lock);
		x25->state = X25_STATE_0;
	}
out_put_route:
	x25_route_put(rt);
out:
	release_sock(sk);
	return rc;
}


static int x25_release(struct socket *sock)
{
	struct sock *sk = sock->sk;
	struct x25_sock *x25;

	if (!sk)
		return 0;

	x25 = x25_sk(sk);

	sock_hold(sk);
	lock_sock(sk);
	switch (x25->state) {

		case X25_STATE_0:
		case X25_STATE_2:
			x25_disconnect(sk, 0, 0, 0);
			__x25_destroy_socket(sk);
			goto out;

		case X25_STATE_1:
		case X25_STATE_3:
		case X25_STATE_4:
			x25_clear_queues(sk);
			x25_write_internal(sk, X25_CLEAR_REQUEST);
			x25_start_t23timer(sk);
			x25->state = X25_STATE_2;
			sk->sk_state	= TCP_CLOSE;
			sk->sk_shutdown	|= SEND_SHUTDOWN;
			sk->sk_state_change(sk);
			sock_set_flag(sk, SOCK_DEAD);
			sock_set_flag(sk, SOCK_DESTROY);
			break;

		case X25_STATE_5:
			x25_write_internal(sk, X25_CLEAR_REQUEST);
			x25_disconnect(sk, 0, 0, 0);
			__x25_destroy_socket(sk);
			goto out;
	}

	sock_orphan(sk);
out:
	release_sock(sk);
	sock_put(sk);
	return 0;
}


static int x25_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
{
	struct sock *sk = sock->sk;
	struct x25_sock *x25 = x25_sk(sk);
	DECLARE_SOCKADDR(struct sockaddr_x25 *, usx25, msg->msg_name);
	struct sockaddr_x25 sx25;
	struct sk_buff *skb;
	unsigned char *asmptr;
	int noblock = msg->msg_flags & MSG_DONTWAIT;
	size_t size;
	int qbit = 0, rc = -EINVAL;

	lock_sock(sk);
	if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_OOB|MSG_EOR|MSG_CMSG_COMPAT))
		goto out;

	/* we currently don't support segmented records at the user interface */
	if (!(msg->msg_flags & (MSG_EOR|MSG_OOB)))
		goto out;

	rc = -EADDRNOTAVAIL;
	if (sock_flag(sk, SOCK_ZAPPED))
		goto out;

	rc = -EPIPE;
	if (sk->sk_shutdown & SEND_SHUTDOWN) {
		send_sig(SIGPIPE, current, 0);
		goto out;
	}

	rc = -ENETUNREACH;
	if (!x25->neighbour)
		goto out;

	if (usx25) {
		rc = -EINVAL;
		if (msg->msg_namelen < sizeof(sx25))
			goto out;
		memcpy(&sx25, usx25, sizeof(sx25));
		rc = -EISCONN;
		if (strcmp(x25->dest_addr.x25_addr, sx25.sx25_addr.x25_addr))
			goto out;
		rc = -EINVAL;
		if (sx25.sx25_family != AF_X25)
			goto out;
	} else {
		/*
		 *	FIXME 1003.1g - if the socket is like this because
		 *	it has become closed (not started closed) we ought
		 *	to SIGPIPE, EPIPE;
		 */
		rc = -ENOTCONN;
		if (sk->sk_state != TCP_ESTABLISHED)
			goto out;

		sx25.sx25_family = AF_X25;
		sx25.sx25_addr   = x25->dest_addr;
	}

	/* Sanity check the packet size */
	if (len > 65535) {
		rc = -EMSGSIZE;
		goto out;
	}

	SOCK_DEBUG(sk, "x25_sendmsg: sendto: Addresses built.\n");

	/* Build a packet */
	SOCK_DEBUG(sk, "x25_sendmsg: sendto: building packet.\n");

	if ((msg->msg_flags & MSG_OOB) && len > 32)
		len = 32;

	size = len + X25_MAX_L2_LEN + X25_EXT_MIN_LEN;

	release_sock(sk);
	skb = sock_alloc_send_skb(sk, size, noblock, &rc);
	lock_sock(sk);
	if (!skb)
		goto out;
	X25_SKB_CB(skb)->flags = msg->msg_flags;

	skb_reserve(skb, X25_MAX_L2_LEN + X25_EXT_MIN_LEN);

	/*
	 *	Put the data on the end
	 */
	SOCK_DEBUG(sk, "x25_sendmsg: Copying user data\n");

	skb_reset_transport_header(skb);
	skb_put(skb, len);

	rc = memcpy_from_msg(skb_transport_header(skb), msg, len);
	if (rc)
		goto out_kfree_skb;

	/*
	 *	If the Q BIT Include socket option is in force, the first
	 *	byte of the user data is the logical value of the Q Bit.
	 */
	if (test_bit(X25_Q_BIT_FLAG, &x25->flags)) {
		if (!pskb_may_pull(skb, 1))
			goto out_kfree_skb;

		qbit = skb->data[0];
		skb_pull(skb, 1);
	}

	/*
	 *	Push down the X.25 header
	 */
	SOCK_DEBUG(sk, "x25_sendmsg: Building X.25 Header.\n");

	if (msg->msg_flags & MSG_OOB) {
		if (x25->neighbour->extended) {
			asmptr    = skb_push(skb, X25_STD_MIN_LEN);
			*asmptr++ = ((x25->lci >> 8) & 0x0F) | X25_GFI_EXTSEQ;
			*asmptr++ = (x25->lci >> 0) & 0xFF;
			*asmptr++ = X25_INTERRUPT;
		} else {
			asmptr    = skb_push(skb, X25_STD_MIN_LEN);
			*asmptr++ = ((x25->lci >> 8) & 0x0F) | X25_GFI_STDSEQ;
			*asmptr++ = (x25->lci >> 0) & 0xFF;
			*asmptr++ = X25_INTERRUPT;
		}
	} else {
		if (x25->neighbour->extended) {
			/* Build an Extended X.25 header */
			asmptr    = skb_push(skb, X25_EXT_MIN_LEN);
			*asmptr++ = ((x25->lci >> 8) & 0x0F) | X25_GFI_EXTSEQ;
			*asmptr++ = (x25->lci >> 0) & 0xFF;
			*asmptr++ = X25_DATA;
			*asmptr++ = X25_DATA;
		} else {
			/* Build an Standard X.25 header */
			asmptr    = skb_push(skb, X25_STD_MIN_LEN);
			*asmptr++ = ((x25->lci >> 8) & 0x0F) | X25_GFI_STDSEQ;
			*asmptr++ = (x25->lci >> 0) & 0xFF;
			*asmptr++ = X25_DATA;
		}

		if (qbit)
			skb->data[0] |= X25_Q_BIT;
	}

	SOCK_DEBUG(sk, "x25_sendmsg: Built header.\n");
	SOCK_DEBUG(sk, "x25_sendmsg: Transmitting buffer\n");

	rc = -ENOTCONN;
	if (sk->sk_state != TCP_ESTABLISHED)
		goto out_kfree_skb;

	if (msg->msg_flags & MSG_OOB)
		skb_queue_tail(&x25->interrupt_out_queue, skb);
	else {
		rc = x25_output(sk, skb);
		len = rc;
		if (rc < 0)
			kfree_skb(skb);
		else if (test_bit(X25_Q_BIT_FLAG, &x25->flags))
			len++;
	}

	x25_kick(sk);
	rc = len;
out:
	release_sock(sk);
	return rc;
out_kfree_skb:
	kfree_skb(skb);
	goto out;
}


static unsigned int x25_new_lci(struct x25_neigh *nb)
{
	unsigned int lci = 1;
	struct sock *sk;

	while ((sk = x25_find_socket(lci, nb)) != NULL) {
		sock_put(sk);
		if (++lci == 4096) {
			lci = 0;
			break;
		}
		cond_resched();
	}

	return lci;
}


static int x25_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
	struct sock *sk = sock->sk;
	struct x25_sock *x25 = x25_sk(sk);
	void __user *argp = (void __user *)arg;
	int rc;

	switch (cmd) {
	case TIOCOUTQ: {
		int amount;

		amount = sk->sk_sndbuf - sk_wmem_alloc_get(sk);
		if (amount < 0)
			amount = 0;
		rc = put_user(amount, (unsigned int __user *)argp);
		break;
	}

	case TIOCINQ: {
		struct sk_buff *skb;
		int amount = 0;
		/*
		 * These two are safe on a single CPU system as
		 * only user tasks fiddle here
		 */
		lock_sock(sk);
		if ((skb = skb_peek(&sk->sk_receive_queue)) != NULL)
			amount = skb->len;
		release_sock(sk);
		rc = put_user(amount, (unsigned int __user *)argp);
		break;
	}

	case SIOCGIFADDR:
	case SIOCSIFADDR:
	case SIOCGIFDSTADDR:
	case SIOCSIFDSTADDR:
	case SIOCGIFBRDADDR:
	case SIOCSIFBRDADDR:
	case SIOCGIFNETMASK:
	case SIOCSIFNETMASK:
	case SIOCGIFMETRIC:
	case SIOCSIFMETRIC:
		rc = -EINVAL;
		break;
	case SIOCADDRT:
	case SIOCDELRT:
		rc = -EPERM;
		if (!capable(CAP_NET_ADMIN))
			break;
		rc = x25_route_ioctl(cmd, argp);
		break;
	case SIOCX25GSUBSCRIP:
		rc = x25_subscr_ioctl(cmd, argp);
		break;
	case SIOCX25SSUBSCRIP:
		rc = -EPERM;
		if (!capable(CAP_NET_ADMIN))
			break;
		rc = x25_subscr_ioctl(cmd, argp);
		break;
	case SIOCX25GFACILITIES: {
		lock_sock(sk);
		rc = copy_to_user(argp, &x25->facilities,
				  sizeof(x25->facilities))
			? -EFAULT : 0;
		release_sock(sk);
		break;
	}

	case SIOCX25SFACILITIES: {
		struct x25_facilities facilities;
		rc = -EFAULT;
		if (copy_from_user(&facilities, argp, sizeof(facilities)))
			break;
		rc = -EINVAL;
		lock_sock(sk);
		if (sk->sk_state != TCP_LISTEN &&
		    sk->sk_state != TCP_CLOSE)
			goto out_fac_release;
		if (facilities.pacsize_in < X25_PS16 ||
		    facilities.pacsize_in > X25_PS4096)
			goto out_fac_release;
		if (facilities.pacsize_out < X25_PS16 ||
		    facilities.pacsize_out > X25_PS4096)
			goto out_fac_release;
		if (facilities.winsize_in < 1 ||
		    facilities.winsize_in > 127)
			goto out_fac_release;
		if (facilities.throughput) {
			int out = facilities.throughput & 0xf0;
			int in  = facilities.throughput & 0x0f;
			if (!out)
				facilities.throughput |=
					X25_DEFAULT_THROUGHPUT << 4;
			else if (out < 0x30 || out > 0xD0)
				goto out_fac_release;
			if (!in)
				facilities.throughput |=
					X25_DEFAULT_THROUGHPUT;
			else if (in < 0x03 || in > 0x0D)
				goto out_fac_release;
		}
		if (facilities.reverse &&
		    (facilities.reverse & 0x81) != 0x81)
			goto out_fac_release;
		x25->facilities = facilities;
		rc = 0;
out_fac_release:
		release_sock(sk);
		break;
	}

	case SIOCX25GDTEFACILITIES: {
		lock_sock(sk);
		rc = copy_to_user(argp, &x25->dte_facilities,
				  sizeof(x25->dte_facilities));
		release_sock(sk);
		if (rc)
			rc = -EFAULT;
		break;
	}

	case SIOCX25SDTEFACILITIES: {
		struct x25_dte_facilities dtefacs;
		rc = -EFAULT;
		if (copy_from_user(&dtefacs, argp, sizeof(dtefacs)))
			break;
		rc = -EINVAL;
		lock_sock(sk);
		if (sk->sk_state != TCP_LISTEN &&
		    sk->sk_state != TCP_CLOSE)
			goto out_dtefac_release;
		if (dtefacs.calling_len > X25_MAX_AE_LEN)
			goto out_dtefac_release;
		if (dtefacs.called_len > X25_MAX_AE_LEN)
			goto out_dtefac_release;
		x25->dte_facilities = dtefacs;
		rc = 0;
out_dtefac_release:
		release_sock(sk);
		break;
	}

	case SIOCX25GCALLUSERDATA: {
		lock_sock(sk);
		rc = copy_to_user(argp, &x25->calluserdata,
				  sizeof(x25->calluserdata))
			? -EFAULT : 0;
		release_sock(sk);
		break;
	}

	case SIOCX25SCALLUSERDATA: {
		struct x25_calluserdata calluserdata;

		rc = -EFAULT;
		if (copy_from_user(&calluserdata, argp, sizeof(calluserdata)))
			break;
		rc = -EINVAL;
		if (calluserdata.cudlength > X25_MAX_CUD_LEN)
			break;
		lock_sock(sk);
		x25->calluserdata = calluserdata;
		release_sock(sk);
		rc = 0;
		break;
	}

	case SIOCX25GCAUSEDIAG: {
		lock_sock(sk);
		rc = copy_to_user(argp, &x25->causediag, sizeof(x25->causediag))
			? -EFAULT : 0;
		release_sock(sk);
		break;
	}

	case SIOCX25SCAUSEDIAG: {
		struct x25_causediag causediag;
		rc = -EFAULT;
		if (copy_from_user(&causediag, argp, sizeof(causediag)))
			break;
		lock_sock(sk);
		x25->causediag = causediag;
		release_sock(sk);
		rc = 0;
		break;

	}

	case SIOCX25SCUDMATCHLEN: {
		struct x25_subaddr sub_addr;
		rc = -EINVAL;
		lock_sock(sk);
		if(sk->sk_state != TCP_CLOSE)
			goto out_cud_release;
		rc = -EFAULT;
		if (copy_from_user(&sub_addr, argp,
				   sizeof(sub_addr)))
			goto out_cud_release;
		rc = -EINVAL;
		if (sub_addr.cudmatchlength > X25_MAX_CUD_LEN)
			goto out_cud_release;
		x25->cudmatchlength = sub_addr.cudmatchlength;
		rc = 0;
out_cud_release:
		release_sock(sk);
		break;
	}

	case SIOCX25CALLACCPTAPPRV: {
		rc = -EINVAL;
		lock_sock(sk);
		if (sk->sk_state == TCP_CLOSE) {
			clear_bit(X25_ACCPT_APPRV_FLAG, &x25->flags);
			rc = 0;
		}
		release_sock(sk);
		break;
	}

	case SIOCX25SENDCALLACCPT:  {
		rc = -EINVAL;
		lock_sock(sk);
		if (sk->sk_state != TCP_ESTABLISHED)
			goto out_sendcallaccpt_release;
		/* must call accptapprv above */
		if (test_bit(X25_ACCPT_APPRV_FLAG, &x25->flags))
			goto out_sendcallaccpt_release;
		x25_write_internal(sk, X25_CALL_ACCEPTED);
		x25->state = X25_STATE_3;
		rc = 0;
out_sendcallaccpt_release:
		release_sock(sk);
		break;
	}

	default:
		rc = -ENOIOCTLCMD;
		break;
	}

	return rc;
}


static void x25_destroy_timer(struct timer_list *t)
{
	struct sock *sk = from_timer(sk, t, sk_timer);

	x25_destroy_socket_from_timer(sk);
}


static int x25_setsockopt(struct socket *sock, int level, int optname,
			  sockptr_t optval, unsigned int optlen)
{
	int opt;
	struct sock *sk = sock->sk;
	int rc = -ENOPROTOOPT;

	if (level != SOL_X25 || optname != X25_QBITINCL)
		goto out;

	rc = -EINVAL;
	if (optlen < sizeof(int))
		goto out;

	rc = -EFAULT;
	if (copy_from_sockptr(&opt, optval, sizeof(int)))
		goto out;

	if (opt)
		set_bit(X25_Q_BIT_FLAG, &x25_sk(sk)->flags);
	else
		clear_bit(X25_Q_BIT_FLAG, &x25_sk(sk)->flags);
	rc = 0;
out:
	return rc;
}


static struct sock *x25_make_new(struct sock *osk)
{
	struct sock *sk = NULL;
	struct x25_sock *x25, *ox25;

	if (osk->sk_type != SOCK_SEQPACKET)
		goto out;

	if ((sk = x25_alloc_socket(sock_net(osk), 0)) == NULL)
		goto out;

	x25 = x25_sk(sk);

	sk->sk_type        = osk->sk_type;
	sk->sk_priority    = osk->sk_priority;
	sk->sk_protocol    = osk->sk_protocol;
	sk->sk_rcvbuf      = osk->sk_rcvbuf;
	sk->sk_sndbuf      = osk->sk_sndbuf;
	sk->sk_state       = TCP_ESTABLISHED;
	sk->sk_backlog_rcv = osk->sk_backlog_rcv;
	sock_copy_flags(sk, osk);

	ox25 = x25_sk(osk);
	x25->t21        = ox25->t21;
	x25->t22        = ox25->t22;
	x25->t23        = ox25->t23;
	x25->t2         = ox25->t2;
	x25->flags	= ox25->flags;
	x25->facilities = ox25->facilities;
	x25->dte_facilities = ox25->dte_facilities;
	x25->cudmatchlength = ox25->cudmatchlength;

	clear_bit(X25_INTERRUPT_FLAG, &x25->flags);
	x25_init_timers(sk);
out:
	return sk;
}


void x25_kill_by_neigh(struct x25_neigh *nb)
{
	struct sock *s;

	write_lock_bh(&x25_list_lock);

	sk_for_each(s, &x25_list) {
		if (x25_sk(s)->neighbour == nb) {
			write_unlock_bh(&x25_list_lock);
			lock_sock(s);
			x25_disconnect(s, ENETUNREACH, 0, 0);
			release_sock(s);
			write_lock_bh(&x25_list_lock);
		}
	}
	write_unlock_bh(&x25_list_lock);

	/* Remove any related forwards */
	x25_clear_forward_by_dev(nb->dev);
}


int x25_parse_address_block(struct sk_buff *skb,
		struct x25_address *called_addr,
		struct x25_address *calling_addr)
{
	unsigned char len;
	int needed;
	int rc;

	if (!pskb_may_pull(skb, 1)) {
		/* packet has no address block */
		rc = 0;
		goto empty;
	}

	len = *skb->data;
	needed = 1 + ((len >> 4) + (len & 0x0f) + 1) / 2;

	if (!pskb_may_pull(skb, needed)) {
		/* packet is too short to hold the addresses it claims
		   to hold */
		rc = -1;
		goto empty;
	}

	return x25_addr_ntoa(skb->data, called_addr, calling_addr);

empty:
	*called_addr->x25_addr = 0;
	*calling_addr->x25_addr = 0;

	return rc;
}


static int x25_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
	struct sock *sk = sock->sk;
	struct sockaddr_x25 *addr = (struct sockaddr_x25 *)uaddr;
	int len, i, rc = 0;

	if (addr_len != sizeof(struct sockaddr_x25) ||
	    addr->sx25_family != AF_X25 ||
	    strnlen(addr->sx25_addr.x25_addr, X25_ADDR_LEN) == X25_ADDR_LEN) {
		rc = -EINVAL;
		goto out;
	}

	/* check for the null_x25_address */
	if (strcmp(addr->sx25_addr.x25_addr, null_x25_address.x25_addr)) {

		len = strlen(addr->sx25_addr.x25_addr);
		for (i = 0; i < len; i++) {
			if (!isdigit(addr->sx25_addr.x25_addr[i])) {
				rc = -EINVAL;
				goto out;
			}
		}
	}

	lock_sock(sk);
	if (sock_flag(sk, SOCK_ZAPPED)) {
		x25_sk(sk)->source_addr = addr->sx25_addr;
		x25_insert_socket(sk);
		sock_reset_flag(sk, SOCK_ZAPPED);
	} else {
		rc = -EINVAL;
	}
	release_sock(sk);
	SOCK_DEBUG(sk, "x25_bind: socket is bound\n");
out:
	return rc;
}


static int x25_create(struct net *net, struct socket *sock, int protocol,
		      int kern)
{
	struct sock *sk;
	struct x25_sock *x25;
	int rc = -EAFNOSUPPORT;

	if (!net_eq(net, &init_net))
		goto out;

	rc = -ESOCKTNOSUPPORT;
	if (sock->type != SOCK_SEQPACKET)
		goto out;

	rc = -EINVAL;
	if (protocol)
		goto out;

	rc = -ENOMEM;
	if ((sk = x25_alloc_socket(net, kern)) == NULL)
		goto out;

	x25 = x25_sk(sk);

	sock_init_data(sock, sk);

	x25_init_timers(sk);

	sock->ops    = &x25_proto_ops;
	sk->sk_protocol = protocol;
	sk->sk_backlog_rcv = x25_backlog_rcv;

	x25->t21   = sysctl_x25_call_request_timeout;
	x25->t22   = sysctl_x25_reset_request_timeout;
	x25->t23   = sysctl_x25_clear_request_timeout;
	x25->t2    = sysctl_x25_ack_holdback_timeout;
	x25->state = X25_STATE_0;
	x25->cudmatchlength = 0;
	set_bit(X25_ACCPT_APPRV_FLAG, &x25->flags);	/* normally no cud  */
							/* on call accept   */

	x25->facilities.winsize_in  = X25_DEFAULT_WINDOW_SIZE;
	x25->facilities.winsize_out = X25_DEFAULT_WINDOW_SIZE;
	x25->facilities.pacsize_in  = X25_DEFAULT_PACKET_SIZE;
	x25->facilities.pacsize_out = X25_DEFAULT_PACKET_SIZE;
	x25->facilities.throughput  = 0;	/* by default don't negotiate
						   throughput */
	x25->facilities.reverse     = X25_DEFAULT_REVERSE;
	x25->dte_facilities.calling_len = 0;
	x25->dte_facilities.called_len = 0;
	memset(x25->dte_facilities.called_ae, '\0',
			sizeof(x25->dte_facilities.called_ae));
	memset(x25->dte_facilities.calling_ae, '\0',
			sizeof(x25->dte_facilities.calling_ae));

	rc = 0;
out:
	return rc;
}


static int x25_accept(struct socket *sock, struct socket *newsock, int flags,
		      bool kern)
{
	struct sock *sk = sock->sk;
	struct sock *newsk;
	struct sk_buff *skb;
	int rc = -EINVAL;

	if (!sk)
		goto out;

	rc = -EOPNOTSUPP;
	if (sk->sk_type != SOCK_SEQPACKET)
		goto out;

	lock_sock(sk);
	rc = -EINVAL;
	if (sk->sk_state != TCP_LISTEN)
		goto out2;

	rc = x25_wait_for_data(sk, sk->sk_rcvtimeo);
	if (rc)
		goto out2;
	skb = skb_dequeue(&sk->sk_receive_queue);
	rc = -EINVAL;
	if (!skb->sk)
		goto out2;
	newsk		 = skb->sk;
	sock_graft(newsk, newsock);

	/* Now attach up the new socket */
	skb->sk = NULL;
	kfree_skb(skb);
	sk_acceptq_removed(sk);
	newsock->state = SS_CONNECTED;
	rc = 0;
out2:
	release_sock(sk);
out:
	return rc;
}


static struct sock *x25_alloc_socket(struct net *net, int kern)
{
	struct x25_sock *x25;
	struct sock *sk = sk_alloc(net, AF_X25, GFP_ATOMIC, &x25_proto, kern);

	if (!sk)
		goto out;

	sock_init_data(NULL, sk);

	x25 = x25_sk(sk);
	skb_queue_head_init(&x25->ack_queue);
	skb_queue_head_init(&x25->fragment_queue);
	skb_queue_head_init(&x25->interrupt_in_queue);
	skb_queue_head_init(&x25->interrupt_out_queue);
out:
	return sk;
}


static int x25_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
		       int flags)
{
	struct sock *sk = sock->sk;
	struct x25_sock *x25 = x25_sk(sk);
	DECLARE_SOCKADDR(struct sockaddr_x25 *, sx25, msg->msg_name);
	size_t copied;
	int qbit, header_len;
	struct sk_buff *skb;
	unsigned char *asmptr;
	int rc = -ENOTCONN;

	lock_sock(sk);

	if (x25->neighbour == NULL)
		goto out;

	header_len = x25->neighbour->extended ?
		X25_EXT_MIN_LEN : X25_STD_MIN_LEN;

	/*
	 * This works for seqpacket too. The receiver has ordered the queue for
	 * us! We do one quick check first though
	 */
	if (sk->sk_state != TCP_ESTABLISHED)
		goto out;

	if (flags & MSG_OOB) {
		rc = -EINVAL;
		if (sock_flag(sk, SOCK_URGINLINE) ||
		    !skb_peek(&x25->interrupt_in_queue))
			goto out;

		skb = skb_dequeue(&x25->interrupt_in_queue);

		if (!pskb_may_pull(skb, X25_STD_MIN_LEN))
			goto out_free_dgram;

		skb_pull(skb, X25_STD_MIN_LEN);

		/*
		 *	No Q bit information on Interrupt data.
		 */
		if (test_bit(X25_Q_BIT_FLAG, &x25->flags)) {
			asmptr  = skb_push(skb, 1);
			*asmptr = 0x00;
		}

		msg->msg_flags |= MSG_OOB;
	} else {
		/* Now we can treat all alike */
		release_sock(sk);
		skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT,
					flags & MSG_DONTWAIT, &rc);
		lock_sock(sk);
		if (!skb)
			goto out;

		if (!pskb_may_pull(skb, header_len))
			goto out_free_dgram;

		qbit = (skb->data[0] & X25_Q_BIT) == X25_Q_BIT;

		skb_pull(skb, header_len);

		if (test_bit(X25_Q_BIT_FLAG, &x25->flags)) {
			asmptr  = skb_push(skb, 1);
			*asmptr = qbit;
		}
	}

	skb_reset_transport_header(skb);
	copied = skb->len;

	if (copied > size) {
		copied = size;
		msg->msg_flags |= MSG_TRUNC;
	}

	/* Currently, each datagram always contains a complete record */
	msg->msg_flags |= MSG_EOR;

	rc = skb_copy_datagram_msg(skb, 0, msg, copied);
	if (rc)
		goto out_free_dgram;

	if (sx25) {
		sx25->sx25_family = AF_X25;
		sx25->sx25_addr   = x25->dest_addr;
		msg->msg_namelen = sizeof(*sx25);
	}

	x25_check_rbuf(sk);
	rc = copied;
out_free_dgram:
	skb_free_datagram(sk, skb);
out:
	release_sock(sk);
	return rc;
}


static int x25_device_event(struct notifier_block *this, unsigned long event,
			    void *ptr)
{
	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
	struct x25_neigh *nb;

	if (!net_eq(dev_net(dev), &init_net))
		return NOTIFY_DONE;

	if (dev->type == ARPHRD_X25) {
		switch (event) {
		case NETDEV_REGISTER:
		case NETDEV_POST_TYPE_CHANGE:
			x25_link_device_up(dev);
			break;
		case NETDEV_DOWN:
			nb = x25_get_neigh(dev);
			if (nb) {
				x25_link_terminated(nb);
				x25_neigh_put(nb);
			}
			x25_route_device_down(dev);
			break;
		case NETDEV_PRE_TYPE_CHANGE:
		case NETDEV_UNREGISTER:
			x25_link_device_down(dev);
			break;
		case NETDEV_CHANGE:
			if (!netif_carrier_ok(dev)) {
				nb = x25_get_neigh(dev);
				if (nb) {
					x25_link_terminated(nb);
					x25_neigh_put(nb);
				}
			}
			break;
		}
	}

	return NOTIFY_DONE;
}


Merge pull request from GHSA-hvq6-f89p-frvp

PJ_DEF(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg)
{
    pj_assert(neg && neg->state==PJMEDIA_SDP_NEG_STATE_WAIT_NEGO);
    return !neg->has_remote_answer;
}

PJSIP is a free and open source multimedia communication library written in C language implementing standard based protocols such as SIP, SDP, RTP, STUN, TURN, and ICE. In PJSIP version 2.10 and earlier, after an initial INVITE has been sent, when two 183 responses are received, with the first one causing negotiation failure, a crash will occur. This results in a denial of service.


static pj_status_t process_m_answer( pj_pool_t *pool,
				     pjmedia_sdp_media *offer,
				     pjmedia_sdp_media *answer,
				     pj_bool_t allow_asym)
{
    unsigned i;

    /* Check that the media type match our offer. */

    if (pj_strcmp(&answer->desc.media, &offer->desc.media)!=0) {
	/* The media type in the answer is different than the offer! */
	return PJMEDIA_SDPNEG_EINVANSMEDIA;
    }

    /* Check that transport in the answer match our offer. */

    /* At this point, transport type must be compatible,
     * the transport instance will do more validation later.
     */
    if (pjmedia_sdp_transport_cmp(&answer->desc.transport,
				  &offer->desc.transport)
	!= PJ_SUCCESS)
    {
	return PJMEDIA_SDPNEG_EINVANSTP;
    }

    /* Check if remote has rejected our offer */
    if (answer->desc.port == 0) {

	/* Remote has rejected our offer.
	 * Deactivate our media too.
	 */
	pjmedia_sdp_media_deactivate(pool, offer);

	/* Don't need to proceed */
	return PJ_SUCCESS;
    }

    /* Ticket #1148: check if remote answer does not set port to zero when
     * offered with port zero. Let's just tolerate it.
     */
    if (offer->desc.port == 0) {
	/* Don't need to proceed */
	return PJ_SUCCESS;
    }

    /* Process direction attributes */
    update_media_direction(pool, answer, offer);

    /* If asymetric media is allowed, then just check that remote answer has
     * codecs that are within the offer.
     *
     * Otherwise if asymetric media is not allowed, then we will choose only
     * one codec in our initial offer to match the answer.
     */
    if (allow_asym) {
	for (i=0; i<answer->desc.fmt_count; ++i) {
	    unsigned j;
	    pj_str_t *rem_fmt = &answer->desc.fmt[i];

	    for (j=0; j<offer->desc.fmt_count; ++j) {
		if (pj_strcmp(rem_fmt, &answer->desc.fmt[j])==0)
		    break;
	    }

	    if (j != offer->desc.fmt_count) {
		/* Found at least one common codec. */
		break;
	    }
	}

	if (i == answer->desc.fmt_count) {
	    /* No common codec in the answer! */
	    return PJMEDIA_SDPNEG_EANSNOMEDIA;
	}

	PJ_TODO(CHECK_SDP_NEGOTIATION_WHEN_ASYMETRIC_MEDIA_IS_ALLOWED);

    } else {
	/* Offer format priority based on answer format index/priority */
	unsigned offer_fmt_prior[PJMEDIA_MAX_SDP_FMT];

	/* Remove all format in the offer that has no matching answer */
	for (i=0; i<offer->desc.fmt_count;) {
	    unsigned pt;
	    pj_uint32_t j;
	    pj_str_t *fmt = &offer->desc.fmt[i];

	    /* Find matching answer */
	    pt = pj_strtoul(fmt);

	    if (pt < 96) {
		for (j=0; j<answer->desc.fmt_count; ++j) {
		    if (pj_strcmp(fmt, &answer->desc.fmt[j])==0)
			break;
		}
	    } else {
		/* This is dynamic payload type.
		 * For dynamic payload type, we must look the rtpmap and
		 * compare the encoding name.
		 */
		const pjmedia_sdp_attr *a;
		pjmedia_sdp_rtpmap or_;

		/* Get the rtpmap for the payload type in the offer. */
		a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt);
		if (!a) {
		    pj_assert(!"Bug! Offer should have been validated");
		    return PJ_EBUG;
		}
		pjmedia_sdp_attr_get_rtpmap(a, &or_);

		/* Find paylaod in answer SDP with matching
		 * encoding name and clock rate.
		 */
		for (j=0; j<answer->desc.fmt_count; ++j) {
		    a = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
						     &answer->desc.fmt[j]);
		    if (a) {
			pjmedia_sdp_rtpmap ar;
			pjmedia_sdp_attr_get_rtpmap(a, &ar);

			/* See if encoding name, clock rate, and channel
			 * count match
			 */
			if (!pj_stricmp(&or_.enc_name, &ar.enc_name) &&
			    or_.clock_rate == ar.clock_rate &&
			    (pj_stricmp(&or_.param, &ar.param)==0 ||
			     (ar.param.slen==1 && *ar.param.ptr=='1')))
			{
			    /* Call custom format matching callbacks */
			    if (custom_fmt_match(pool, &or_.enc_name,
						 offer, i, answer, j, 0) ==
				PJ_SUCCESS)
			    {
				/* Match! */
				break;
			    }
			}
		    }
		}
	    }

	    if (j == answer->desc.fmt_count) {
		/* This format has no matching answer.
		 * Remove it from our offer.
		 */
		pjmedia_sdp_attr *a;

		/* Remove rtpmap associated with this format */
		a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt);
		if (a)
		    pjmedia_sdp_media_remove_attr(offer, a);

		/* Remove fmtp associated with this format */
		a = pjmedia_sdp_media_find_attr2(offer, "fmtp", fmt);
		if (a)
		    pjmedia_sdp_media_remove_attr(offer, a);

		/* Remove this format from offer's array */
		pj_array_erase(offer->desc.fmt, sizeof(offer->desc.fmt[0]),
			       offer->desc.fmt_count, i);
		--offer->desc.fmt_count;

	    } else {
		offer_fmt_prior[i] = j;
		++i;
	    }
	}

	if (0 == offer->desc.fmt_count) {
	    /* No common codec in the answer! */
	    return PJMEDIA_SDPNEG_EANSNOMEDIA;
	}

	/* Post process:
	 * - Resort offer formats so the order match to the answer.
	 * - Remove answer formats that unmatches to the offer.
	 */

	/* Resort offer formats */
	for (i=0; i<offer->desc.fmt_count; ++i) {
	    unsigned j;
	    for (j=i+1; j<offer->desc.fmt_count; ++j) {
		if (offer_fmt_prior[i] > offer_fmt_prior[j]) {
		    unsigned tmp = offer_fmt_prior[i];
		    offer_fmt_prior[i] = offer_fmt_prior[j];
		    offer_fmt_prior[j] = tmp;
		    str_swap(&offer->desc.fmt[i], &offer->desc.fmt[j]);
		}
	    }
	}

	/* Remove unmatched answer formats */
	{
	    unsigned del_cnt = 0;
	    for (i=0; i<answer->desc.fmt_count;) {
		/* The offer is ordered now, also the offer_fmt_prior */
		if (i >= offer->desc.fmt_count ||
		    offer_fmt_prior[i]-del_cnt != i)
		{
		    pj_str_t *fmt = &answer->desc.fmt[i];
		    pjmedia_sdp_attr *a;

		    /* Remove rtpmap associated with this format */
		    a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", fmt);
		    if (a)
			pjmedia_sdp_media_remove_attr(answer, a);

		    /* Remove fmtp associated with this format */
		    a = pjmedia_sdp_media_find_attr2(answer, "fmtp", fmt);
		    if (a)
			pjmedia_sdp_media_remove_attr(answer, a);

		    /* Remove this format from answer's array */
		    pj_array_erase(answer->desc.fmt,
				   sizeof(answer->desc.fmt[0]),
				   answer->desc.fmt_count, i);
		    --answer->desc.fmt_count;

		    ++del_cnt;
		} else {
		    ++i;
		}
	    }
	}
    }

    /* Looks okay */
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_register_fmt_match_cb(
					const pj_str_t *fmt_name,
					pjmedia_sdp_neg_fmt_match_cb cb)
{
    struct fmt_match_cb_t *f = NULL;
    unsigned i;

    PJ_ASSERT_RETURN(fmt_name, PJ_EINVAL);

    /* Check if the callback for the format name has been registered */
    for (i = 0; i < fmt_match_cb_cnt; ++i) {
	if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0)
	    break;
    }

    /* Unregistration */

    if (cb == NULL) {
	if (i == fmt_match_cb_cnt)
	    return PJ_ENOTFOUND;

	pj_array_erase(fmt_match_cb, sizeof(fmt_match_cb[0]),
		       fmt_match_cb_cnt, i);
	fmt_match_cb_cnt--;

	return PJ_SUCCESS;
    }

    /* Registration */

    if (i < fmt_match_cb_cnt) {
	/* The same format name has been registered before */
	if (cb != fmt_match_cb[i].cb)
	    return PJ_EEXISTS;
	else
	    return PJ_SUCCESS;
    }

    if (fmt_match_cb_cnt >= PJ_ARRAY_SIZE(fmt_match_cb))
	return PJ_ETOOMANY;

    f = &fmt_match_cb[fmt_match_cb_cnt++];
    f->fmt_name = *fmt_name;
    f->cb = cb;

    return PJ_SUCCESS;
}


PJ_INLINE(void) rewrite_pt(pj_pool_t *pool, pj_str_t *attr_val,
			   const pj_str_t *old_pt, const pj_str_t *new_pt)
{
    int len_diff = (int)(new_pt->slen - old_pt->slen);

    /* Note that attribute value should be null-terminated. */
    if (len_diff > 0) {
	pj_str_t new_val;
	new_val.ptr = (char*)pj_pool_alloc(pool, attr_val->slen+len_diff+1);
	new_val.slen = attr_val->slen + len_diff;
	pj_memcpy(new_val.ptr + len_diff, attr_val->ptr, attr_val->slen + 1);
	*attr_val = new_val;
    } else if (len_diff < 0) {
	attr_val->slen += len_diff;
	pj_memmove(attr_val->ptr, attr_val->ptr - len_diff,
		   attr_val->slen + 1);
    }
    pj_memcpy(attr_val->ptr, new_pt->ptr, new_pt->slen);
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_prefer_remote_codec_order(
						pjmedia_sdp_neg *neg,
						pj_bool_t prefer_remote)
{
    PJ_ASSERT_RETURN(neg, PJ_EINVAL);
    neg->prefer_remote_codec_order = prefer_remote;
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_local( pjmedia_sdp_neg *neg,
					const pjmedia_sdp_session **local)
{
    PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
    PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE);

    *local = neg->active_local_sdp;
    return PJ_SUCCESS;
}


PJ_DEF(pjmedia_sdp_neg_state) pjmedia_sdp_neg_get_state( pjmedia_sdp_neg *neg )
{
    /* Check arguments are valid. */
    PJ_ASSERT_RETURN(neg != NULL, PJMEDIA_SDP_NEG_STATE_NULL);
    return neg->state;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool,
					       pjmedia_sdp_neg *neg,
					       pj_bool_t allow_asym)
{
    pj_status_t status;

    /* Check arguments are valid. */
    PJ_ASSERT_RETURN(pool && neg, PJ_EINVAL);

    /* Must be in STATE_WAIT_NEGO state. */
    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO,
		     PJMEDIA_SDPNEG_EINSTATE);

    /* Must have remote offer. */
    PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJ_EBUG);

    if (neg->has_remote_answer) {
	pjmedia_sdp_session *active;
	status = process_answer(pool, neg->neg_local_sdp, neg->neg_remote_sdp,
			        allow_asym, &active);
	if (status == PJ_SUCCESS) {
	    /* Only update active SDPs when negotiation is successfull */
	    neg->active_local_sdp = active;
	    neg->active_remote_sdp = neg->neg_remote_sdp;
	}
    } else {
	pjmedia_sdp_session *answer = NULL;

	status = create_answer(pool, neg->prefer_remote_codec_order,
                               neg->answer_with_multiple_codecs,
			       neg->neg_local_sdp, neg->neg_remote_sdp,
			       &answer);
	if (status == PJ_SUCCESS) {
	    pj_uint32_t active_ver;

	    if (neg->active_local_sdp)
		active_ver = neg->active_local_sdp->origin.version;
	    else
		active_ver = neg->initial_sdp->origin.version;

#if PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION
	    answer->origin.version = active_ver;

	    if ((neg->active_local_sdp == NULL) ||
		(pjmedia_sdp_session_cmp(answer, neg->active_local_sdp, 0)
								!= PJ_SUCCESS))
	    {
		++answer->origin.version;
	    }
#else
	    answer->origin.version = active_ver + 1;
#endif
	    /* Only update active SDPs when negotiation is successfull */
	    neg->active_local_sdp = answer;
	    neg->active_remote_sdp = neg->neg_remote_sdp;
	}
    }

    /* State is DONE regardless */
    neg->state = PJMEDIA_SDP_NEG_STATE_DONE;

    /* Save state */
    neg->answer_was_remote = neg->has_remote_answer;

    /* Revert back initial SDP if nego fails */
    if (status != PJ_SUCCESS)
	neg->initial_sdp = neg->initial_sdp_tmp;

    /* Clear temporary SDP */
    neg->initial_sdp_tmp = NULL;
    neg->neg_local_sdp = neg->neg_remote_sdp = NULL;
    neg->has_remote_answer = PJ_FALSE;

    return status;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_local_answer( pj_pool_t *pool,
				  pjmedia_sdp_neg *neg,
				  const pjmedia_sdp_session *local)
{
    /* Check arguments are valid. */
    PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);

    /* Can only do this in STATE_REMOTE_OFFER or WAIT_NEGO.
     * If we already provide local offer, then set_remote_answer() should
     * be called instead of this function.
     */
    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER ||
    		     neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO,
		     PJMEDIA_SDPNEG_EINSTATE);

    /* State now is STATE_WAIT_NEGO. */
    neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
    if (local) {
	neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
	if (neg->initial_sdp) {
	    /* Retain initial_sdp value. */
	    neg->initial_sdp_tmp = neg->initial_sdp;
	    neg->initial_sdp = pjmedia_sdp_session_clone(pool,
							 neg->initial_sdp);

	    /* I don't think there is anything in RFC 3264 that mandates
	     * answerer to place the same origin (and increment version)
	     * in the answer, but probably it won't hurt either.
	     * Note that the version will be incremented in
	     * pjmedia_sdp_neg_negotiate()
	     */
	    neg->neg_local_sdp->origin.id = neg->initial_sdp->origin.id;
	} else {
	    neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
	}
    } else {
	PJ_ASSERT_RETURN(neg->initial_sdp, PJMEDIA_SDPNEG_ENOINITIAL);
	neg->initial_sdp_tmp = neg->initial_sdp;
	neg->initial_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp);
	neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp);
    }

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer2(
                                    pj_pool_t *pool,
				    pjmedia_sdp_neg *neg,
                                    unsigned flags,
				    const pjmedia_sdp_session *local)
{
    pjmedia_sdp_session *new_offer;
    pjmedia_sdp_session *old_offer;
    unsigned oi; /* old offer media index */
    pj_status_t status;

    /* Check arguments are valid. */
    PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);

    /* Can only do this in STATE_DONE. */
    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE,
		     PJMEDIA_SDPNEG_EINSTATE);

    /* Validate the new offer */
    status = pjmedia_sdp_validate(local);
    if (status != PJ_SUCCESS)
	return status;

    /* Change state to STATE_LOCAL_OFFER */
    neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;

    /* When there is no active local SDP in state PJMEDIA_SDP_NEG_STATE_DONE,
     * it means that the previous initial SDP nego must have been failed,
     * so we'll just set the local SDP offer here.
     */
    if (!neg->active_local_sdp) {
	neg->initial_sdp_tmp = NULL;
	neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
	neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);

	return PJ_SUCCESS;
    }

    /* Init vars */
    old_offer = neg->active_local_sdp;
    new_offer = pjmedia_sdp_session_clone(pool, local);

    /* RFC 3264 Section 8: When issuing an offer that modifies the session,
     * the "o=" line of the new SDP MUST be identical to that in the
     * previous SDP, except that the version in the origin field MUST
     * increment by one from the previous SDP.
     */
    pj_strdup(pool, &new_offer->origin.user, &old_offer->origin.user);
    new_offer->origin.id = old_offer->origin.id;

    pj_strdup(pool, &new_offer->origin.net_type, &old_offer->origin.net_type);
    pj_strdup(pool, &new_offer->origin.addr_type,&old_offer->origin.addr_type);
    pj_strdup(pool, &new_offer->origin.addr, &old_offer->origin.addr);

    if ((flags & PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE) == 0) {
       /* Generating the new offer, in the case media lines doesn't match the
        * active SDP (e.g. current/active SDP's have m=audio and m=video lines,
        * and the new offer only has m=audio line), the negotiator will fix
        * the new offer by reordering and adding the missing media line with
        * port number set to zero.
        */
        for (oi = 0; oi < old_offer->media_count; ++oi) {
	    pjmedia_sdp_media *om;
	    pjmedia_sdp_media *nm;
	    unsigned ni; /* new offer media index */
	    pj_bool_t found = PJ_FALSE;

	    om = old_offer->media[oi];
	    for (ni = oi; ni < new_offer->media_count; ++ni) {
	        nm = new_offer->media[ni];
	        if (pj_strcmp(&nm->desc.media, &om->desc.media) == 0) {
		    if (ni != oi) {
		        /* The same media found but the position unmatched to
                         * the old offer, so let's put this media in the right
                         * place, and keep the order of the rest.
		         */
		        pj_array_insert(
                            new_offer->media,		 /* array    */
			    sizeof(new_offer->media[0]), /* elmt size*/
			    ni,				 /* count    */
		            oi,				 /* pos      */
			    &nm);			 /* new elmt */
		    }
		    found = PJ_TRUE;
		    break;
	        }
	    }
	    if (!found) {
	        pjmedia_sdp_media *m;

	        m = sdp_media_clone_deactivate(pool, om, om, local);

	        pj_array_insert(new_offer->media, sizeof(new_offer->media[0]),
			        new_offer->media_count++, oi, &m);
	    }
        }
    } else {
        /* If media type change is allowed, the negotiator only needs to fix
         * the new offer by adding the missing media line(s) with port number
         * set to zero.
         */
        for (oi = new_offer->media_count; oi < old_offer->media_count; ++oi) {
            pjmedia_sdp_media *m;

	    m = sdp_media_clone_deactivate(pool, old_offer->media[oi],
                                           old_offer->media[oi], local);

	    pj_array_insert(new_offer->media, sizeof(new_offer->media[0]),
	                    new_offer->media_count++, oi, &m);

        }
    }

    /* New_offer fixed */
#if PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION
    new_offer->origin.version = old_offer->origin.version;

    if (pjmedia_sdp_session_cmp(new_offer, neg->initial_sdp, 0) != PJ_SUCCESS)
    {
	++new_offer->origin.version;
    }
#else
    new_offer->origin.version = old_offer->origin.version + 1;
#endif

    neg->initial_sdp_tmp = neg->initial_sdp;
    neg->initial_sdp = new_offer;
    neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, new_offer);

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_remote( pjmedia_sdp_neg *neg,
				   const pjmedia_sdp_session **remote)
{
    PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
    PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE);

    *remote = neg->active_remote_sdp;
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_send_local_offer( pj_pool_t *pool,
				  pjmedia_sdp_neg *neg,
				  const pjmedia_sdp_session **offer)
{
    /* Check arguments are valid. */
    PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL);

    *offer = NULL;

    /* Can only do this in STATE_DONE or STATE_LOCAL_OFFER. */
    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE ||
		     neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
		     PJMEDIA_SDPNEG_EINSTATE);

    if (neg->state == PJMEDIA_SDP_NEG_STATE_DONE) {
	/* If in STATE_DONE, set the active SDP as the offer. */
	PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE);

	/* Retain initial SDP */
	if (neg->initial_sdp) {
	    neg->initial_sdp_tmp = neg->initial_sdp;
    	    neg->initial_sdp = pjmedia_sdp_session_clone(pool,
							 neg->initial_sdp);
	}

	neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
	neg->neg_local_sdp = pjmedia_sdp_session_clone(pool,
						       neg->active_local_sdp);

#if PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION
    	if (pjmedia_sdp_session_cmp(neg->neg_local_sdp,
    				    neg->initial_sdp, 0) != PJ_SUCCESS)
    	{
	    neg->neg_local_sdp->origin.version++;
    	}
#else
    	neg->neg_local_sdp->origin.version++;
#endif

	*offer = neg->neg_local_sdp;

    } else {
	/* We assume that we're in STATE_LOCAL_OFFER.
	 * In this case set the neg_local_sdp as the offer.
	 */
	*offer = neg->neg_local_sdp;
    }

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_local_offer( pj_pool_t *pool,
				      const pjmedia_sdp_session *local,
				      pjmedia_sdp_neg **p_neg)
{
    pjmedia_sdp_neg *neg;
    pj_status_t status;

    /* Check arguments are valid. */
    PJ_ASSERT_RETURN(pool && local && p_neg, PJ_EINVAL);

    *p_neg = NULL;

    /* Validate local offer. */
    PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status);

    /* Create and initialize negotiator. */
    neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg);
    PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM);

    neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
    neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER;
    neg->answer_with_multiple_codecs = PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS;
    neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
    neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);

    *p_neg = neg;
    return PJ_SUCCESS;
}


static pjmedia_sdp_media *sdp_media_clone_deactivate(
				    pj_pool_t *pool,
                                    const pjmedia_sdp_media *rem_med,
                                    const pjmedia_sdp_media *local_med,
                                    const pjmedia_sdp_session *local_sess)
{
    pjmedia_sdp_media *res;

    res = pjmedia_sdp_media_clone_deactivate(pool, rem_med);
    if (!res)
	return NULL;

    if (!res->conn && (!local_sess || !local_sess->conn)) {
	if (local_med && local_med->conn)
	    res->conn = pjmedia_sdp_conn_clone(pool, local_med->conn);
	else {
	    res->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
	    res->conn->net_type = pj_str("IN");
	    res->conn->addr_type = pj_str("IP4");
	    res->conn->addr = pj_str("127.0.0.1");
	}
    }

    return res;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_fmt_match(pj_pool_t *pool,
					      pjmedia_sdp_media *offer,
					      unsigned o_fmt_idx,
					      pjmedia_sdp_media *answer,
					      unsigned a_fmt_idx,
					      unsigned option)
{
    const pjmedia_sdp_attr *attr;
    pjmedia_sdp_rtpmap o_rtpmap, a_rtpmap;
    unsigned o_pt;
    unsigned a_pt;

    o_pt = pj_strtoul(&offer->desc.fmt[o_fmt_idx]);
    a_pt = pj_strtoul(&answer->desc.fmt[a_fmt_idx]);

    if (o_pt < 96 || a_pt < 96) {
	if (o_pt == a_pt)
	    return PJ_SUCCESS;
	else
	    return PJMEDIA_SDP_EFORMATNOTEQUAL;
    }

    /* Get the format rtpmap from the offer. */
    attr = pjmedia_sdp_media_find_attr2(offer, "rtpmap",
					&offer->desc.fmt[o_fmt_idx]);
    if (!attr) {
	pj_assert(!"Bug! Offer haven't been validated");
	return PJ_EBUG;
    }
    pjmedia_sdp_attr_get_rtpmap(attr, &o_rtpmap);

    /* Get the format rtpmap from the answer. */
    attr = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
					&answer->desc.fmt[a_fmt_idx]);
    if (!attr) {
	pj_assert(!"Bug! Answer haven't been validated");
	return PJ_EBUG;
    }
    pjmedia_sdp_attr_get_rtpmap(attr, &a_rtpmap);

    if (pj_stricmp(&o_rtpmap.enc_name, &a_rtpmap.enc_name) != 0 ||
	(o_rtpmap.clock_rate != a_rtpmap.clock_rate) ||
	(!(pj_stricmp(&o_rtpmap.param, &a_rtpmap.param) == 0 ||
	   (a_rtpmap.param.slen == 0 && o_rtpmap.param.slen == 1 &&
	    *o_rtpmap.param.ptr == '1') ||
	   (o_rtpmap.param.slen == 0 && a_rtpmap.param.slen == 1 &&
	    *a_rtpmap.param.ptr=='1'))))
    {
	return PJMEDIA_SDP_EFORMATNOTEQUAL;
    }

    return custom_fmt_match(pool, &o_rtpmap.enc_name,
			    offer, o_fmt_idx, answer, a_fmt_idx, option);
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_offer( pj_pool_t *pool,
				  pjmedia_sdp_neg *neg,
				  const pjmedia_sdp_session *remote)
{
    /* Check arguments are valid. */
    PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);

    /* Can only do this in STATE_DONE.
     * If we already provide local offer, then rx_remote_answer() should
     * be called instead of this function.
     */
    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE,
		     PJMEDIA_SDPNEG_EINSTATE);

    /* State now is STATE_REMOTE_OFFER. */
    neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;
    neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_remote( pjmedia_sdp_neg *neg,
				const pjmedia_sdp_session **remote)
{
    PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
    PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJMEDIA_SDPNEG_ENONEG);

    *remote = neg->neg_remote_sdp;
    return PJ_SUCCESS;
}


static void apply_answer_symmetric_pt(pj_pool_t *pool,
				      pjmedia_sdp_media *answer,
				      unsigned pt_cnt,
				      const pj_str_t pt_offer[],
				      const pj_str_t pt_answer[])
{
    pjmedia_sdp_attr *a_tmp[PJMEDIA_MAX_SDP_ATTR];
    unsigned i, a_tmp_cnt = 0;

    /* Rewrite the payload types in the answer if different to
     * the ones in the offer.
     */
    for (i = 0; i < pt_cnt; ++i) {
	pjmedia_sdp_attr *a;

	/* Skip if the PTs are the same already, e.g: static PT. */
	if (pj_strcmp(&pt_answer[i], &pt_offer[i]) == 0)
	    continue;

	/* Rewrite payload type in the answer to match to the offer */
	pj_strdup(pool, &answer->desc.fmt[i], &pt_offer[i]);

	/* Also update payload type in rtpmap */
	a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &pt_answer[i]);
	if (a) {
	    rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]);
	    /* Temporarily remove the attribute in case the new payload
	     * type is being used by another format in the media.
	     */
	    pjmedia_sdp_media_remove_attr(answer, a);
	    a_tmp[a_tmp_cnt++] = a;
	}

	/* Also update payload type in fmtp */
	a = pjmedia_sdp_media_find_attr2(answer, "fmtp", &pt_answer[i]);
	if (a) {
	    rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]);
	    /* Temporarily remove the attribute in case the new payload
	     * type is being used by another format in the media.
	     */
	    pjmedia_sdp_media_remove_attr(answer, a);
	    a_tmp[a_tmp_cnt++] = a;
	}
    }

    /* Return back 'rtpmap' and 'fmtp' attributes */
    for (i = 0; i < a_tmp_cnt; ++i)
	pjmedia_sdp_media_add_attr(answer, a_tmp[i]);
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg,
			       const pjmedia_sdp_session **local)
{
    PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
    PJ_ASSERT_RETURN(neg->neg_local_sdp, PJMEDIA_SDPNEG_ENONEG);

    *local = neg->neg_local_sdp;
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_answer( pj_pool_t *pool,
				   pjmedia_sdp_neg *neg,
				   const pjmedia_sdp_session *remote)
{
    /* Check arguments are valid. */
    PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);

    /* Can only do this in STATE_LOCAL_OFFER.
     * If we haven't provided local offer, then rx_remote_offer() should
     * be called instead of this function.
     */
    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
		     PJMEDIA_SDPNEG_EINSTATE);

    /* We're ready to negotiate. */
    neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
    neg->has_remote_answer = PJ_TRUE;
    neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool,
				    pjmedia_sdp_neg *neg,
				    const pjmedia_sdp_session *local)
{
    return pjmedia_sdp_neg_modify_local_offer2(pool, neg, 0, local);
}


static void str_swap(pj_str_t *str1, pj_str_t *str2)
{
    pj_str_t tmp = *str1;
    *str1 = *str2;
    *str2 = tmp;
}


static void update_media_direction(pj_pool_t *pool,
				   const pjmedia_sdp_media *remote,
				   pjmedia_sdp_media *local)
{
    pjmedia_dir old_dir = PJMEDIA_DIR_ENCODING_DECODING,
	        new_dir;

    /* Get the media direction of local SDP */
    if (pjmedia_sdp_media_find_attr2(local, "sendonly", NULL))
	old_dir = PJMEDIA_DIR_ENCODING;
    else if (pjmedia_sdp_media_find_attr2(local, "recvonly", NULL))
	old_dir = PJMEDIA_DIR_DECODING;
    else if (pjmedia_sdp_media_find_attr2(local, "inactive", NULL))
	old_dir = PJMEDIA_DIR_NONE;

    new_dir = old_dir;

    /* Adjust local media direction based on remote media direction */
    if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) {
	/* If remote has "a=inactive", then local is inactive too */

	new_dir = PJMEDIA_DIR_NONE;

    } else if(pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) {
	/* If remote has "a=sendonly", then set local to "recvonly" if
	 * it is currently "sendrecv". Otherwise if local is NOT "recvonly",
	 * then set local direction to "inactive".
	 */
	switch (old_dir) {
	case PJMEDIA_DIR_ENCODING_DECODING:
	    new_dir = PJMEDIA_DIR_DECODING;
	    break;
	case PJMEDIA_DIR_DECODING:
	    /* No change */
	    break;
	default:
	    new_dir = PJMEDIA_DIR_NONE;
	    break;
	}

    } else if(pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) {
	/* If remote has "a=recvonly", then set local to "sendonly" if
	 * it is currently "sendrecv". Otherwise if local is NOT "sendonly",
	 * then set local direction to "inactive"
	 */

	switch (old_dir) {
	case PJMEDIA_DIR_ENCODING_DECODING:
	    new_dir = PJMEDIA_DIR_ENCODING;
	    break;
	case PJMEDIA_DIR_ENCODING:
	    /* No change */
	    break;
	default:
	    new_dir = PJMEDIA_DIR_NONE;
	    break;
	}

    } else {
	/* Remote indicates "sendrecv" capability. No change to local
	 * direction
	 */
    }

    if (new_dir != old_dir) {
	pjmedia_sdp_attr *a = NULL;

	remove_all_media_directions(local);

	switch (new_dir) {
	case PJMEDIA_DIR_NONE:
	    a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
	    break;
	case PJMEDIA_DIR_ENCODING:
	    a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
	    break;
	case PJMEDIA_DIR_DECODING:
	    a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
	    break;
	default:
	    /* sendrecv */
	    break;
	}

	if (a) {
	    pjmedia_sdp_media_add_attr(local, a);
	}
    }
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool,
				      const pjmedia_sdp_session *initial,
				      const pjmedia_sdp_session *remote,
				      pjmedia_sdp_neg **p_neg)
{
    pjmedia_sdp_neg *neg;
    pj_status_t status;

    /* Check arguments are valid. */
    PJ_ASSERT_RETURN(pool && remote && p_neg, PJ_EINVAL);

    *p_neg = NULL;

    /* Validate remote offer and initial answer */
    status = pjmedia_sdp_validate2(remote, PJ_FALSE);
    if (status != PJ_SUCCESS)
	return status;

    /* Create and initialize negotiator. */
    neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg);
    PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM);

    neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER;
    neg->answer_with_multiple_codecs = PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS;
    neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);

    if (initial) {
	PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(initial))==PJ_SUCCESS,
			 status);

	neg->initial_sdp = pjmedia_sdp_session_clone(pool, initial);
	neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, initial);

	neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;

    } else {

	neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;

    }

    *p_neg = neg;
    return PJ_SUCCESS;
}


static pj_status_t match_offer(pj_pool_t *pool,
			       pj_bool_t prefer_remote_codec_order,
                               pj_bool_t answer_with_multiple_codecs,
			       const pjmedia_sdp_media *offer,
			       const pjmedia_sdp_media *preanswer,
			       const pjmedia_sdp_session *preanswer_sdp,
			       pjmedia_sdp_media **p_answer)
{
    unsigned i;
    pj_bool_t master_has_codec = 0,
	      master_has_other = 0,
	      found_matching_codec = 0,
	      found_matching_telephone_event = 0,
	      found_matching_other = 0;
    unsigned pt_answer_count = 0;
    pj_str_t pt_answer[PJMEDIA_MAX_SDP_FMT];
    pj_str_t pt_offer[PJMEDIA_MAX_SDP_FMT];
    pjmedia_sdp_media *answer;
    const pjmedia_sdp_media *master, *slave;
    unsigned nclockrate = 0, clockrate[PJMEDIA_MAX_SDP_FMT];
    unsigned ntel_clockrate = 0, tel_clockrate[PJMEDIA_MAX_SDP_FMT];

    /* If offer has zero port, just clone the offer */
    if (offer->desc.port == 0) {
	answer = sdp_media_clone_deactivate(pool, offer, preanswer,
					    preanswer_sdp);
	*p_answer = answer;
	return PJ_SUCCESS;
    }

    /* If the preanswer define zero port, this media is being rejected,
     * just clone the preanswer.
     */
    if (preanswer->desc.port == 0) {
	answer = pjmedia_sdp_media_clone(pool, preanswer);
	*p_answer = answer;
	return PJ_SUCCESS;
    }

    /* Set master/slave negotiator based on prefer_remote_codec_order. */
    if (prefer_remote_codec_order) {
	master = offer;
	slave  = preanswer;
    } else {
	master = preanswer;
	slave  = offer;
    }

    /* With the addition of telephone-event and dodgy MS RTC SDP,
     * the answer generation algorithm looks really shitty...
     */
    for (i=0; i<master->desc.fmt_count; ++i) {
	unsigned j;

	if (pj_isdigit(*master->desc.fmt[i].ptr)) {
	    /* This is normal/standard payload type, where it's identified
	     * by payload number.
	     */
	    unsigned pt;

	    pt = pj_strtoul(&master->desc.fmt[i]);

	    if (pt < 96) {
		/* For static payload type, it's enough to compare just
		 * the payload number.
		 */

		master_has_codec = 1;

		/* We just need to select one codec if not allowing multiple.
		 * Continue if we have selected matching codec for previous
		 * payload.
		 */
		if (!answer_with_multiple_codecs && found_matching_codec)
		    continue;

		/* Find matching codec in local descriptor. */
		for (j=0; j<slave->desc.fmt_count; ++j) {
		    unsigned p;
		    p = pj_strtoul(&slave->desc.fmt[j]);
		    if (p == pt && pj_isdigit(*slave->desc.fmt[j].ptr)) {
			unsigned k;

			found_matching_codec = 1;
			pt_offer[pt_answer_count] = slave->desc.fmt[j];
			pt_answer[pt_answer_count++] = slave->desc.fmt[j];

			/* Take note of clock rate for tel-event. Note: for
			 * static PT, we assume the clock rate is 8000.
			 */
			for (k=0; k<nclockrate; ++k)
			    if (clockrate[k] == 8000)
				break;
			if (k == nclockrate)
			    clockrate[nclockrate++] = 8000;
			break;
		    }
		}

	    } else {
		/* This is dynamic payload type.
		 * For dynamic payload type, we must look the rtpmap and
		 * compare the encoding name.
		 */
		const pjmedia_sdp_attr *a;
		pjmedia_sdp_rtpmap or_;
		pj_bool_t is_codec = 0;

		/* Get the rtpmap for the payload type in the master. */
		a = pjmedia_sdp_media_find_attr2(master, "rtpmap",
						 &master->desc.fmt[i]);
		if (!a) {
		    pj_assert(!"Bug! Offer should have been validated");
		    return PJMEDIA_SDP_EMISSINGRTPMAP;
		}
		pjmedia_sdp_attr_get_rtpmap(a, &or_);

		if (pj_stricmp2(&or_.enc_name, "telephone-event")) {
		    master_has_codec = 1;
		    if (!answer_with_multiple_codecs && found_matching_codec)
			continue;
		    is_codec = 1;
		}

		/* Find paylaod in our initial SDP with matching
		 * encoding name and clock rate.
		 */
		for (j=0; j<slave->desc.fmt_count; ++j) {
		    a = pjmedia_sdp_media_find_attr2(slave, "rtpmap",
						     &slave->desc.fmt[j]);
		    if (a) {
			pjmedia_sdp_rtpmap lr;
			pjmedia_sdp_attr_get_rtpmap(a, &lr);

			/* See if encoding name, clock rate, and
			 * channel count  match
			 */
			if (!pj_stricmp(&or_.enc_name, &lr.enc_name) &&
			    or_.clock_rate == lr.clock_rate &&
			    (pj_stricmp(&or_.param, &lr.param)==0 ||
			     (lr.param.slen==0 && or_.param.slen==1 &&
						 *or_.param.ptr=='1') ||
			     (or_.param.slen==0 && lr.param.slen==1 &&
						  *lr.param.ptr=='1')))
			{
			    /* Match! */
			    if (is_codec) {
				pjmedia_sdp_media *o_med, *a_med;
				unsigned o_fmt_idx, a_fmt_idx;
				unsigned k;

				o_med = (pjmedia_sdp_media*)offer;
				a_med = (pjmedia_sdp_media*)preanswer;
				o_fmt_idx = prefer_remote_codec_order? i:j;
				a_fmt_idx = prefer_remote_codec_order? j:i;

				/* Call custom format matching callbacks */
				if (custom_fmt_match(pool, &or_.enc_name,
						     o_med, o_fmt_idx,
						     a_med, a_fmt_idx,
						     ALLOW_MODIFY_ANSWER) !=
				    PJ_SUCCESS)
				{
				    continue;
				}
				found_matching_codec = 1;

				/* Take note of clock rate for tel-event */
				for (k=0; k<nclockrate; ++k)
				    if (clockrate[k] == or_.clock_rate)
					break;
				if (k == nclockrate)
				    clockrate[nclockrate++] = or_.clock_rate;
			    } else {
			    	unsigned k;

				/* Keep track of tel-event clock rate,
				 * to prevent duplicate.
				 */
				for (k=0; k<ntel_clockrate; ++k)
				    if (tel_clockrate[k] == or_.clock_rate)
					break;
				if (k < ntel_clockrate)
				    continue;

				tel_clockrate[ntel_clockrate++] = or_.clock_rate;
				found_matching_telephone_event = 1;
			    }

			    pt_offer[pt_answer_count] =
						prefer_remote_codec_order?
						offer->desc.fmt[i]:
						offer->desc.fmt[j];
			    pt_answer[pt_answer_count++] =
						prefer_remote_codec_order?
						preanswer->desc.fmt[j]:
						preanswer->desc.fmt[i];
			    break;
			}
		    }
		}
	    }

	} else {
	    /* This is a non-standard, brain damaged SDP where the payload
	     * type is non-numeric. It exists e.g. in Microsoft RTC based
	     * UA, to indicate instant messaging capability.
	     * Example:
	     *	- m=x-ms-message 5060 sip null
	     */
	    master_has_other = 1;
	    if (found_matching_other)
		continue;

	    for (j=0; j<slave->desc.fmt_count; ++j) {
		if (!pj_strcmp(&master->desc.fmt[i], &slave->desc.fmt[j])) {
		    /* Match */
		    found_matching_other = 1;
		    pt_offer[pt_answer_count] = prefer_remote_codec_order?
						offer->desc.fmt[i]:
						offer->desc.fmt[j];
		    pt_answer[pt_answer_count++] = prefer_remote_codec_order?
						   preanswer->desc.fmt[j]:
						   preanswer->desc.fmt[i];
		    break;
		}
	    }
	}
    }

    /* See if all types of master can be matched. */
    if (master_has_codec && !found_matching_codec) {
	return PJMEDIA_SDPNEG_NOANSCODEC;
    }

    /* If this comment is removed, negotiation will fail if remote has offered
       telephone-event and local is not configured with telephone-event

    if (offer_has_telephone_event && !found_matching_telephone_event) {
	return PJMEDIA_SDPNEG_NOANSTELEVENT;
    }
    */

    if (master_has_other && !found_matching_other) {
	return PJMEDIA_SDPNEG_NOANSUNKNOWN;
    }

    /* Seems like everything is in order. */

    /* Remove unwanted telephone-event formats. */
    if (found_matching_telephone_event) {
	pj_str_t first_televent_offer = {0};
	pj_str_t first_televent_answer = {0};
	unsigned matched_cnt = 0;

	for (i=0; i<pt_answer_count; ) {
	    const pjmedia_sdp_attr *a;
	    pjmedia_sdp_rtpmap r;
	    unsigned j;

	    /* Skip static PT, as telephone-event uses dynamic PT */
	    if (!pj_isdigit(*pt_answer[i].ptr) || pj_strtol(&pt_answer[i])<96)
	    {
		++i;
		continue;
	    }

	    /* Get the rtpmap for format. */
	    a = pjmedia_sdp_media_find_attr2(preanswer, "rtpmap",
					     &pt_answer[i]);
	    pj_assert(a);
	    pjmedia_sdp_attr_get_rtpmap(a, &r);

	    /* Only care for telephone-event format */
	    if (pj_stricmp2(&r.enc_name, "telephone-event")) {
		++i;
		continue;
	    }

	    if (first_televent_offer.slen == 0) {
		first_televent_offer = pt_offer[i];
		first_televent_answer = pt_answer[i];
	    }

	    for (j=0; j<nclockrate; ++j) {
		if (r.clock_rate==clockrate[j])
		    break;
	    }

	    /* This tel-event's clockrate is unwanted, remove the tel-event */
	    if (j==nclockrate) {
		pj_array_erase(pt_answer, sizeof(pt_answer[0]),
			       pt_answer_count, i);
		pj_array_erase(pt_offer, sizeof(pt_offer[0]),
			       pt_answer_count, i);
		pt_answer_count--;
	    } else {
		++matched_cnt;
		++i;
	    }
	}

	/* Tel-event is wanted, but no matched clock rate (to the selected
	 * audio codec), just put back any first matched tel-event formats.
	 */
	if (!matched_cnt) {
	    pt_offer[pt_answer_count] = first_televent_offer;
	    pt_answer[pt_answer_count++] = first_televent_answer;
	}
    }

    /* Build the answer by cloning from preanswer, and reorder the payload
     * to suit the offer.
     */
    answer = pjmedia_sdp_media_clone(pool, preanswer);
    for (i=0; i<pt_answer_count; ++i) {
	unsigned j;
	for (j=i; j<answer->desc.fmt_count; ++j) {
	    if (!pj_strcmp(&answer->desc.fmt[j], &pt_answer[i]))
		break;
	}
	pj_assert(j != answer->desc.fmt_count);
	str_swap(&answer->desc.fmt[i], &answer->desc.fmt[j]);
    }

    /* Remove unwanted local formats. */
    for (i=pt_answer_count; i<answer->desc.fmt_count; ++i) {
	pjmedia_sdp_attr *a;

	/* Remove rtpmap for this format */
	a = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
					 &answer->desc.fmt[i]);
	if (a) {
	    pjmedia_sdp_media_remove_attr(answer, a);
	}

	/* Remove fmtp for this format */
	a = pjmedia_sdp_media_find_attr2(answer, "fmtp",
					 &answer->desc.fmt[i]);
	if (a) {
	    pjmedia_sdp_media_remove_attr(answer, a);
	}
    }
    answer->desc.fmt_count = pt_answer_count;

#if PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT
    apply_answer_symmetric_pt(pool, answer, pt_answer_count,
			      pt_offer, pt_answer);
#endif

    /* Update media direction. */
    update_media_direction(pool, offer, answer);

    *p_answer = answer;
    return PJ_SUCCESS;
}


static pj_status_t process_answer(pj_pool_t *pool,
				  pjmedia_sdp_session *local_offer,
				  pjmedia_sdp_session *answer,
				  pj_bool_t allow_asym,
				  pjmedia_sdp_session **p_active)
{
    unsigned omi = 0; /* Offer media index */
    unsigned ami = 0; /* Answer media index */
    pj_bool_t has_active = PJ_FALSE;
    pjmedia_sdp_session *offer;
    pj_status_t status;

    /* Check arguments. */
    PJ_ASSERT_RETURN(pool && local_offer && answer && p_active, PJ_EINVAL);

    /* Duplicate local offer SDP. */
    offer = pjmedia_sdp_session_clone(pool, local_offer);

    /* Check that media count match between offer and answer */
    // Ticket #527, different media count is allowed for more interoperability,
    // however, the media order must be same between offer and answer.
    // if (offer->media_count != answer->media_count)
    //	   return PJMEDIA_SDPNEG_EMISMEDIA;

    /* Now update each media line in the offer with the answer. */
    for (; omi<offer->media_count; ++omi) {
	if (ami == answer->media_count) {
	    /* The answer has less media than the offer */
	    pjmedia_sdp_media *am;

	    /* Generate matching-but-disabled-media for the answer */
	    am = sdp_media_clone_deactivate(pool, offer->media[omi],
	                                    offer->media[omi], offer);
	    answer->media[answer->media_count++] = am;
	    ++ami;

	    /* Deactivate our media offer too */
	    pjmedia_sdp_media_deactivate(pool, offer->media[omi]);

	    /* No answer media to be negotiated */
	    continue;
	}

	status = process_m_answer(pool, offer->media[omi], answer->media[ami],
				  allow_asym);

	/* If media type is mismatched, just disable the media. */
	if (status == PJMEDIA_SDPNEG_EINVANSMEDIA) {
	    pjmedia_sdp_media_deactivate(pool, offer->media[omi]);
	    continue;
	}
	/* No common format in the answer media. */
	else if (status == PJMEDIA_SDPNEG_EANSNOMEDIA) {
	    pjmedia_sdp_media_deactivate(pool, offer->media[omi]);
	    pjmedia_sdp_media_deactivate(pool, answer->media[ami]);
	}
	/* Return the error code, for other errors. */
	else if (status != PJ_SUCCESS) {
	    return status;
	}

	if (offer->media[omi]->desc.port != 0)
	    has_active = PJ_TRUE;

	++ami;
    }

    *p_active = offer;

    return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA;
}


static pj_status_t custom_fmt_match(pj_pool_t *pool,
				    const pj_str_t *fmt_name,
				    pjmedia_sdp_media *offer,
				    unsigned o_fmt_idx,
				    pjmedia_sdp_media *answer,
				    unsigned a_fmt_idx,
				    unsigned option)
{
    unsigned i;

    for (i = 0; i < fmt_match_cb_cnt; ++i) {
	if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) {
	    pj_assert(fmt_match_cb[i].cb);
	    return (*fmt_match_cb[i].cb)(pool, offer, o_fmt_idx,
					 answer, a_fmt_idx,
					 option);
	}
    }

    /* Not customized format matching found, should be matched */
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_answer_multiple_codecs(
                        pjmedia_sdp_neg *neg,
                        pj_bool_t answer_multiple)
{
    PJ_ASSERT_RETURN(neg, PJ_EINVAL);
    neg->answer_with_multiple_codecs = answer_multiple;
    return PJ_SUCCESS;
}


PJ_DEF(pj_bool_t) pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg)
{
    PJ_ASSERT_RETURN(neg, PJ_FALSE);

    return neg->answer_was_remote;
}


PJ_DEF(const char*) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state)
{
    if ((int)state >=0 && state < (pjmedia_sdp_neg_state)PJ_ARRAY_SIZE(state_str))
	return state_str[state];

    return "<?UNKNOWN?>";
}


static void remove_all_media_directions(pjmedia_sdp_media *m)
{
    pjmedia_sdp_media_remove_all_attr(m, "inactive");
    pjmedia_sdp_media_remove_all_attr(m, "sendrecv");
    pjmedia_sdp_media_remove_all_attr(m, "sendonly");
    pjmedia_sdp_media_remove_all_attr(m, "recvonly");
}


PJ_DEF(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg)
{
    PJ_ASSERT_RETURN(neg, PJ_EINVAL);

    /* Must be in LOCAL_OFFER state. */
    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER ||
		     neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER,
		     PJMEDIA_SDPNEG_EINSTATE);

    if (neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER &&
	neg->active_local_sdp)
    {
	/* Increment next version number. This happens if for example
	 * the reinvite offer is rejected by 488. If we don't increment
	 * the version here, the next offer will have the same version.
	 */
	neg->active_local_sdp->origin.version++;
    }

    /* Revert back initial SDP */
    if (neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
	neg->initial_sdp = neg->initial_sdp_tmp;

    /* Clear temporary SDP */
    neg->initial_sdp_tmp = NULL;
    neg->neg_local_sdp = neg->neg_remote_sdp = NULL;
    neg->has_remote_answer = PJ_FALSE;

    /* Reset state to done */
    neg->state = PJMEDIA_SDP_NEG_STATE_DONE;

    return PJ_SUCCESS;
}


static pj_status_t create_answer( pj_pool_t *pool,
				  pj_bool_t prefer_remote_codec_order,
                                  pj_bool_t answer_with_multiple_codecs,
				  const pjmedia_sdp_session *initial,
				  const pjmedia_sdp_session *offer,
				  pjmedia_sdp_session **p_answer)
{
    pj_status_t status = PJMEDIA_SDPNEG_ENOMEDIA;
    pj_bool_t has_active = PJ_FALSE;
    pjmedia_sdp_session *answer;
    char media_used[PJMEDIA_MAX_SDP_MEDIA];
    unsigned i;

    /* Validate remote offer.
     * This should have been validated before.
     */
    PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(offer))==PJ_SUCCESS, status);

    /* Create initial answer by duplicating initial SDP,
     * but clear all media lines. The media lines will be filled up later.
     */
    answer = pjmedia_sdp_session_clone(pool, initial);
    PJ_ASSERT_RETURN(answer != NULL, PJ_ENOMEM);

    answer->media_count = 0;

    pj_bzero(media_used, sizeof(media_used));

    /* For each media line, create our answer based on our initial
     * capability.
     */
    for (i=0; i<offer->media_count; ++i) {
	const pjmedia_sdp_media *om;	/* offer */
	const pjmedia_sdp_media *im;	/* initial media */
	pjmedia_sdp_media *am = NULL;	/* answer/result */
	unsigned j;

	om = offer->media[i];

	/* Find media description in our initial capability that matches
	 * the media type and transport type of offer's media, has
	 * matching codec, and has not been used to answer other offer.
	 */
	for (im=NULL, j=0; j<initial->media_count; ++j) {
	    im = initial->media[j];
	    if (pj_strcmp(&om->desc.media, &im->desc.media)==0 &&
		pj_strcmp(&om->desc.transport, &im->desc.transport)==0 &&
		media_used[j] == 0)
	    {
                pj_status_t status2;

		/* See if it has matching codec. */
		status2 = match_offer(pool, prefer_remote_codec_order,
                                      answer_with_multiple_codecs,
				      om, im, initial, &am);
		if (status2 == PJ_SUCCESS) {
		    /* Mark media as used. */
		    media_used[j] = 1;
		    break;
                } else {
                    status = status2;
                }
	    }
	}

	if (j==initial->media_count) {
	    /* No matching media.
	     * Reject the offer by setting the port to zero in the answer.
	     */
	    /* For simplicity in the construction of the answer, we'll
	     * just clone the media from the offer. Anyway receiver will
	     * ignore anything in the media once it sees that the port
	     * number is zero.
	     */
	    am = sdp_media_clone_deactivate(pool, om, om, answer);
	} else {
	    /* The answer is in am */
	    pj_assert(am != NULL);
	}

	/* Add the media answer */
	answer->media[answer->media_count++] = am;

	/* Check if this media is active.*/
	if (am->desc.port != 0)
	    has_active = PJ_TRUE;
    }

    *p_answer = answer;

    return has_active ? PJ_SUCCESS : status;
}


char: lp: fix possible integer overflow in lp_setup()

The lp_setup() code doesn't apply any bounds checking when passing
"lp=none", and only in this case, resulting in an overflow of the
parport_nr[] array. All versions in Git history are affected.

Reported-By: Roee Hay <roee.hay@hcl.com>
Cc: Ben Hutchings <ben@decadent.org.uk>
Cc: stable@vger.kernel.org
Signed-off-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

static int __init lp_init_module (void)
{
	if (parport[0]) {
		/* The user gave some parameters.  Let's see what they were.  */
		if (!strncmp(parport[0], "auto", 4))
			parport_nr[0] = LP_PARPORT_AUTO;
		else {
			int n;
			for (n = 0; n < LP_NO && parport[n]; n++) {
				if (!strncmp(parport[n], "none", 4))
					parport_nr[n] = LP_PARPORT_NONE;
				else {
					char *ep;
					unsigned long r = simple_strtoul(parport[n], &ep, 0);
					if (ep != parport[n])
						parport_nr[n] = r;
					else {
						printk(KERN_ERR "lp: bad port specifier `%s'\n", parport[n]);
						return -ENODEV;
					}
				}
			}
		}
	}

	return lp_init();
}

Linux drivers/char/lp.c Out-of-Bounds Write. Due to a missing bounds check, and the fact that parport_ptr integer is static, a 'secure boot' kernel command line adversary (can happen due to bootloader vulns, e.g. Google Nexus 6's CVE-2016-10277, where due to a vulnerability the adversary has partial control over the command line) can overflow the parport_nr array in the following code, by appending many (>LP_NO) 'lp=none' arguments to the command line.


static void lp_release_parport(struct lp_struct *this_lp)
{
	if (test_and_clear_bit(LP_PARPORT_CLAIMED, &this_lp->bits)) {
		parport_release (this_lp->dev);
	}
}


static int lp_reset(int minor)
{
	int retval;
	lp_claim_parport_or_block (&lp_table[minor]);
	w_ctr(minor, LP_PSELECP);
	udelay (LP_DELAY);
	w_ctr(minor, LP_PSELECP | LP_PINITP);
	retval = r_str(minor);
	lp_release_parport (&lp_table[minor]);
	return retval;
}


static int lp_check_status(int minor)
{
	int error = 0;
	unsigned int last = lp_table[minor].last_error;
	unsigned char status = r_str(minor);
	if ((status & LP_PERRORP) && !(LP_F(minor) & LP_CAREFUL))
		/* No error. */
		last = 0;
	else if ((status & LP_POUTPA)) {
		if (last != LP_POUTPA) {
			last = LP_POUTPA;
			printk(KERN_INFO "lp%d out of paper\n", minor);
		}
		error = -ENOSPC;
	} else if (!(status & LP_PSELECD)) {
		if (last != LP_PSELECD) {
			last = LP_PSELECD;
			printk(KERN_INFO "lp%d off-line\n", minor);
		}
		error = -EIO;
	} else if (!(status & LP_PERRORP)) {
		if (last != LP_PERRORP) {
			last = LP_PERRORP;
			printk(KERN_INFO "lp%d on fire\n", minor);
		}
		error = -EIO;
	} else {
		last = 0; /* Come here if LP_CAREFUL is set and no
                             errors are reported. */
	}

	lp_table[minor].last_error = last;

	if (last != 0)
		lp_error(minor);

	return error;
}


static int lp_open(struct inode * inode, struct file * file)
{
	unsigned int minor = iminor(inode);
	int ret = 0;

	mutex_lock(&lp_mutex);
	if (minor >= LP_NO) {
		ret = -ENXIO;
		goto out;
	}
	if ((LP_F(minor) & LP_EXIST) == 0) {
		ret = -ENXIO;
		goto out;
	}
	if (test_and_set_bit(LP_BUSY_BIT_POS, &LP_F(minor))) {
		ret = -EBUSY;
		goto out;
	}
	/* If ABORTOPEN is set and the printer is offline or out of paper,
	   we may still want to open it to perform ioctl()s.  Therefore we
	   have commandeered O_NONBLOCK, even though it is being used in
	   a non-standard manner.  This is strictly a Linux hack, and
	   should most likely only ever be used by the tunelp application. */
	if ((LP_F(minor) & LP_ABORTOPEN) && !(file->f_flags & O_NONBLOCK)) {
		int status;
		lp_claim_parport_or_block (&lp_table[minor]);
		status = r_str(minor);
		lp_release_parport (&lp_table[minor]);
		if (status & LP_POUTPA) {
			printk(KERN_INFO "lp%d out of paper\n", minor);
			LP_F(minor) &= ~LP_BUSY;
			ret = -ENOSPC;
			goto out;
		} else if (!(status & LP_PSELECD)) {
			printk(KERN_INFO "lp%d off-line\n", minor);
			LP_F(minor) &= ~LP_BUSY;
			ret = -EIO;
			goto out;
		} else if (!(status & LP_PERRORP)) {
			printk(KERN_ERR "lp%d printer error\n", minor);
			LP_F(minor) &= ~LP_BUSY;
			ret = -EIO;
			goto out;
		}
	}
	lp_table[minor].lp_buffer = kmalloc(LP_BUFFER_SIZE, GFP_KERNEL);
	if (!lp_table[minor].lp_buffer) {
		LP_F(minor) &= ~LP_BUSY;
		ret = -ENOMEM;
		goto out;
	}
	/* Determine if the peripheral supports ECP mode */
	lp_claim_parport_or_block (&lp_table[minor]);
	if ( (lp_table[minor].dev->port->modes & PARPORT_MODE_ECP) &&
             !parport_negotiate (lp_table[minor].dev->port,
                                 IEEE1284_MODE_ECP)) {
		printk (KERN_INFO "lp%d: ECP mode\n", minor);
		lp_table[minor].best_mode = IEEE1284_MODE_ECP;
	} else {
		lp_table[minor].best_mode = IEEE1284_MODE_COMPAT;
	}
	/* Leave peripheral in compatibility mode */
	parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
	lp_release_parport (&lp_table[minor]);
	lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
out:
	mutex_unlock(&lp_mutex);
	return ret;
}


static long lp_ioctl(struct file *file, unsigned int cmd,
			unsigned long arg)
{
	unsigned int minor;
	struct timeval par_timeout;
	int ret;

	minor = iminor(file_inode(file));
	mutex_lock(&lp_mutex);
	switch (cmd) {
	case LPSETTIMEOUT:
		if (copy_from_user(&par_timeout, (void __user *)arg,
					sizeof (struct timeval))) {
			ret = -EFAULT;
			break;
		}
		ret = lp_set_timeout(minor, &par_timeout);
		break;
	default:
		ret = lp_do_ioctl(minor, cmd, arg, (void __user *)arg);
		break;
	}
	mutex_unlock(&lp_mutex);

	return ret;
}


static int lp_register(int nr, struct parport *port)
{
	lp_table[nr].dev = parport_register_device(port, "lp",
						   lp_preempt, NULL, NULL, 0,
						   (void *) &lp_table[nr]);
	if (lp_table[nr].dev == NULL)
		return 1;
	lp_table[nr].flags |= LP_EXIST;

	if (reset)
		lp_reset(nr);

	device_create(lp_class, port->dev, MKDEV(LP_MAJOR, nr), NULL,
		      "lp%d", nr);

	printk(KERN_INFO "lp%d: using %s (%s).\n", nr, port->name,
	       (port->irq == PARPORT_IRQ_NONE)?"polling":"interrupt-driven");

#ifdef CONFIG_LP_CONSOLE
	if (!nr) {
		if (port->modes & PARPORT_MODE_SAFEININT) {
			register_console(&lpcons);
			console_registered = port;
			printk (KERN_INFO "lp%d: console ready\n", CONSOLE_LP);
		} else
			printk (KERN_ERR "lp%d: cannot run console on %s\n",
				CONSOLE_LP, port->name);
	}
#endif

	return 0;
}


static int __init lp_setup (char *str)
{
	static int parport_ptr;
	int x;

	if (get_option(&str, &x)) {
		if (x == 0) {
			/* disable driver on "lp=" or "lp=0" */
			parport_nr[0] = LP_PARPORT_OFF;
		} else {
			printk(KERN_WARNING "warning: 'lp=0x%x' is deprecated, ignored\n", x);
			return 0;
		}
	} else if (!strncmp(str, "parport", 7)) {
		int n = simple_strtoul(str+7, NULL, 10);
		if (parport_ptr < LP_NO)
			parport_nr[parport_ptr++] = n;
		else
			printk(KERN_INFO "lp: too many ports, %s ignored.\n",
			       str);
	} else if (!strcmp(str, "auto")) {
		parport_nr[0] = LP_PARPORT_AUTO;
	} else if (!strcmp(str, "none")) {
		if (parport_ptr < LP_NO)
			parport_nr[parport_ptr++] = LP_PARPORT_NONE;
		else
			printk(KERN_INFO "lp: too many ports, %s ignored.\n",
			       str);
	} else if (!strcmp(str, "reset")) {
		reset = 1;
	}
	return 1;
}


static void lp_console_write (struct console *co, const char *s,
			      unsigned count)
{
	struct pardevice *dev = lp_table[CONSOLE_LP].dev;
	struct parport *port = dev->port;
	ssize_t written;

	if (parport_claim (dev))
		/* Nothing we can do. */
		return;

	parport_set_timeout (dev, 0);

	/* Go to compatibility mode. */
	parport_negotiate (port, IEEE1284_MODE_COMPAT);

	do {
		/* Write the data, converting LF->CRLF as we go. */
		ssize_t canwrite = count;
		char *lf = memchr (s, '\n', count);
		if (lf)
			canwrite = lf - s;

		if (canwrite > 0) {
			written = parport_write (port, s, canwrite);

			if (written <= 0)
				continue;

			s += written;
			count -= written;
			canwrite -= written;
		}

		if (lf && canwrite <= 0) {
			const char *crlf = "\r\n";
			int i = 2;

			/* Dodge the original '\n', and put '\r\n' instead. */
			s++;
			count--;
			do {
				written = parport_write (port, crlf, i);
				if (written > 0)
					i -= written, crlf += written;
			} while (i > 0 && (CONSOLE_LP_STRICT || written > 0));
		}
	} while (count > 0 && (CONSOLE_LP_STRICT || written > 0));

	parport_release (dev);
}


static int lp_preempt(void *handle)
{
	struct lp_struct *this_lp = (struct lp_struct *)handle;
	set_bit(LP_PREEMPT_REQUEST, &this_lp->bits);
	return (1);
}


static void lp_attach (struct parport *port)
{
	unsigned int i;

	switch (parport_nr[0]) {
	case LP_PARPORT_UNSPEC:
	case LP_PARPORT_AUTO:
		if (parport_nr[0] == LP_PARPORT_AUTO &&
		    port->probe_info[0].class != PARPORT_CLASS_PRINTER)
			return;
		if (lp_count == LP_NO) {
			printk(KERN_INFO "lp: ignoring parallel port (max. %d)\n",LP_NO);
			return;
		}
		if (!lp_register(lp_count, port))
			lp_count++;
		break;

	default:
		for (i = 0; i < LP_NO; i++) {
			if (port->number == parport_nr[i]) {
				if (!lp_register(i, port))
					lp_count++;
				break;
			}
		}
		break;
	}
}


static int lp_negotiate(struct parport * port, int mode)
{
	if (parport_negotiate (port, mode) != 0) {
		mode = IEEE1284_MODE_COMPAT;
		parport_negotiate (port, mode);
	}

	return (mode);
}


static void lp_claim_parport_or_block(struct lp_struct *this_lp)
{
	if (!test_and_set_bit(LP_PARPORT_CLAIMED, &this_lp->bits)) {
		parport_claim_or_block (this_lp->dev);
	}
}


static ssize_t lp_write(struct file * file, const char __user * buf,
		        size_t count, loff_t *ppos)
{
	unsigned int minor = iminor(file_inode(file));
	struct parport *port = lp_table[minor].dev->port;
	char *kbuf = lp_table[minor].lp_buffer;
	ssize_t retv = 0;
	ssize_t written;
	size_t copy_size = count;
	int nonblock = ((file->f_flags & O_NONBLOCK) ||
			(LP_F(minor) & LP_ABORT));

#ifdef LP_STATS
	if (time_after(jiffies, lp_table[minor].lastcall + LP_TIME(minor)))
		lp_table[minor].runchars = 0;

	lp_table[minor].lastcall = jiffies;
#endif

	/* Need to copy the data from user-space. */
	if (copy_size > LP_BUFFER_SIZE)
		copy_size = LP_BUFFER_SIZE;

	if (mutex_lock_interruptible(&lp_table[minor].port_mutex))
		return -EINTR;

	if (copy_from_user (kbuf, buf, copy_size)) {
		retv = -EFAULT;
		goto out_unlock;
	}

 	/* Claim Parport or sleep until it becomes available
 	 */
	lp_claim_parport_or_block (&lp_table[minor]);
	/* Go to the proper mode. */
	lp_table[minor].current_mode = lp_negotiate (port,
						     lp_table[minor].best_mode);

	parport_set_timeout (lp_table[minor].dev,
			     (nonblock ? PARPORT_INACTIVITY_O_NONBLOCK
			      : lp_table[minor].timeout));

	if ((retv = lp_wait_ready (minor, nonblock)) == 0)
	do {
		/* Write the data. */
		written = parport_write (port, kbuf, copy_size);
		if (written > 0) {
			copy_size -= written;
			count -= written;
			buf  += written;
			retv += written;
		}

		if (signal_pending (current)) {
			if (retv == 0)
				retv = -EINTR;

			break;
		}

		if (copy_size > 0) {
			/* incomplete write -> check error ! */
			int error;

			parport_negotiate (lp_table[minor].dev->port,
					   IEEE1284_MODE_COMPAT);
			lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;

			error = lp_wait_ready (minor, nonblock);

			if (error) {
				if (retv == 0)
					retv = error;
				break;
			} else if (nonblock) {
				if (retv == 0)
					retv = -EAGAIN;
				break;
			}

			parport_yield_blocking (lp_table[minor].dev);
			lp_table[minor].current_mode
			  = lp_negotiate (port,
					  lp_table[minor].best_mode);

		} else if (need_resched())
			schedule ();

		if (count) {
			copy_size = count;
			if (copy_size > LP_BUFFER_SIZE)
				copy_size = LP_BUFFER_SIZE;

			if (copy_from_user(kbuf, buf, copy_size)) {
				if (retv == 0)
					retv = -EFAULT;
				break;
			}
		}
	} while (count > 0);

	if (test_and_clear_bit(LP_PREEMPT_REQUEST,
			       &lp_table[minor].bits)) {
		printk(KERN_INFO "lp%d releasing parport\n", minor);
		parport_negotiate (lp_table[minor].dev->port,
				   IEEE1284_MODE_COMPAT);
		lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
		lp_release_parport (&lp_table[minor]);
	}
out_unlock:
	mutex_unlock(&lp_table[minor].port_mutex);

 	return retv;
}


static int lp_release(struct inode * inode, struct file * file)
{
	unsigned int minor = iminor(inode);

	lp_claim_parport_or_block (&lp_table[minor]);
	parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
	lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
	lp_release_parport (&lp_table[minor]);
	kfree(lp_table[minor].lp_buffer);
	lp_table[minor].lp_buffer = NULL;
	LP_F(minor) &= ~LP_BUSY;
	return 0;
}


static int lp_wait_ready(int minor, int nonblock)
{
	int error = 0;

	/* If we're not in compatibility mode, we're ready now! */
	if (lp_table[minor].current_mode != IEEE1284_MODE_COMPAT) {
	  return (0);
	}

	do {
		error = lp_check_status (minor);
		if (error && (nonblock || (LP_F(minor) & LP_ABORT)))
			break;
		if (signal_pending (current)) {
			error = -EINTR;
			break;
		}
	} while (error);
	return error;
}


static int __init lp_init (void)
{
	int i, err = 0;

	if (parport_nr[0] == LP_PARPORT_OFF)
		return 0;

	for (i = 0; i < LP_NO; i++) {
		lp_table[i].dev = NULL;
		lp_table[i].flags = 0;
		lp_table[i].chars = LP_INIT_CHAR;
		lp_table[i].time = LP_INIT_TIME;
		lp_table[i].wait = LP_INIT_WAIT;
		lp_table[i].lp_buffer = NULL;
#ifdef LP_STATS
		lp_table[i].lastcall = 0;
		lp_table[i].runchars = 0;
		memset (&lp_table[i].stats, 0, sizeof (struct lp_stats));
#endif
		lp_table[i].last_error = 0;
		init_waitqueue_head (&lp_table[i].waitq);
		init_waitqueue_head (&lp_table[i].dataq);
		mutex_init(&lp_table[i].port_mutex);
		lp_table[i].timeout = 10 * HZ;
	}

	if (register_chrdev (LP_MAJOR, "lp", &lp_fops)) {
		printk (KERN_ERR "lp: unable to get major %d\n", LP_MAJOR);
		return -EIO;
	}

	lp_class = class_create(THIS_MODULE, "printer");
	if (IS_ERR(lp_class)) {
		err = PTR_ERR(lp_class);
		goto out_reg;
	}

	if (parport_register_driver (&lp_driver)) {
		printk (KERN_ERR "lp: unable to register with parport\n");
		err = -EIO;
		goto out_class;
	}

	if (!lp_count) {
		printk (KERN_INFO "lp: driver loaded but no devices found\n");
#ifndef CONFIG_PARPORT_1284
		if (parport_nr[0] == LP_PARPORT_AUTO)
			printk (KERN_INFO "lp: (is IEEE 1284 support enabled?)\n");
#endif
	}

	return 0;

out_class:
	class_destroy(lp_class);
out_reg:
	unregister_chrdev(LP_MAJOR, "lp");
	return err;
}


static void lp_cleanup_module (void)
{
	unsigned int offset;

	parport_unregister_driver (&lp_driver);

#ifdef CONFIG_LP_CONSOLE
	unregister_console (&lpcons);
#endif

	unregister_chrdev(LP_MAJOR, "lp");
	for (offset = 0; offset < LP_NO; offset++) {
		if (lp_table[offset].dev == NULL)
			continue;
		parport_unregister_device(lp_table[offset].dev);
		device_destroy(lp_class, MKDEV(LP_MAJOR, offset));
	}
	class_destroy(lp_class);
}


static int lp_do_ioctl(unsigned int minor, unsigned int cmd,
	unsigned long arg, void __user *argp)
{
	int status;
	int retval = 0;

#ifdef LP_DEBUG
	printk(KERN_DEBUG "lp%d ioctl, cmd: 0x%x, arg: 0x%lx\n", minor, cmd, arg);
#endif
	if (minor >= LP_NO)
		return -ENODEV;
	if ((LP_F(minor) & LP_EXIST) == 0)
		return -ENODEV;
	switch ( cmd ) {
		case LPTIME:
			if (arg > UINT_MAX / HZ)
				return -EINVAL;
			LP_TIME(minor) = arg * HZ/100;
			break;
		case LPCHAR:
			LP_CHAR(minor) = arg;
			break;
		case LPABORT:
			if (arg)
				LP_F(minor) |= LP_ABORT;
			else
				LP_F(minor) &= ~LP_ABORT;
			break;
		case LPABORTOPEN:
			if (arg)
				LP_F(minor) |= LP_ABORTOPEN;
			else
				LP_F(minor) &= ~LP_ABORTOPEN;
			break;
		case LPCAREFUL:
			if (arg)
				LP_F(minor) |= LP_CAREFUL;
			else
				LP_F(minor) &= ~LP_CAREFUL;
			break;
		case LPWAIT:
			LP_WAIT(minor) = arg;
			break;
		case LPSETIRQ:
			return -EINVAL;
			break;
		case LPGETIRQ:
			if (copy_to_user(argp, &LP_IRQ(minor),
					sizeof(int)))
				return -EFAULT;
			break;
		case LPGETSTATUS:
			if (mutex_lock_interruptible(&lp_table[minor].port_mutex))
				return -EINTR;
			lp_claim_parport_or_block (&lp_table[minor]);
			status = r_str(minor);
			lp_release_parport (&lp_table[minor]);
			mutex_unlock(&lp_table[minor].port_mutex);

			if (copy_to_user(argp, &status, sizeof(int)))
				return -EFAULT;
			break;
		case LPRESET:
			lp_reset(minor);
			break;
#ifdef LP_STATS
		case LPGETSTATS:
			if (copy_to_user(argp, &LP_STAT(minor),
					sizeof(struct lp_stats)))
				return -EFAULT;
			if (capable(CAP_SYS_ADMIN))
				memset(&LP_STAT(minor), 0,
						sizeof(struct lp_stats));
			break;
#endif
 		case LPGETFLAGS:
 			status = LP_F(minor);
			if (copy_to_user(argp, &status, sizeof(int)))
				return -EFAULT;
			break;

		default:
			retval = -EINVAL;
	}
	return retval;
}


static int lp_set_timeout(unsigned int minor, struct timeval *par_timeout)
{
	long to_jiffies;

	/* Convert to jiffies, place in lp_table */
	if ((par_timeout->tv_sec < 0) ||
	    (par_timeout->tv_usec < 0)) {
		return -EINVAL;
	}
	to_jiffies = DIV_ROUND_UP(par_timeout->tv_usec, 1000000/HZ);
	to_jiffies += par_timeout->tv_sec * (long) HZ;
	if (to_jiffies <= 0) {
		return -EINVAL;
	}
	lp_table[minor].timeout = to_jiffies;
	return 0;
}


static long lp_compat_ioctl(struct file *file, unsigned int cmd,
			unsigned long arg)
{
	unsigned int minor;
	struct timeval par_timeout;
	int ret;

	minor = iminor(file_inode(file));
	mutex_lock(&lp_mutex);
	switch (cmd) {
	case LPSETTIMEOUT:
		if (compat_get_timeval(&par_timeout, compat_ptr(arg))) {
			ret = -EFAULT;
			break;
		}
		ret = lp_set_timeout(minor, &par_timeout);
		break;
#ifdef LP_STATS
	case LPGETSTATS:
		/* FIXME: add an implementation if you set LP_STATS */
		ret = -EINVAL;
		break;
#endif
	default:
		ret = lp_do_ioctl(minor, cmd, arg, compat_ptr(arg));
		break;
	}
	mutex_unlock(&lp_mutex);

	return ret;
}


static void lp_detach (struct parport *port)
{
	/* Write this some day. */
#ifdef CONFIG_LP_CONSOLE
	if (console_registered == port) {
		unregister_console(&lpcons);
		console_registered = NULL;
	}
#endif /* CONFIG_LP_CONSOLE */
}


static void lp_error (int minor)
{
	DEFINE_WAIT(wait);
	int polling;

	if (LP_F(minor) & LP_ABORT)
		return;

	polling = lp_table[minor].dev->port->irq == PARPORT_IRQ_NONE;
	if (polling) lp_release_parport (&lp_table[minor]);
	prepare_to_wait(&lp_table[minor].waitq, &wait, TASK_INTERRUPTIBLE);
	schedule_timeout(LP_TIMEOUT_POLLED);
	finish_wait(&lp_table[minor].waitq, &wait);
	if (polling) lp_claim_parport_or_block (&lp_table[minor]);
	else parport_yield_blocking (lp_table[minor].dev);
}


static ssize_t lp_read(struct file * file, char __user * buf,
		       size_t count, loff_t *ppos)
{
	DEFINE_WAIT(wait);
	unsigned int minor=iminor(file_inode(file));
	struct parport *port = lp_table[minor].dev->port;
	ssize_t retval = 0;
	char *kbuf = lp_table[minor].lp_buffer;
	int nonblock = ((file->f_flags & O_NONBLOCK) ||
			(LP_F(minor) & LP_ABORT));

	if (count > LP_BUFFER_SIZE)
		count = LP_BUFFER_SIZE;

	if (mutex_lock_interruptible(&lp_table[minor].port_mutex))
		return -EINTR;

	lp_claim_parport_or_block (&lp_table[minor]);

	parport_set_timeout (lp_table[minor].dev,
			     (nonblock ? PARPORT_INACTIVITY_O_NONBLOCK
			      : lp_table[minor].timeout));

	parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
	if (parport_negotiate (lp_table[minor].dev->port,
			       IEEE1284_MODE_NIBBLE)) {
		retval = -EIO;
		goto out;
	}

	while (retval == 0) {
		retval = parport_read (port, kbuf, count);

		if (retval > 0)
			break;

		if (nonblock) {
			retval = -EAGAIN;
			break;
		}

		/* Wait for data. */

		if (lp_table[minor].dev->port->irq == PARPORT_IRQ_NONE) {
			parport_negotiate (lp_table[minor].dev->port,
					   IEEE1284_MODE_COMPAT);
			lp_error (minor);
			if (parport_negotiate (lp_table[minor].dev->port,
					       IEEE1284_MODE_NIBBLE)) {
				retval = -EIO;
				goto out;
			}
		} else {
			prepare_to_wait(&lp_table[minor].waitq, &wait, TASK_INTERRUPTIBLE);
			schedule_timeout(LP_TIMEOUT_POLLED);
			finish_wait(&lp_table[minor].waitq, &wait);
		}

		if (signal_pending (current)) {
			retval = -ERESTARTSYS;
			break;
		}

		cond_resched ();
	}
	parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
 out:
	lp_release_parport (&lp_table[minor]);

	if (retval > 0 && copy_to_user (buf, kbuf, retval))
		retval = -EFAULT;

	mutex_unlock(&lp_table[minor].port_mutex);

	return retval;
}