5 * Does all this gloriously nifty connection handling stuff...
14 #include <sys/socket.h>
15 #include <netinet/in.h>
18 static void connkill_real(aim_session_t *sess, aim_conn_t **deadconn)
21 aim_rxqueue_cleanbyconn(sess, *deadconn);
22 aim_tx_cleanqueue(sess, *deadconn);
24 if ((*deadconn)->fd != -1)
25 aim_conn_close(*deadconn);
28 * XXX ->priv should never be touched by the library. I know
29 * it used to be, but I'm getting rid of all that. Use
32 if ((*deadconn)->priv)
33 free((*deadconn)->priv);
36 * This will free ->internal if it necessary...
38 if ((*deadconn)->type == AIM_CONN_TYPE_RENDEZVOUS)
39 aim_conn_kill_rend(sess, *deadconn);
48 * aim_connrst - Clears out connection list, killing remaining connections.
49 * @sess: Session to be cleared
51 * Clears out the connection list and kills any connections left.
54 static void aim_connrst(aim_session_t *sess)
57 faim_mutex_init(&sess->connlistlock);
60 aim_conn_t *cur = sess->connlist, *tmp;
65 connkill_real(sess, &cur);
70 sess->connlist = NULL;
76 * aim_conn_init - Reset a connection to default values.
77 * @deadconn: Connection to be reset
79 * Initializes and/or resets a connection structure.
82 static void aim_conn_init(aim_conn_t *deadconn)
89 deadconn->subtype = -1;
92 deadconn->lastactivity = 0;
93 deadconn->forcedlatency = 0;
94 deadconn->handlerlist = NULL;
95 deadconn->priv = NULL;
96 faim_mutex_init(&deadconn->active);
97 faim_mutex_init(&deadconn->seqnum_lock);
103 * aim_conn_getnext - Gets a new connection structure.
106 * Allocate a new empty connection structure.
109 static aim_conn_t *aim_conn_getnext(aim_session_t *sess)
113 if (!(newconn = malloc(sizeof(aim_conn_t))))
115 memset(newconn, 0, sizeof(aim_conn_t));
117 aim_conn_init(newconn);
119 newconn->next = sess->connlist;
120 sess->connlist = newconn;
126 * aim_conn_kill - Close and free a connection.
127 * @sess: Session for the connection
128 * @deadconn: Connection to be freed
130 * Close, clear, and free a connection structure. Should never be
131 * called from within libfaim.
134 faim_export void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn)
136 aim_conn_t *cur, **prev;
138 if (!deadconn || !*deadconn)
141 for (prev = &sess->connlist; (cur = *prev); ) {
142 if (cur == *deadconn) {
152 connkill_real(sess, &cur);
158 * aim_conn_close - Close a connection
159 * @deadconn: Connection to close
161 * Close (but not free) a connection.
163 * This leaves everything untouched except for clearing the
164 * handler list and setting the fd to -1 (used to recognize
165 * dead connections). It will also remove cookies if necessary.
168 faim_export void aim_conn_close(aim_conn_t *deadconn)
171 faim_mutex_destroy(&deadconn->active);
172 faim_mutex_destroy(&deadconn->seqnum_lock);
173 if (deadconn->fd >= 3)
176 if (deadconn->handlerlist)
177 aim_clearhandlers(deadconn);
178 if (deadconn->type == AIM_CONN_TYPE_RENDEZVOUS)
179 aim_conn_close_rend((aim_session_t *)deadconn->sessv, deadconn);
186 * aim_getconn_type - Find a connection of a specific type
187 * @sess: Session to search
188 * @type: Type of connection to look for
190 * Searches for a connection of the specified type in the
191 * specified session. Returns the first connection of that
195 faim_export aim_conn_t *aim_getconn_type(aim_session_t *sess, int type)
199 faim_mutex_lock(&sess->connlistlock);
200 for (cur = sess->connlist; cur; cur = cur->next) {
201 if ((cur->type == type) &&
202 !(cur->status & AIM_CONN_STATUS_INPROGRESS))
205 faim_mutex_unlock(&sess->connlistlock);
210 faim_export aim_conn_t *aim_getconn_type_all(aim_session_t *sess, int type)
214 faim_mutex_lock(&sess->connlistlock);
215 for (cur = sess->connlist; cur; cur = cur->next) {
216 if (cur->type == type)
219 faim_mutex_unlock(&sess->connlistlock);
224 /* If you pass -1 for the fd, you'll get what you ask for. Gibberish. */
225 faim_export aim_conn_t *aim_getconn_fd(aim_session_t *sess, int fd)
229 faim_mutex_lock(&sess->connlistlock);
230 for (cur = sess->connlist; cur; cur = cur->next) {
234 faim_mutex_unlock(&sess->connlistlock);
240 * aim_proxyconnect - An extrememly quick and dirty SOCKS5 interface.
241 * @sess: Session to connect
242 * @host: Host to connect to
243 * @port: Port to connect to
244 * @statusret: Return value of the connection
246 * Attempts to connect to the specified host via the configured
247 * proxy settings, if present. If no proxy is configured for
248 * this session, the connection is done directly.
250 * XXX this is really awful.
253 static int aim_proxyconnect(aim_session_t *sess, const char *host, fu16_t port, fu32_t *statusret)
257 if (strlen(sess->socksproxy.server)) { /* connecting via proxy */
259 unsigned char buf[512];
260 struct sockaddr_in sa;
263 unsigned short proxyport = 1080;
265 for(i=0;i<(int)strlen(sess->socksproxy.server);i++) {
266 if (sess->socksproxy.server[i] == ':') {
267 proxyport = atoi(&(sess->socksproxy.server[i+1]));
272 proxy = (char *)malloc(i+1);
273 strncpy(proxy, sess->socksproxy.server, i);
276 if (!(hp = gethostbyname(proxy))) {
277 faimdprintf(sess, 0, "proxyconnect: unable to resolve proxy name\n");
278 *statusret = (h_errno | AIM_CONN_STATUS_RESOLVERR);
283 memset(&sa.sin_zero, 0, 8);
284 sa.sin_port = htons(proxyport);
285 memcpy(&sa.sin_addr, hp->h_addr, hp->h_length);
286 sa.sin_family = hp->h_addrtype;
288 fd = socket(hp->h_addrtype, SOCK_STREAM, 0);
289 if (connect(fd, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) < 0) {
290 faimdprintf(sess, 0, "proxyconnect: unable to connect to proxy\n");
296 buf[0] = 0x05; /* SOCKS version 5 */
297 if (strlen(sess->socksproxy.username)) {
298 buf[1] = 0x02; /* two methods */
299 buf[2] = 0x00; /* no authentication */
300 buf[3] = 0x02; /* username/password authentication */
308 if (write(fd, buf, i) < i) {
314 if (read(fd, buf, 2) < 2) {
320 if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
326 /* check if we're doing username authentication */
327 if (buf[1] == 0x02) {
328 i = aimutil_put8(buf, 0x01); /* version 1 */
329 i += aimutil_put8(buf+i, strlen(sess->socksproxy.username));
330 i += aimutil_putstr(buf+i, sess->socksproxy.username, strlen(sess->socksproxy.username));
331 i += aimutil_put8(buf+i, strlen(sess->socksproxy.password));
332 i += aimutil_putstr(buf+i, sess->socksproxy.password, strlen(sess->socksproxy.password));
333 if (write(fd, buf, i) < i) {
338 if (read(fd, buf, 2) < 2) {
343 if ((buf[0] != 0x01) || (buf[1] != 0x00)) {
350 i = aimutil_put8(buf, 0x05);
351 i += aimutil_put8(buf+i, 0x01); /* CONNECT */
352 i += aimutil_put8(buf+i, 0x00); /* reserved */
353 i += aimutil_put8(buf+i, 0x03); /* address type: host name */
354 i += aimutil_put8(buf+i, strlen(host));
355 i += aimutil_putstr(buf+i, host, strlen(host));
356 i += aimutil_put16(buf+i, port);
358 if (write(fd, buf, i) < i) {
363 if (read(fd, buf, 10) < 10) {
368 if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
374 } else { /* connecting directly */
375 struct sockaddr_in sa;
378 if (!(hp = gethostbyname(host))) {
379 *statusret = (h_errno | AIM_CONN_STATUS_RESOLVERR);
383 memset(&sa, 0, sizeof(struct sockaddr_in));
384 sa.sin_port = htons(port);
385 memcpy(&sa.sin_addr, hp->h_addr, hp->h_length);
386 sa.sin_family = hp->h_addrtype;
388 fd = socket(hp->h_addrtype, SOCK_STREAM, 0);
390 if (sess->flags & AIM_SESS_FLAGS_NONBLOCKCONNECT)
391 fcntl(fd, F_SETFL, O_NONBLOCK); /* XXX save flags */
393 if (connect(fd, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) < 0) {
394 if (sess->flags & AIM_SESS_FLAGS_NONBLOCKCONNECT) {
395 if ((errno == EINPROGRESS) || (errno == EINTR)) {
397 *statusret |= AIM_CONN_STATUS_INPROGRESS;
409 * aim_cloneconn - clone an aim_conn_t
410 * @sess: session containing parent
411 * @src: connection to clone
413 * A new connection is allocated, and the values are filled in
414 * appropriately. Note that this function sets the new connnection's
415 * ->priv pointer to be equal to that of its parent: only the pointer
416 * is copied, not the data it points to.
418 * This function returns a pointer to the new aim_conn_t, or %NULL on
421 faim_internal aim_conn_t *aim_cloneconn(aim_session_t *sess, aim_conn_t *src)
425 if (!(conn = aim_conn_getnext(sess)))
428 faim_mutex_lock(&conn->active);
431 conn->type = src->type;
432 conn->subtype = src->subtype;
433 conn->seqnum = src->seqnum;
434 conn->priv = src->priv;
435 conn->internal = src->internal;
436 conn->lastactivity = src->lastactivity;
437 conn->forcedlatency = src->forcedlatency;
438 conn->sessv = src->sessv;
439 aim_clonehandlers(sess, conn, src);
441 faim_mutex_unlock(&conn->active);
447 * aim_newconn - Open a new connection
448 * @sess: Session to create connection in
449 * @type: Type of connection to create
450 * @dest: Host to connect to (in "host:port" syntax)
452 * Opens a new connection to the specified dest host of specified
453 * type, using the proxy settings if available. If @host is %NULL,
454 * the connection is allocated and returned, but no connection
457 * FIXME: Return errors in a more sane way.
460 faim_export aim_conn_t *aim_newconn(aim_session_t *sess, int type, const char *dest)
462 aim_conn_t *connstruct;
463 fu16_t port = FAIM_LOGIN_PORT;
467 if (!(connstruct = aim_conn_getnext(sess)))
470 faim_mutex_lock(&connstruct->active);
472 connstruct->sessv = (void *)sess;
473 connstruct->type = type;
475 if (!dest) { /* just allocate a struct */
477 connstruct->status = 0;
478 faim_mutex_unlock(&connstruct->active);
483 * As of 23 Jul 1999, AOL now sends the port number, preceded by a
484 * colon, in the BOS redirect. This fatally breaks all previous
485 * libfaims. Bad, bad AOL.
487 * We put this here to catch every case.
491 for(i = 0; i < (int)strlen(dest); i++) {
492 if (dest[i] == ':') {
493 port = atoi(&(dest[i+1]));
498 host = (char *)malloc(i+1);
499 strncpy(host, dest, i);
502 if ((ret = aim_proxyconnect(sess, host, port, &connstruct->status)) < 0) {
504 connstruct->status = (errno | AIM_CONN_STATUS_CONNERR);
506 faim_mutex_unlock(&connstruct->active);
509 connstruct->fd = ret;
511 faim_mutex_unlock(&connstruct->active);
519 * aim_conngetmaxfd - Return the highest valued file discriptor in session
520 * @sess: Session to search
522 * Returns the highest valued filed descriptor of all open
523 * connections in @sess.
526 faim_export int aim_conngetmaxfd(aim_session_t *sess)
531 faim_mutex_lock(&sess->connlistlock);
532 for (cur = sess->connlist, j = 0; cur; cur = cur->next) {
536 faim_mutex_unlock(&sess->connlistlock);
542 * aim_conn_in_sess - Predicate to test the precense of a connection in a sess
543 * @sess: Session to look in
544 * @conn: Connection to look for
546 * Searches @sess for the passed connection. Returns 1 if its present,
550 faim_export int aim_conn_in_sess(aim_session_t *sess, aim_conn_t *conn)
554 faim_mutex_lock(&sess->connlistlock);
555 for (cur = sess->connlist; cur; cur = cur->next) {
557 faim_mutex_unlock(&sess->connlistlock);
561 faim_mutex_unlock(&sess->connlistlock);
567 * aim_select - Wait for a socket with data or timeout
568 * @sess: Session to wait on
569 * @timeout: How long to wait
570 * @status: Return status
572 * Waits for a socket with data or for timeout, whichever comes first.
575 * Return codes in *status:
576 * -1 error in select() (%NULL returned)
577 * 0 no events pending (%NULL returned)
578 * 1 outgoing data pending (%NULL returned)
579 * 2 incoming data pending (connection with pending data returned)
581 * XXX: we could probably stand to do a little courser locking here.
584 faim_export aim_conn_t *aim_select(aim_session_t *sess, struct timeval *timeout, int *status)
588 int maxfd, i, haveconnecting = 0;
590 faim_mutex_lock(&sess->connlistlock);
591 if (!sess->connlist) {
592 faim_mutex_unlock(&sess->connlistlock);
596 faim_mutex_unlock(&sess->connlistlock);
601 faim_mutex_lock(&sess->connlistlock);
602 for (cur = sess->connlist, maxfd = 0; cur; cur = cur->next) {
604 /* don't let invalid/dead connections sit around */
606 faim_mutex_unlock(&sess->connlistlock);
608 } else if (cur->status & AIM_CONN_STATUS_INPROGRESS) {
609 FD_SET(cur->fd, &wfds);
613 FD_SET(cur->fd, &fds);
617 faim_mutex_unlock(&sess->connlistlock);
620 * If we have data waiting to be sent, return
622 * We have to not do this if theres at least one
623 * connection thats still connecting, since that connection
624 * may have queued data and this return would prevent
625 * the connection from ever completing! This is a major
626 * inadequacy of the libfaim way of doing things. It means
627 * that nothing can transmit as long as there's connecting
630 * But its still better than having blocking connects.
633 if (!haveconnecting && sess->queue_outgoing) {
638 if ((i = select(maxfd+1, &fds, &wfds, NULL, timeout))>=1) {
639 faim_mutex_lock(&sess->connlistlock);
640 for (cur = sess->connlist; cur; cur = cur->next) {
641 if ((FD_ISSET(cur->fd, &fds)) ||
642 ((cur->status & AIM_CONN_STATUS_INPROGRESS) &&
643 FD_ISSET(cur->fd, &wfds))) {
645 faim_mutex_unlock(&sess->connlistlock);
646 return cur; /* XXX race condition here -- shouldnt unlock connlist */
649 *status = 0; /* shouldn't happen */
650 } else if ((i == -1) && (errno == EINTR)) /* treat interrupts as a timeout */
653 *status = i; /* can be 0 or -1 */
655 faim_mutex_unlock(&sess->connlistlock);
657 return NULL; /* no waiting or error, return */
661 * aim_conn_setlatency - Set a forced latency value for connection
662 * @conn: Conn to set latency for
663 * @newval: Number of seconds to force between transmits
665 * Causes @newval seconds to be spent between transmits on a connection.
667 * This is my lame attempt at overcoming not understanding the rate
670 * XXX: This should really be replaced with something that scales and
671 * backs off like the real rate limiting does.
674 faim_export int aim_conn_setlatency(aim_conn_t *conn, int newval)
680 faim_mutex_lock(&conn->active);
681 conn->forcedlatency = newval;
682 conn->lastactivity = 0; /* reset this just to make sure */
683 faim_mutex_unlock(&conn->active);
689 * aim_setupproxy - Configure a proxy for this session
690 * @sess: Session to set proxy for
691 * @server: SOCKS server
692 * @username: SOCKS username
693 * @password: SOCKS password
695 * Call this with your SOCKS5 proxy server parameters before
696 * the first call to aim_newconn(). If called with all %NULL
697 * args, it will clear out a previously set proxy.
699 * Set username and password to %NULL if not applicable.
702 faim_export void aim_setupproxy(aim_session_t *sess, const char *server, const char *username, const char *password)
704 /* clear out the proxy info */
705 if (!server || !strlen(server)) {
706 memset(sess->socksproxy.server, 0, sizeof(sess->socksproxy.server));
707 memset(sess->socksproxy.username, 0, sizeof(sess->socksproxy.username));
708 memset(sess->socksproxy.password, 0, sizeof(sess->socksproxy.password));
712 strncpy(sess->socksproxy.server, server, sizeof(sess->socksproxy.server));
713 if (username && strlen(username))
714 strncpy(sess->socksproxy.username, username, sizeof(sess->socksproxy.username));
715 if (password && strlen(password))
716 strncpy(sess->socksproxy.password, password, sizeof(sess->socksproxy.password));
721 static void defaultdebugcb(aim_session_t *sess, int level, const char *format, va_list va)
724 vfprintf(stderr, format, va);
730 * aim_session_init - Initializes a session structure
731 * @sess: Session to initialize
732 * @flags: Flags to use. Any of %AIM_SESS_FLAGS %OR'd together.
733 * @debuglevel: Level of debugging output (zero is least)
735 * Sets up the initial values for a session.
738 faim_export void aim_session_init(aim_session_t *sess, fu32_t flags, int debuglevel)
744 memset(sess, 0, sizeof(aim_session_t));
746 sess->queue_outgoing = NULL;
747 sess->queue_incoming = NULL;
748 sess->pendingjoin = NULL;
749 sess->pendingjoinexchange = 0;
750 aim_initsnachash(sess);
751 sess->msgcookies = NULL;
752 sess->snacid_next = 0x00000001;
755 sess->debug = debuglevel;
756 sess->debugcb = defaultdebugcb;
758 sess->modlistv = NULL;
761 * Default to SNAC login unless XORLOGIN is explicitly set.
763 if (!(flags & AIM_SESS_FLAGS_XORLOGIN))
764 sess->flags |= AIM_SESS_FLAGS_SNACLOGIN;
765 sess->flags |= flags;
768 * This must always be set. Default to the queue-based
769 * version for back-compatibility.
771 aim_tx_setenqueue(sess, AIM_TX_QUEUED, NULL);
775 * Register all the modules for this session...
777 aim__registermodule(sess, misc_modfirst); /* load the catch-all first */
778 aim__registermodule(sess, buddylist_modfirst);
779 aim__registermodule(sess, admin_modfirst);
780 aim__registermodule(sess, bos_modfirst);
781 aim__registermodule(sess, search_modfirst);
782 aim__registermodule(sess, stats_modfirst);
783 aim__registermodule(sess, auth_modfirst);
784 aim__registermodule(sess, msg_modfirst);
785 aim__registermodule(sess, chatnav_modfirst);
786 aim__registermodule(sess, chat_modfirst);
787 aim__registermodule(sess, locate_modfirst);
788 aim__registermodule(sess, general_modfirst);
794 * aim_session_kill - Deallocate a session
795 * @sess: Session to kill
798 faim_export void aim_session_kill(aim_session_t *sess)
803 aim__shutdownmodules(sess);
809 * aim_setdebuggingcb - Set the function to call when outputting debugging info
810 * @sess: Session to change
811 * @cb: Function to call
813 * The function specified is called whenever faimdprintf() is used within
814 * libfaim, and the session's debugging level is greater tha nor equal to
815 * the value faimdprintf was called with.
818 faim_export int aim_setdebuggingcb(aim_session_t *sess, faim_debugging_callback_t cb)
830 * aim_conn_isconnecting - Determine if a connection is connecting
831 * @conn: Connection to examine
833 * Returns nonzero if the connection is in the process of
834 * connecting (or if it just completed and aim_conn_completeconnect()
835 * has yet to be called on it).
838 faim_export int aim_conn_isconnecting(aim_conn_t *conn)
844 return !!(conn->status & AIM_CONN_STATUS_INPROGRESS);
848 * XXX this is nearly as ugly as proxyconnect().
850 faim_export int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn)
854 int res, error = ETIMEDOUT;
855 aim_rxcallback_t userfunc;
857 if (!conn || (conn->fd == -1))
860 if (!(conn->status & AIM_CONN_STATUS_INPROGRESS))
864 FD_SET(conn->fd, &fds);
866 FD_SET(conn->fd, &wfds);
870 if ((res = select(conn->fd+1, &fds, &wfds, NULL, &tv)) == -1) {
872 aim_conn_close(conn);
875 } else if (res == 0) {
876 faimdprintf(sess, 0, "aim_conn_completeconnect: false alarm on %d\n", conn->fd);
877 return 0; /* hasn't really completed yet... */
880 if (FD_ISSET(conn->fd, &fds) || FD_ISSET(conn->fd, &wfds)) {
881 int len = sizeof(error);
883 if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
888 aim_conn_close(conn);
893 fcntl(conn->fd, F_SETFL, 0); /* XXX should restore original flags */
895 conn->status &= ~AIM_CONN_STATUS_INPROGRESS;
897 if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE)))
898 userfunc(sess, NULL, conn);
900 /* Flush out the queues if there was something waiting for this conn */
901 aim_tx_flushqueue(sess);
906 faim_export aim_session_t *aim_conn_getsess(aim_conn_t *conn)
912 return (aim_session_t *)conn->sessv;
918 * Closes -ALL- open connections.
921 faim_export int aim_logoff(aim_session_t *sess)
924 aim_connrst(sess); /* in case we want to connect again */