/* -*-mode: C; compile-command: "make -j MEMDEBUG=TRUE";-*- */ /* * Copyright 2006-2009 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef XWFEATURE_SMS #include #include #include #include #include #include #include "linuxsms.h" #include "strutils.h" #define SMS_DIR "/tmp/xw_sms" #define LOCK_FILE ".lock" /* The idea here is to mimic an SMS-based transport using files. We'll use a * directory in /tmp to hold "messages", in files with names based on phone * number and port. Servers and clients will listen for changes in that * directory and "receive" messages when a change is noted to a file with * their phone number and on the port they listen to. * * Server's phone number is known to both server and client and passed on the * commandline. Client's "phone number" is the PID of the client process. * When the client sends to the server the phone number is passed in and its * return (own) number is included in the "message". * * If I'm the server, I create an empty file for each port I listen on. (Only * one now....). Clients will append to that. Likewise, a client creates a * file that it will listen on. * * Data is encoded using the same mechanism so size constraints can be * checked. */ struct LinSMSData { XP_UCHAR myPhone[MAX_PHONE_LEN+1]; XP_UCHAR myQueue[256]; FILE* lock; int wd, fd; /* for inotify */ void* storage; XP_U16 count; XP_U16 port; XP_Bool amServer; }; static void makeQueuePath( const XP_UCHAR* phone, XP_U16 port, XP_UCHAR* path, XP_U16 pathlen ) { snprintf( path, pathlen, "%s/%s_%d", SMS_DIR, phone, port ); } static void lock_queue( LinSMSData* data ) { char lock[256]; snprintf( lock, sizeof(lock), "%s/%s", data->myQueue, LOCK_FILE ); FILE* fp = fopen( lock, "w" ); XP_ASSERT( NULL == data->lock ); data->lock = fp; } static void unlock_queue( LinSMSData* data ) { XP_ASSERT( NULL != data->lock ); fclose( data->lock ); data->lock = NULL; } void linux_sms_init( CommonGlobals* globals, const CommsAddrRec* addr ) { LinSMSData* data = globals->smsData; if ( !data ) { data = XP_MALLOC( globals->params->util->mpool, sizeof(*data) ); XP_ASSERT( !!data ); XP_MEMSET( data, 0, sizeof(*data) ); globals->smsData = data; data->amServer = comms_getIsServer( globals->game.comms ); if ( data->amServer ) { XP_STRNCPY( data->myPhone, addr->u.sms.phone, sizeof(data->myPhone) ); } else { snprintf( data->myPhone, sizeof(data->myPhone), "%.6d", getpid() ); } makeQueuePath( data->myPhone, addr->u.sms.port, data->myQueue, sizeof(data->myQueue) ); data->port = addr->u.sms.port; (void)g_mkdir_with_parents( data->myQueue, 0777 ); int fd = inotify_init(); data->fd = fd; data->wd = inotify_add_watch( fd, data->myQueue, IN_MODIFY ); (*globals->socketChanged)( globals, -1, fd, &data->storage ); } } /* linux_sms_init */ void linux_sms_close( CommonGlobals* globals ) { LinSMSData* data = globals->smsData; if ( !!data ) { XP_FREE( globals->params->util->mpool, data ); globals->smsData = NULL; } } /* linux_sms_close */ XP_S16 linux_sms_send( CommonGlobals* globals, const XP_U8* buf, XP_U16 buflen, const XP_UCHAR* phone, XP_U16 port ) { XP_S16 nSent = -1; LinSMSData* data = globals->smsData; XP_ASSERT( !!data ); char path[256]; lock_queue( data ); #ifdef DEBUG gchar* str64 = g_base64_encode( buf, buflen ); #endif makeQueuePath( phone, port, path, sizeof(path) ); g_mkdir_with_parents( path, 0777 ); /* just in case */ int len = strlen( path ); snprintf( &path[len], sizeof(path)-len, "/%d", ++data->count ); XP_UCHAR sms[buflen*2]; /* more like (buflen*4/3) */ XP_U16 smslen = sizeof(sms); binToSms( sms, &smslen, buf, buflen ); XP_ASSERT( smslen == strlen(sms) ); #ifdef DEBUG XP_ASSERT( !strcmp( str64, sms ) ); g_free( str64 ); XP_U8 testout[buflen]; XP_U16 lenout = sizeof( testout ); XP_ASSERT( smsToBin( testout, &lenout, sms, smslen ) ); XP_ASSERT( lenout == buflen ); XP_ASSERT( XP_MEMCMP( testout, buf, smslen ) ); #endif FILE* fp = fopen( path, "w" ); XP_ASSERT( !!fp ); (void)fprintf( fp, "from: %s\n", data->myPhone ); (void)fprintf( fp, "%s\n", sms ); fclose( fp ); sync(); unlock_queue( data ); nSent = buflen; return nSent; } /* linux_sms_send */ static XP_S16 decodeAndDelete( LinSMSData* data, const gchar* name, XP_U8* buf, XP_U16 buflen, CommsAddrRec* addr ) { XP_S16 nRead = -1; char path[256]; snprintf( path, sizeof(path), "%s/%s", data->myQueue, name ); gchar* contents; gsize length; #ifdef DEBUG gboolean success = #endif g_file_get_contents( path, &contents, &length, NULL ); XP_ASSERT( success ); unlink( path ); if ( 0 == strncmp( "from: ", contents, 6 ) ) { gchar* eol = strstr( contents, "\n" ); *eol = '\0'; XP_STRNCPY( addr->u.sms.phone, &contents[6], sizeof(addr->u.sms.phone) ); XP_ASSERT( !*eol ); ++eol; /* skip NULL */ *strstr(eol, "\n" ) = '\0'; XP_U16 inlen = strlen(eol); /* skip \n */ XP_U8 out[inlen]; XP_U16 outlen = sizeof(out); XP_Bool valid = smsToBin( out, &outlen, eol, inlen ); if ( valid && outlen <= buflen ) { XP_MEMCPY( buf, out, outlen ); nRead = outlen; addr->conType = COMMS_CONN_SMS; addr->u.sms.port = data->port; } } g_free( contents ); return nRead; } /* decodeAndDelete */ XP_S16 linux_sms_receive( CommonGlobals* globals, int sock, XP_U8* buf, XP_U16 buflen, CommsAddrRec* addr ) { XP_S16 nRead = -1; LinSMSData* data = globals->smsData; XP_ASSERT( sock == data->fd ); lock_queue( data ); /* read required or we'll just get the event again. But we don't care about the result or the buffer contents. */ XP_U8 buffer[sizeof(struct inotify_event) + 16]; if ( 0 > read( sock, buffer, sizeof(buffer) ) ) { } char shortest[256] = { '\0' }; GDir* dir = g_dir_open( data->myQueue, 0, NULL ); for ( ; ; ) { const gchar* name = g_dir_read_name( dir ); if ( NULL == name ) { break; } else if ( 0 == strcmp( name, LOCK_FILE ) ) { continue; } if ( !shortest[0] || 0 < strcmp( shortest, name ) ) { snprintf( shortest, sizeof(shortest), "%s", name ); } } g_dir_close( dir ); if ( !!shortest[0] ) { nRead = decodeAndDelete( data, shortest, buf, buflen, addr ); } unlock_queue( data ); return nRead; } /* linux_sms_receive */ #endif /* XWFEATURE_SMS */