diff options
author | James Maki <jmaki@multitech.com> | 2010-04-23 11:58:20 -0500 |
---|---|---|
committer | James Maki <jmaki@multitech.com> | 2010-04-23 11:58:20 -0500 |
commit | 14fb44b17123b27e562379f51b75ee889982688d (patch) | |
tree | 3c2344f5c42396ab839638ee12f7c2f66a2656cb /src | |
download | sms-utils-14fb44b17123b27e562379f51b75ee889982688d.tar.gz sms-utils-14fb44b17123b27e562379f51b75ee889982688d.tar.bz2 sms-utils-14fb44b17123b27e562379f51b75ee889982688d.zip |
initial commit
Diffstat (limited to 'src')
36 files changed, 6939 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..74dbeb3 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,52 @@ +AUTOMAKE_OPTIONS = gnu +AM_CFLAGS = -Wall -Wno-format +#AM_CFLAGS = -Wall +bin_PROGRAMS = sms pdu-decoder pdu-encoder +sms_SOURCES = sms_main.c \ + sms_utils.c \ + sms_send.c \ + sms_send_email.c \ + sms_list.c \ + sms_config.c \ + phonebook.c \ + sms_delete.c \ + xprintf.c \ + utils.c \ + atcmd.c \ + pdu.c \ + pdu_decode.c \ + pdu_encode.c +pdu_decoder_SOURCES = pdu_decoder.c \ + pdu_decode.c \ + pdu_encode.c \ + utils.c \ + atcmd.c \ + pdu.c \ + xprintf.c \ + sms_utils.c +pdu_encoder_SOURCES = pdu_encoder.c \ + pdu_decode.c \ + pdu_encode.c \ + utils.c \ + atcmd.c \ + pdu.c \ + xprintf.c \ + sms_utils.c +noinst_HEADERS = sms_utils.h \ + utils.h \ + log.h list.h \ + cmd_options.h \ + sms_send.h \ + sms_send_email.h \ + sms_list.h \ + sms_config.h \ + phonebook.h \ + sms_delete.h \ + atcmd.h \ + pdu.h \ + pdu_decode.h \ + pdu_encode.h \ + xprintf.h + +EXTRA_DIST = + diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..34ea00c --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,496 @@ +# Makefile.in generated by automake 1.10.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +bin_PROGRAMS = sms$(EXEEXT) pdu-decoder$(EXEEXT) pdu-encoder$(EXEEXT) +subdir = src +DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) +PROGRAMS = $(bin_PROGRAMS) +am_pdu_decoder_OBJECTS = pdu_decoder.$(OBJEXT) pdu_decode.$(OBJEXT) \ + pdu_encode.$(OBJEXT) utils.$(OBJEXT) atcmd.$(OBJEXT) \ + pdu.$(OBJEXT) xprintf.$(OBJEXT) sms_utils.$(OBJEXT) +pdu_decoder_OBJECTS = $(am_pdu_decoder_OBJECTS) +pdu_decoder_LDADD = $(LDADD) +am_pdu_encoder_OBJECTS = pdu_encoder.$(OBJEXT) pdu_decode.$(OBJEXT) \ + pdu_encode.$(OBJEXT) utils.$(OBJEXT) atcmd.$(OBJEXT) \ + pdu.$(OBJEXT) xprintf.$(OBJEXT) sms_utils.$(OBJEXT) +pdu_encoder_OBJECTS = $(am_pdu_encoder_OBJECTS) +pdu_encoder_LDADD = $(LDADD) +am_sms_OBJECTS = sms_main.$(OBJEXT) sms_utils.$(OBJEXT) \ + sms_send.$(OBJEXT) sms_send_email.$(OBJEXT) sms_list.$(OBJEXT) \ + sms_config.$(OBJEXT) phonebook.$(OBJEXT) sms_delete.$(OBJEXT) \ + xprintf.$(OBJEXT) utils.$(OBJEXT) atcmd.$(OBJEXT) \ + pdu.$(OBJEXT) pdu_decode.$(OBJEXT) pdu_encode.$(OBJEXT) +sms_OBJECTS = $(am_sms_OBJECTS) +sms_LDADD = $(LDADD) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(pdu_decoder_SOURCES) $(pdu_encoder_SOURCES) $(sms_SOURCES) +DIST_SOURCES = $(pdu_decoder_SOURCES) $(pdu_encoder_SOURCES) \ + $(sms_SOURCES) +HEADERS = $(noinst_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = gnu +AM_CFLAGS = -Wall -Wno-format +sms_SOURCES = sms_main.c \ + sms_utils.c \ + sms_send.c \ + sms_send_email.c \ + sms_list.c \ + sms_config.c \ + phonebook.c \ + sms_delete.c \ + xprintf.c \ + utils.c \ + atcmd.c \ + pdu.c \ + pdu_decode.c \ + pdu_encode.c + +pdu_decoder_SOURCES = pdu_decoder.c \ + pdu_decode.c \ + pdu_encode.c \ + utils.c \ + atcmd.c \ + pdu.c \ + xprintf.c \ + sms_utils.c + +pdu_encoder_SOURCES = pdu_encoder.c \ + pdu_decode.c \ + pdu_encode.c \ + utils.c \ + atcmd.c \ + pdu.c \ + xprintf.c \ + sms_utils.c + +noinst_HEADERS = sms_utils.h \ + utils.h \ + log.h list.h \ + cmd_options.h \ + sms_send.h \ + sms_send_email.h \ + sms_list.h \ + sms_config.h \ + phonebook.h \ + sms_delete.h \ + atcmd.h \ + pdu.h \ + pdu_decode.h \ + pdu_encode.h \ + xprintf.h + +EXTRA_DIST = +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \ + $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \ + else :; fi; \ + done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \ + rm -f "$(DESTDIR)$(bindir)/$$f"; \ + done + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +pdu-decoder$(EXEEXT): $(pdu_decoder_OBJECTS) $(pdu_decoder_DEPENDENCIES) + @rm -f pdu-decoder$(EXEEXT) + $(LINK) $(pdu_decoder_OBJECTS) $(pdu_decoder_LDADD) $(LIBS) +pdu-encoder$(EXEEXT): $(pdu_encoder_OBJECTS) $(pdu_encoder_DEPENDENCIES) + @rm -f pdu-encoder$(EXEEXT) + $(LINK) $(pdu_encoder_OBJECTS) $(pdu_encoder_LDADD) $(LIBS) +sms$(EXEEXT): $(sms_OBJECTS) $(sms_DEPENDENCIES) + @rm -f sms$(EXEEXT) + $(LINK) $(sms_OBJECTS) $(sms_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atcmd.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pdu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pdu_decode.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pdu_decoder.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pdu_encode.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pdu_encoder.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/phonebook.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sms_config.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sms_delete.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sms_list.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sms_main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sms_send.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sms_send_email.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sms_utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xprintf.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-info: install-info-am + +install-man: + +install-pdf: install-pdf-am + +install-ps: install-ps-am + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/atcmd.c b/src/atcmd.c new file mode 100644 index 0000000..10470c1 --- /dev/null +++ b/src/atcmd.c @@ -0,0 +1,1087 @@ +/* + * AT Command Utilities (and terminal stuff for now) + * + * Copyright (C) 2010 by Multi-Tech Systems + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#define _GNU_SOURCE + +#define __ATCMD_C 1 + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <string.h> + +#include "global.h" +#include "log.h" +#include "utils.h" +#include "atcmd.h" +#include "sms_utils.h" + +static const struct baud_map __baud_map[] = { + {B300, 300}, + {B600, 600}, + {B1200, 1200}, + {B1800, 1800}, + {B2400, 2400}, + {B4800, 4800}, + {B9600, 9600}, + {B19200, 19200}, + {B38400, 38400}, + {B57600, 57600}, + {B115200, 115200}, + {B230400, 230400}, + {B460800, 460800}, + {B921600, 921600}, +}; + +speed_t value_to_baud(speed_t value) +{ + int n = ARRAY_SIZE(__baud_map); + int i; + + for (i = 0; i < n; ++i) { + if (__baud_map[i].value == value) { + return __baud_map[i].baud; + } + } + + log_warning("baud rate not valid: %lu", (unsigned long) value); + + return (speed_t) -1; +} + +int rts_get(int fd) +{ + int err; + int status; + + err = ioctl(fd, TIOCMGET, &status); + if (err < 0) { + return -1; + } + return status & TIOCM_RTS ? 1 : 0; +} + +int dtr_get(int fd) +{ + int err; + int status; + + err = ioctl(fd, TIOCMGET, &status); + if (err < 0) { + return -1; + } + return status & TIOCM_DTR ? 1 : 0; +} + +int cts_get(int fd) +{ + int err; + int status; + + err = ioctl(fd, TIOCMGET, &status); + if (err < 0) { + return -1; + } + return status & TIOCM_CTS ? 1 : 0; +} + +int dsr_get(int fd) +{ + int err; + int status; + + err = ioctl(fd, TIOCMGET, &status); + if (err < 0) { + return -1; + } + return status & TIOCM_DSR ? 1 : 0; +} + +int cd_get(int fd) +{ + int err; + int status; + + err = ioctl(fd, TIOCMGET, &status); + if (err < 0) { + return -1; + } + return status & TIOCM_CD ? 1 : 0; +} + +int ri_get(int fd) +{ + int err; + int status; + + err = ioctl(fd, TIOCMGET, &status); + if (err < 0) { + return -1; + } + return status & TIOCM_RI ? 1 : 0; +} + +int line_signal_set(int fd, int signal, int value) +{ + int err; + int status; + + err = ioctl(fd, TIOCMGET, &status); + if (err < 0) { + return -1; + } + + if (value) { + status &= ~signal; + } else { + status |= signal; + } + + err = ioctl(fd, TIOCMSET, &status); + if (err < 0) { + return -1; + } + + return status; +} + +int rts_set(int fd, int value) +{ + return line_signal_set(fd, TIOCM_RTS, value); +} + +int dtr_set(int fd, int value) +{ + return line_signal_set(fd, TIOCM_DTR, value); +} + +int tty_configure(int fd, speed_t baud_rate) +{ + int err; + struct termios params; + + err = tcgetattr(fd, ¶ms); + if (err < 0) { + log_error("tcgetattr failed: %m"); + return -1; + } + + cfmakeraw(¶ms); + cfsetspeed(¶ms, baud_rate); + + err = tcsetattr(fd, TCSANOW, ¶ms); + if (err < 0) { + log_error("tcsetattr failed: %m"); + return -1; + } + + return 0; +} + +int tty_open(const char *dev, speed_t baud_rate) +{ + int fd; + + fd = open(dev, O_RDWR | O_NOCTTY); + if (fd < 0) { + log_error("failed to open %s: %m", dev); + return fd; + } + + if (!isatty(fd)) { + log_error("%s is not a tty device", dev); + close(fd); + return -1; + } + + tty_configure(fd, baud_rate); + tcflush(fd, TCIOFLUSH); + + log_debug("rts: %d", rts_get(fd)); + log_debug("dtr: %d", dtr_get(fd)); + log_debug("cts: %d", cts_get(fd)); + log_debug("dsr: %d", dsr_get(fd)); + log_debug("cd: %d", cd_get(fd)); + log_debug("ri: %d", ri_get(fd)); + + return fd; +} + +int tty_set_read_timeout(int fd, int timeout) +{ + int err; + struct termios params; + + err = tcgetattr(fd, ¶ms); + if (err < 0) { + log_error("tcgetattr failed: %m"); + return -1; + } + + if (timeout < 0) { + params.c_cc[VTIME] = 0; + params.c_cc[VMIN] = 0; + } else if (timeout == 0) { + params.c_cc[VTIME] = 0; + params.c_cc[VMIN] = 1; + } else { + params.c_cc[VTIME] = (timeout + 50) / 100; + params.c_cc[VMIN] = 0; + } + + err = tcsetattr(fd, TCSANOW, ¶ms); + if (err < 0) { + log_error("tcsetattr failed: %m"); + return -1; + } + + return 0; +} + +int tty_close(int fd) +{ + tcflush(fd, TCIOFLUSH); + return close(fd); +} + +int atcmd_read(int fd, char *buf, size_t len) +{ + int err; + + err = read(fd, buf, len); + if (!err) { + log_notice("read timeout"); + } else if (err < 0) { + log_error("read failed: %m"); + } + + return err; +} + +int atcmd_read_until(int fd, char *buf, size_t len, const char *stop) +{ + int err; + char c; + ssize_t total = 0; + size_t stop_len = strlen(stop); + + if (!stop_len) { + BUG("specify a stop string"); + } + + while (1) { + err = atcmd_read(fd, &c, 1); + if (err <= 0) { + buf[total] = '\0'; + return err; + } + + if (total < len - 1) { + buf[total++] = c; + } + + if (stop_len <= total && c == stop[stop_len - 1] && + !memcmp(buf + (total - stop_len), stop, stop_len)) { + buf[total] = '\0'; + break; + } + + if (total >= len - 1) { + buf[total] = '\0'; + log_notice("buffer exceeded before finding stop sequence"); + log_notice("buffer so far: %s", buf); + return -1; + } + } + + debug_buffer("read:", buf, strlen(buf)); + + return total; +} + +int atcmd_readline(int fd, char *buf, size_t len) +{ + int tmp; + + tmp = atcmd_read_until(fd, buf, len, ATCMD_RESPONSE_EOL); + if (tmp <= 0) { + log_debug("atcmd_read_until failed with %d", tmp); + return tmp; + } + + buf = strpbrk(buf, ATCMD_RESPONSE_EOL); + if (buf) { + *buf = '\0'; + } + + return tmp; +} + +int atcmd_expect_line(int fd, char *buf, size_t len, const char *expect) +{ + int tmp; + + while (1) { + tmp = atcmd_readline(fd, buf, len); + if (tmp <= 0) { + *buf = '\0'; + log_debug("atcmd_readline failed"); + return tmp; + } + + if (strstr(buf, expect)) { + return tmp; + } else if (!strcmp(buf, "OK")) { + log_error("expected %s but got %s", expect, buf); + return -1; + } else if (!strcmp(buf, "ERROR")) { + log_error("expected %s but got %s", expect, buf); + return -1; + } else if (!strncmp(buf, "+CMS ERROR: ", sizeof("+CMS ERROR: ") - 1)) { + log_error("expected %s but got %s", expect, buf); + return -1; + } + } + + return -1; +} + +int atcmd_write(int fd, const char *buf, size_t len) +{ + int err; + + debug_buffer("writing:", buf, len); + + err = full_write(fd, buf, len); + if (err < 0) { + log_error("full_write failed: %m"); + } + return err; +} + +int atcmd_write_str(int fd, const char *buf) +{ + return atcmd_write(fd, buf, strlen(buf)); +} + +int atcmd_vprintf(int fd, char *fmt, va_list ap) +{ + char *buf; + int err; + + err = vasprintf(&buf, fmt, ap); + if (err < 0) { + log_error("out of memory"); + return err; + } + + err = atcmd_write_str(fd, buf); + if (err < 0) { + log_debug("atcmd_write failed"); + } + free(buf); + + return err; +} + +int atcmd_printf(int fd, char *fmt, ...) +{ + va_list ap; + int err; + + va_start(ap, fmt); + err = atcmd_vprintf(fd, fmt, ap); + if (err < 0) { + log_debug("atcmd_write failed"); + } + va_end(ap); + + return err; +} + +int atcmd_writeline(int fd, char *fmt, ...) +{ + va_list ap; + int err; + int total = 0; + + va_start(ap, fmt); + err = atcmd_vprintf(fd, fmt, ap); + va_end(ap); + if (err < 0) { + log_debug("atcmd_vprintf failed"); + return err; + } + total += err; + + err = atcmd_write_str(fd, ATCMD_EOL); + if (err < 0) { + log_debug("atcmd_write_str failed"); + return err; + } + total += err; + + return total; +} + +/** + * atcmd_value_tok - Tokenize an AT compound value. + * @str: The compound value to tokenize. + * + * Return: A pointer to the next token is returned and @str is updated for the next + * call. Returns NULL on error or when there are no more tokens. + * + */ +char *atcmd_value_tok(char **str) +{ + char *next; + char *begin; + + begin = *str; + if (!begin || !*begin) { + return NULL; + } + + begin += strspn(begin, " \t"); + + if (*begin == '\"') { + next = ++begin; + + next = strchr(next, '\"'); + if (!next) { + log_notice("unterminated string"); + return NULL; + } + *next++ = '\0'; + + next = strchr(next, ','); + if (next) { + *next++ = '\0'; + } + } else if (*begin == '(') { + next = ++begin; + + next = strchr(next, ')'); + if (!next) { + log_notice("unterminated group"); + return NULL; + } + *next++ = '\0'; + + next = strchr(next, ','); + if (next) { + *next++ = '\0'; + } + } else { + next = begin; + + next = strchr(next, ','); + if (next) { + *next++ = '\0'; + } + + strrstrip(begin); + } + + *str = next; + + return begin; +} + +/** + * atcmd_response_brk - Break an AT command response info line in half. + * @str: The response info line to break in half + * + * expected format: "<key>: <value>". <value> can be the empty string. + * + * Return: A pointer to <key> is returned. @str is set to <value>. + * + */ +char *atcmd_response_brk(char **str) +{ + char *next; + char *begin; + + begin = *str; + if (!begin || !*begin) { + return NULL; + } + + begin += strspn(begin, " \t"); + + next = begin; + + next = strstr(next, ": "); + if (!next) { + log_notice("separator \": \" not found"); + return NULL; + } + *next++ = '\0'; + next += strspn(next, " \t"); + + *str = next; + + return begin; +} + +int atcmd_e_write(int fd, int mode) +{ + char buf[ATCMD_LINE_SIZE]; + int tmp; + + atcmd_writeline(fd, "ATE%d", mode); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return 0; +} + +int atcmd_plus_cmgf_write(int fd, int mode) +{ + char buf[ATCMD_LINE_SIZE]; + int tmp; + + atcmd_writeline(fd, "AT+CMGF=%d", mode); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return 0; +} + +int atcmd_plus_cmgw_write(int fd, const char *msg, size_t msg_len) +{ + char buf[ATCMD_LINE_SIZE]; + int tmp; + int mem_index; + + atcmd_writeline(fd, "AT+CMGW=%d", msg_len); + tmp = atcmd_read_until(fd, buf, sizeof(buf), "> "); + if (tmp <= 0) { + log_debug("expected > start sequence but it was not received"); + return -1; + } + + tmp = atcmd_write(fd, msg, strlen(msg)); + tmp = atcmd_write_str(fd, CONTROL_Z_STR); + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMGW: "); + if (tmp <= 0) { + log_debug("expected +CMGW: but it was not received"); + return -1; + } + + mem_index = atoi(buf + strlen("+CMGW: ")); + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return mem_index; +} + +int atcmd_plus_cmgs_write(int fd, const char *msg, size_t msg_len) +{ + char buf[ATCMD_LINE_SIZE]; + int tmp; + int msg_ref; + + atcmd_writeline(fd, "AT+CMGS=%d", msg_len); + tmp = atcmd_read_until(fd, buf, sizeof(buf), "> "); + if (tmp <= 0) { + log_debug("expected > start sequence but it was not received"); + return -1; + } + + tmp = atcmd_write(fd, msg, strlen(msg)); + tmp = atcmd_write_str(fd, CONTROL_Z_STR); + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMGS: "); + if (tmp <= 0) { + log_debug("expected +CMGS: but it was not received"); + return -1; + } + msg_ref = atoi(buf + strlen("+CMGS: ")); + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return msg_ref; +} + +int atcmd_plus_cmss_write(int fd, int index, const char *addr, int addr_type) +{ + char buf[ATCMD_LINE_SIZE]; + int tmp; + int msg_ref; + + atcmd_printf(fd, "AT+CMSS=%d", index); + if (addr) { + atcmd_printf(fd, ",\"%s\"", addr); + if (addr_type != SMS_ADDR_UNSPEC) { + atcmd_printf(fd, ",%d", addr_type); + } + } + atcmd_write_str(fd, ATCMD_EOL); + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMSS: "); + if (tmp <= 0) { + log_debug("expected +CMSS: but it was not received"); + return -1; + } + + msg_ref = atoi(buf + strlen("+CMSS: ")); + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return msg_ref; +} + +int atcmd_plus_cmgd_write(int fd, int index) +{ + char buf[ATCMD_LINE_SIZE]; + int tmp; + + atcmd_writeline(fd, "AT+CMGD=%d", index); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return 0; +} + +int atcmd_response_foreach_line(int fd, atcmd_response_callback_t call, void *prv) +{ + char buf[ATCMD_LINE_SIZE]; + int tmp; + + while (1) { + tmp = atcmd_readline(fd, buf, sizeof(buf)); + if (tmp <= 0) { + return -1; + } + + tmp = call(buf, strlen(buf), prv); + if (tmp) { + return tmp; + } + } + + return -1; +} + +int atcmd_plus_cpms_read(int fd, struct data_store *read_store, + struct data_store *send_store, struct data_store *new_store) +{ + char buf[ATCMD_LINE_SIZE]; + char *save; + char *token; + int tmp; + int i; + + struct data_store *data_stores[] = {read_store, send_store, new_store}; + struct data_store *store; + + atcmd_writeline(fd, "AT+CPMS?"); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPMS: "); + if (tmp <= 0) { + log_debug("expected +CPMS: but it was not received"); + return -1; + } + + save = buf; + token = atcmd_response_brk(&save); + if (!token) { + log_debug("atcmd_response_brk failed"); + return -1; + } + + for (i = 0; i < ARRAY_SIZE(data_stores); i++) { + store = data_stores[i]; + + token = atcmd_value_tok(&save); + if (!token) { + return -1; + } + strncpy(store->name, token, STORE_NAME_LEN); + + token = atcmd_value_tok(&save); + if (!token) { + return -1; + } + store->used = atoi(token); + + token = atcmd_value_tok(&save); + if (!token) { + return -1; + } + store->max = atoi(token); + + log_debug("name[%d]: %s", i, store->name); + log_debug("used[%d]: %d", i, store->used); + log_debug("max[%d]: %d", i, store->max); + } + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return 0; +} + +int atcmd_plus_cpms_test(int fd, struct store_locations *read_loc, + struct store_locations *send_loc, struct store_locations *new_loc) +{ + char buf[ATCMD_LINE_SIZE]; + char *save; + char *token; + char *choices; + int tmp; + int i, j; + + struct store_locations *locations[] = {read_loc, send_loc, new_loc}; + struct store_locations *loc; + + atcmd_writeline(fd, "AT+CPMS=?"); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPMS: "); + if (tmp <= 0) { + log_debug("expected +CPMS: but it was not received"); + return -1; + } + + save = buf; + token = atcmd_response_brk(&save); + if (!token) { + log_debug("atcmd_response_brk failed"); + return -1; + } + + for (i = 0; i < ARRAY_SIZE(locations); i++) { + loc = locations[i]; + + token = atcmd_value_tok(&save); + if (!token) { + break; + } + + choices = token; + for (j = 0; j < STORE_LOCATIONS_MAX; j++) { + token = atcmd_value_tok(&choices); + if (!token) { + break; + } + + strncpy(loc->names[j], token, STORE_NAME_LEN); + + log_debug("loc[%d] choice[%d]: %s", i, j, token); + + loc->nr_locations++; + } + } + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return 0; +} + +int atcmd_plus_cpms_write(int fd, const char *read_name, + const char *send_name, const char *new_name) +{ + char buf[ATCMD_LINE_SIZE]; + int tmp; + + atcmd_writeline(fd, "AT+CPMS=\"%s\",\"%s\",\"%s\"", + read_name, send_name, new_name); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return 0; +} + +int atcmd_plus_cpbs_read(int fd, struct data_store *store) +{ + char buf[ATCMD_LINE_SIZE]; + char *save; + char *token; + int tmp; + + atcmd_writeline(fd, "AT+CPBS?"); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPBS: "); + if (tmp <= 0) { + log_debug("expected +CPBS: but it was not received"); + return -1; + } + + save = buf; + token = atcmd_response_brk(&save); + if (!token) { + log_debug("atcmd_response_brk failed"); + return -1; + } + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("atcmd_value_tok name"); + return -1; + } + strncpy(store->name, token, STORE_NAME_LEN); + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("atcmd_value_tok used"); + return -1; + } + store->used = atoi(token); + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("atcmd_value_tok max"); + return -1; + } + store->max = atoi(token); + + log_debug("name: %s", store->name); + log_debug("used: %d", store->used); + log_debug("max: %d", store->max); + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return 0; +} + +int atcmd_plus_cpbs_test(int fd, struct store_locations *loc) +{ + char buf[ATCMD_LINE_SIZE]; + char *save; + char *token; + char *choices; + int tmp; + int i; + + atcmd_writeline(fd, "AT+CPBS=?"); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPBS: "); + if (tmp <= 0) { + log_debug("expected +CPBS: but it was not received"); + return -1; + } + + save = buf; + token = atcmd_response_brk(&save); + if (!token) { + log_debug("atcmd_response_brk failed"); + return -1; + } + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("atcmd_value_tok group"); + return -1; + } + + choices = token; + for (i = 0; i < STORE_LOCATIONS_MAX; i++) { + token = atcmd_value_tok(&choices); + if (!token) { + break; + } + + strncpy(loc->names[i], token, STORE_NAME_LEN); + + log_debug("choice[%d]: %s", i, token); + + loc->nr_locations++; + } + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return 0; +} + +int atcmd_plus_cpbs_write(int fd, const char *name) +{ + char buf[ATCMD_LINE_SIZE]; + int tmp; + + atcmd_writeline(fd, "AT+CPBS=\"%s\"", name); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return 0; +} + +int atcmd_plus_cpbr_test(int fd, struct phonebook_store *store) +{ + char buf[ATCMD_LINE_SIZE]; + char *save; + char *range; + char *token; + int tmp; + + atcmd_writeline(fd, "AT+CPBR=?"); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPBR: "); + if (tmp <= 0) { + log_debug("expected +CPBR: but it was not received"); + return -1; + } + + save = buf; + token = atcmd_response_brk(&save); + if (!token) { + log_debug("atcmd_response_brk failed"); + return -1; + } + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("atcmd_value_tok start"); + return -1; + } + + range = token; + token = atcmd_value_tok(&range); + if (!token) { + log_debug("atcmd_value_tok range"); + return -1; + } + + range = strchr(token, '-'); + if (!range) { + log_debug("break range"); + return -1; + } + store->index_min = atoi(token); + store->index_max = atoi(range + 1); + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("atcmd_value_tok addr_max"); + return -1; + } + store->addr_max = atoi(token); + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("atcmd_value_tok name_max"); + return -1; + } + store->name_max = atoi(token); + + log_debug("index_min: %d", store->index_min); + log_debug("index_max: %d", store->index_max); + log_debug("addr_max: %d", store->addr_max); + log_debug("name_max: %d", store->name_max); + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + return 0; +} + +int atcmd_init(int fd, int read_timeout) +{ + char buf[ATCMD_LINE_SIZE]; + int tmp; + + tmp = tty_set_read_timeout(fd, read_timeout); + if (tmp < 0) { + return tmp; + } + + atcmd_writeline(fd, "ATV1"); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + log_debug("expected OK but it was not received"); + return -1; + } + + tmp = atcmd_e_write(fd, 0); + if (tmp < 0) { + return tmp; + } + + struct msg_store read_store; + struct msg_store send_store; + struct msg_store new_store; + + memset(&read_store, 0, sizeof(read_store)); + memset(&send_store, 0, sizeof(send_store)); + memset(&new_store, 0, sizeof(new_store)); + + atcmd_plus_cpms_test(fd, &read_store.choices, + &send_store.choices, &new_store.choices); + atcmd_plus_cpms_read(fd, &read_store.selected, + &send_store.selected, &new_store.selected); + if (Global.core.msg_store_read && Global.core.msg_store_send && + Global.core.msg_store_new) { + atcmd_plus_cpms_write(fd, Global.core.msg_store_read, + Global.core.msg_store_send, Global.core.msg_store_new); + } + + return 0; +} diff --git a/src/atcmd.h b/src/atcmd.h new file mode 100644 index 0000000..692a711 --- /dev/null +++ b/src/atcmd.h @@ -0,0 +1,125 @@ +#ifndef __ATCMD_H +#define __ATCMD_H + +#include <termios.h> +#include <stdarg.h> + +#ifdef __ATCMD_C +#define ATCMD_EXTERN +#else +#define ATCMD_EXTERN extern +#endif + +#define CONTROL_Z 0x1A +#define CONTROL_Z_STR "\x1A" + +#define ATCMD_EOL "\r" +#define ATCMD_RESPONSE_EOL "\r\n" + +#define ATCMD_LINE_SIZE 1024 + +struct baud_map { + speed_t baud; + speed_t value; +}; + +speed_t value_to_baud(speed_t value); + +int rts_get(int fd); +int dtr_get(int fd); +int cts_get(int fd); +int dsr_get(int fd); +int cd_get(int fd); +int ri_get(int fd); +int line_signal_set(int fd, int signal, int value); +int rts_set(int fd, int value); +int dtr_set(int fd, int value); + +int tty_configure(int fd, speed_t baud_rate); +int tty_open(const char *dev, speed_t baud_rate); +int tty_close(int tty); +int tty_set_read_timeout(int fd, int timeout); + + +int atcmd_read_until(int fd, char *buf, size_t len, const char *stop); +int atcmd_readline(int fd, char *buf, size_t len); +int atcmd_expect_line(int fd, char *buf, size_t len, const char *expect); + +int atcmd_write(int fd, const char *buf, size_t len); +int atcmd_write_str(int fd, const char *buf); +int atcmd_vprintf(int fd, char *fmt, va_list ap); +int atcmd_printf(int fd, char *fmt, ...); +int atcmd_writeline(int fd, char *fmt, ...); + +char *atcmd_value_tok(char **str); +char *atcmd_response_brk(char **str); + +typedef int (*atcmd_response_callback_t)(char *buf, size_t len, void *prv); +int atcmd_response_foreach_line(int fd, atcmd_response_callback_t call, void *prv); + +int atcmd_e_write(int fd, int mode); +int atcmd_plus_cmgf_write(int fd, int mode); +int atcmd_plus_cmgw_write(int fd, const char *msg, size_t msg_len); +int atcmd_plus_cmgs_write(int fd, const char *msg, size_t msg_len); +int atcmd_plus_cmss_write(int fd, int index, const char *addr, int addr_type); +int atcmd_plus_cmgd_write(int fd, int index); + +#define STORE_NAME_LEN 2 +#define STORE_NAME_SIZE (STORE_NAME_LEN + 1) +#define STORE_LOCATIONS_MAX 8 + +struct data_store { + char name[STORE_NAME_SIZE]; + int used; + int max; +}; + +struct store_locations { + char names[STORE_LOCATIONS_MAX][STORE_NAME_SIZE]; + int nr_locations; +}; + +struct msg_store { + struct data_store selected; + struct store_locations choices; +}; + +int atcmd_plus_cpms_read(int fd, struct data_store *read_store, + struct data_store *send_store, struct data_store *new_store); +int atcmd_plus_cpms_test(int fd, struct store_locations *read_loc, + struct store_locations *send_loc, struct store_locations *new_loc); +int atcmd_plus_cpms_write(int fd, const char *read_name, + const char *send_name, const char *new_name); + +struct phonebook_store { + struct data_store selected; + struct store_locations choices; + int index_min; + int index_max; + int addr_max; + int name_max; +}; + +int atcmd_plus_cpbs_read(int fd, struct data_store *store); +int atcmd_plus_cpbs_test(int fd, struct store_locations *loc); +int atcmd_plus_cpbs_write(int fd, const char *name); + +int atcmd_plus_cpbr_test(int fd, struct phonebook_store *store); + +int atcmd_init(int fd, int read_timeout); + +#if __ATCMD_C +const char *abort_dfl[] = { + "NO DIAL TONE", + "NO DIALTONE", + "NO ANSWER", + "NO CARRIER", + "DELAYED", + "VOICE", + "BUSY", +}; +#else +ATCMD_EXTERN const char *abort_dfl[]; +#endif + +#endif /* ~__ATCMD_H */ diff --git a/src/cmd_options.h b/src/cmd_options.h new file mode 100644 index 0000000..fef1b16 --- /dev/null +++ b/src/cmd_options.h @@ -0,0 +1,33 @@ +#ifndef __CMD_OPTIONS_H +#define __CMD_OPTIONS_H + +#define __CMD_OPT_DEVICE "d:" +#define __CMD_OPT_BAUD_RATE "b:" +#define __CMD_OPT_INTERACTIVE "i" +#define __CMD_OPT_NON_INTERACTIVE "n" +#define __CMD_OPT_VERBOSE "v" +#define __CMD_OPT_FILE "f:" + +#define CMD_OPT_DEVICE 'd' +#define CMD_OPT_BAUD_RATE 'b' +#define CMD_OPT_INTERACTIVE 'i' +#define CMD_OPT_NON_INTERACTIVE 'n' +#define CMD_OPT_VERBOSE 'v' +#define CMD_OPT_FILE 'f' + +enum cmd_opt_long_only { + CMD_OPT_VERSION = 128, + CMD_OPT_HELP, + CMD_OPT_READ_TIMEOUT, + CMD_OPT_ALPHABET, + CMD_OPT_SMSC_ADDR, + CMD_OPT_CMGW_FIRST, + CMD_OPT_MSG_STORE_READ, + CMD_OPT_MSG_STORE_SEND, + CMD_OPT_MSG_STORE_NEW, + CMD_OPT_PHONEBOOK_STORE, + CMD_OPT_SUBJECT, +}; + +#endif /* ~__CMD_OPTIONS_H */ + diff --git a/src/global.h b/src/global.h new file mode 100644 index 0000000..ccfb6b4 --- /dev/null +++ b/src/global.h @@ -0,0 +1,92 @@ +#ifndef __GLOBAL_H +#define __GLOBAL_H + +#include "config.h" +#include <termios.h> +#include "utils.h" +#include "log.h" + +#ifdef __MAIN_FILE_C +#define GLOBAL_EXTERN +#else +#define GLOBAL_EXTERN extern +#endif + +struct global_user { + char *name; + char *email; +}; + +struct global_core { + speed_t baud_rate; + int read_timeout; + char *device; + int verbose; + int interactive; + + char *msg_store_read; + char *msg_store_send; + char *msg_store_new; + char *pb_store; + + char *editor; + char *edit_file; +}; + +struct global_smtp { + char *server; + int port; + char *user; + char *passwd; + char *encryption; +}; + +struct global_send_email { + char *domain; +}; + +struct global_config { + struct global_user user; + struct global_core core; + struct global_smtp smtp; + struct global_send_email send_email; +}; + +#if __MAIN_FILE_C +struct global_config Global = { + .user = { + .name = NULL, + .email = NULL, + }, + .core = { + .verbose = false, + .interactive = true, + .baud_rate = B115200, + .read_timeout = 5000, + .device = DEFAULT_DEVICE, + .msg_store_read = "MT", + .msg_store_send = "MT", + .msg_store_new = "MT", + .pb_store = "ME", + .editor = "vi", + .edit_file = "${HOME}/.smsmsg", + }, + .smtp = { + .server = NULL, + .port = 0, + .user = NULL, + .passwd = NULL, + .encryption = NULL, + }, + .send_email = { + .domain = NULL, + }, +}; +#else +extern struct global_config Global; +#endif + + + +#endif /* ~__GLOBAL_H */ + diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..9d807f9 --- /dev/null +++ b/src/list.h @@ -0,0 +1,245 @@ +#ifndef __LIST_H +#define __LIST_H + +/* This file is from Linux Kernel (include/linux/list.h) + * and modified by simply removing hardware prefetching of list items. + * Here by copyright, credits attributed to wherever they belong. + * Kulesh Shanmugasundaram (kulesh [squiggly] isis.poly.edu) + */ + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head *prev, struct list_head *next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = (void *) 0; + entry->prev = (void *) 0; +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(struct list_head *head) +{ + return head->next == head; +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); \ + pos = pos->next) +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); \ + pos = pos->prev) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + + +#endif + diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..d53b795 --- /dev/null +++ b/src/log.h @@ -0,0 +1,47 @@ +#ifndef __LOG_H +#define __LOG_H + +#include "config.h" + +#if CONFIG_USE_SYSLOG +# include <syslog.h> +# define LOG_FACILITY LOG_USER + +# define __log(level, name, format, args...) \ + syslog(LOG_FACILITY | level, "[" name "] %s:%s:%d: " format "\n" , \ + __FILE__ , __func__ , __LINE__ , ## args) +#else +# define __log(level, name, format, args...) \ + fprintf(stderr, "[" name "] %s:%s:%d: " format "\n" , \ + __FILE__ , __func__ , __LINE__ , ## args) +#endif + +#define log_emerg(format, args...) __log(LOG_EMERG, "EMERG", format , ## args) +#define log_alert(format, args...) __log(LOG_ALERT, "ALERT", format , ## args) +#define log_crit(format, args...) __log(LOG_CRIT, "CRIT", format , ## args) +#define log_error(format, args...) __log(LOG_ERR, "ERROR", format , ## args) +#define log_warning(format, args...) __log(LOG_WARNING, "WARNING", format , ## args) +#define log_notice(format, args...) __log(LOG_NOTICE, "NOTICE", format , ## args) +#define log_info(format, args...) __log(LOG_INFO, "INFO", format , ## args) +#if DEBUG +# define log_debug(format, args...) __log(LOG_DEBUG, "DEBUG", format , ## args) +#else +# define log_debug(format, args...) do {} while (0) +#endif + +#if DEBUG +# define debug_buffer(msg, buf, len) \ +do { \ + __log(LOG_DEBUG, "DEBUG", msg " \"%.*J\"", len, buf); \ +} while (0) +#else +# define debug_buffer(_msg, _buf, _len) do { } while (0) +#endif + +#define BUG(format, args...) \ +do { \ + log_emerg("BUG: " format , ## args); \ + exit(124); \ +} while (0) + +#endif /* ~__LOG_H */ diff --git a/src/pdu.c b/src/pdu.c new file mode 100644 index 0000000..2ceb7ae --- /dev/null +++ b/src/pdu.c @@ -0,0 +1,306 @@ +/* + * PDU common + * + * Copyright (C) 2010 by James Maki + * + * Author: James Maki <jamescmaki@gmail.com> + * + * 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 + * + */ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <ctype.h> +#include <time.h> +#include <unistd.h> + +#include "config.h" +#include "log.h" +#include "pdu.h" +#include "sms_utils.h" + +int hex_nibble_scan(const char *buf, size_t len) +{ + static const char digits[] = "0123456789ABCDEF"; + + int i; + int result = 0; + + STRLEN_CHECK(buf, len, -1); + + for (i = 0; i < len; i++) { + const char *where; + + where = strchr(digits, toupper(buf[i])); + if (!where) { + return -1; + } + + result = (result << 4) + (where - digits); + } + + return result; +} + +int hex_byte_decode(const char *buf) +{ + return hex_nibble_scan(buf, HEX_BYTE_LEN); +} + +int hex_byte_encode(char *buf, size_t len, int byte) +{ + if (len < HEX_BYTE_LEN) { + log_error("buffer is not large enough"); + } + + return snprintf(buf, len, "%02X", byte & 0xFF); +} + +int nibble_swap(char *buf, size_t len) +{ + int i; + char c; + + if (len & 1) { + BUG("odd buffer size found"); + } + + for (i = 0; i < len; i += HEX_BYTE_LEN) { + c = buf[i]; + buf[i] = buf[i + 1]; + buf[i + 1] = c; + } + + return i; +} + +int strunpad(char *str, unsigned char c) +{ + char *cp = strchr(str, c); + + if (cp) { + *cp = '\0'; + } + + return strlen(str); +} + +int strpad(char *str, size_t len, unsigned char c) +{ + int i; + int start = strlen(str); + + for (i = start; i < len; i++) { + str[i] = c; + } + + str[i] = '\0'; + + return i - start; +} + +int pdu_format_timestamp(struct pdu_info *pdu, char *str, size_t len, const char *fmt) +{ + if (len <= 0) { + return 0; + } + + switch (pdu->type.msg_type) { + case PDU_MTI_DELIVER: + case PDU_MTI_REPORT: + return strftime(str, len, fmt, &pdu->timestamp); + default: + *str = '\0'; + return 0; + } +} + +int pdu_format_vp(struct pdu_info *pdu, char *str, size_t len) +{ + int val; + + if (len <= 0) { + return 0; + } + + *str = '\0'; + + val = pdu->validity_period; + + switch (pdu->type.msg_type) { + case PDU_MTI_SUBMIT: + if (val >= 0 && val <= 143) { + val = (val + 1) * 5; + } else if (val >= 144 && val <= 167) { + val = ((val - 143) * 30) + (12 * 60); + } else if (val >= 168 && val <= 196) { + val = (val - 166) * (24 * 60); + } else { + val = (val - 192) * (7 * 24 * 60); + } + + return snprintf(str, len, "%d", val); + default: + return 0; + } +} + +int pdu_addr_check(const char *addr_str) +{ + if (addr_str[0] == '+') { + addr_str++; + } + + if (!*addr_str) { + log_error("empty addr"); + return -1; + } + + int len = strlen(addr_str); + int bad = strcspn(addr_str, "1234567890"); + + if (bad != len) { + log_error("bad character in addr at offset %d", bad); + return -1; + } + + return 0; +} + +#define GWRITTEN_SEP(c) ((c) == '-' || (c) == '.') +#define NUM_ADDR "1234567890-." +#define CMD_ADDR "1234567890*#+-." + +int pdu_addr_type_infer(const char *str) +{ + if (*str == '+' && strlen(str + 1) == strspn(str + 1, NUM_ADDR)) { + log_debug("SMS_ADDR_GLOBAL"); + return SMS_ADDR_GLOBAL; + } else if (strlen(str) == strspn(str, NUM_ADDR)) { + log_debug("SMS_ADDR_LOCAL"); + return SMS_ADDR_LOCAL; + } else if (strlen(str) == strspn(str, CMD_ADDR)) { + log_debug("SMS_ADDR_CMD"); + return SMS_ADDR_CMD; + } else { + log_debug("SMS_ADDR_TEXT"); + return SMS_ADDR_TEXT; + } +} + +static int pdu_addr_clean_copy(struct pdu_addr *addr, const char *src) +{ + char *dest = addr->addr; + int count = 0; + int c; + + while((c = *src++)) { + if(isdigit(c)) { + if(count >= PDU_ADDR_SIZE - 1) { + log_error("addr exceeds max length"); + return -1; + } + dest[count++] = c; + } else if(GWRITTEN_SEP(c)) { + continue; + } else if(c == '+' && !count) { + continue; + } else { + log_error("addr contains invalid char %c at offset %d", c, count); + return -1; + } + } + + dest[count] = '\0'; + + if(!count) { + log_error("empty addr"); + return -1; + } + + return count; +} + +int pdu_addr_fill(struct pdu_addr *addr, const char *addr_str, int type) +{ + int tmp; + + memset(addr, 0 , sizeof(*addr)); + + addr->type = pdu_addr_type_infer(addr_str); + + tmp = pdu_addr_clean_copy(addr, addr_str); + if (tmp < 0) { + return tmp; + } + + log_debug("addr: %s", addr->addr); + + if (type) { + addr->type = type; + } + + log_debug("addr-type: 0x%02X", addr->type); + + return tmp; +} + +int pdu_user_data_read(int fd, struct pdu_info *pdu) +{ + int err; + char c; + int total = 0; + int len; + + if (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT) { + len = PDU_UD_7BIT_MAX; + } else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_EIGHT) { + len = PDU_UD_8BIT_MAX; + } else { + return -1; + } + + while (1) { + err = read(fd, &c, 1); + if (err < 0) { + log_error("read failed: %m"); + return err; + } else if (err > 0) { + if (total < len) { + pdu->user_data[total++] = c; + } else { + log_debug("read max message length %d", total); + goto done; + } + } else { + log_debug("read eof at %d", total); + goto done; + } + } + +done: + + pdu->user_data[total] = '\0'; + pdu->user_data_len = total; + + debug_buffer("message is: ", pdu->user_data, total); + + return total; +} diff --git a/src/pdu.h b/src/pdu.h new file mode 100644 index 0000000..0305550 --- /dev/null +++ b/src/pdu.h @@ -0,0 +1,132 @@ +#ifndef __PDU_H +#define __PDU_H + +#include <stdint.h> +#include <string.h> +#include <time.h> + +#include <phonebook.h> + +extern char *strptime(const char *s, const char *format, struct tm *tm); + +#define PDU_BUFFER_SIZE 1024 +#define PDU_ADDR_SIZE PHONEBOOK_ADDR_SIZE + +#define PDU_OCTETS_MAX 140 +#define PDU_UD_8BIT_MAX PDU_OCTETS_MAX +#define PDU_UD_7BIT_MAX (PDU_OCTETS_MAX * 8 / 7) +#define PDU_UD_SIZE (PDU_UD_7BIT_MAX + 1) + +#define PDU_TIMESTAMP_LEN 14 +#define PDU_TIMESTAMP_SIZE (PDU_TIMESTAMP_LEN + 1) +#define GMT_OFFSET_LEN 2 +#define GMT_OFFSET_IDX (PDU_TIMESTAMP_LEN - GMT_OFFSET_LEN) + +#define PDU_VPF_RELATIVE_2DAYS 0xA8 + +struct pdu_addr { + char addr[PDU_ADDR_SIZE]; + uint8_t type; + uint8_t len; +}; + +enum { + PDU_MTI_DELIVER = 0, + PDU_MTI_SUBMIT = 1, + PDU_MTI_REPORT = 2, + PDU_MTI_RESERVED = 3, +}; + +enum { + PDU_VPF_NOT_PRESENT = 0, + PDU_VPF_ENHANCED = 1, + PDU_VPF_RELATIVE = 2, + PDU_VPF_ABSOUTE = 3, +}; + +enum { + PDU_ALPHABET_DEFAULT = 0, + PDU_ALPHABET_EIGHT = 1, + PDU_ALPHABET_UCS2 = 2, + PDU_ALPHABET_RESERVED = 3, +}; + +struct pdu_info { + int msg_len; + struct pdu_addr smsc_addr; + struct pdu_addr addr; + union { + uint8_t type; + struct { + uint8_t msg_type: 2; + uint8_t more_msg: 1; + uint8_t unused: 2; + uint8_t status_report: 1; + uint8_t user_data_header: 1; + uint8_t reply_path: 1; + }; + struct { + uint8_t msg_type: 2; + uint8_t reject_duplicates: 1; + uint8_t validity_period_format: 2; + uint8_t status_report: 1; + uint8_t user_data_header: 1; + uint8_t reply_path: 1; + }; + } type; + uint8_t msg_reference; + uint8_t protocol_id; + union { + uint8_t data_coding; + struct { + uint8_t msg_class: 2; + uint8_t alphabet: 2; + uint8_t have_msg_class: 1; + uint8_t compressed: 1; + uint8_t unused: 2; + } general; + } data_coding; + uint8_t validity_period; + struct tm timestamp; + uint8_t user_data_len; + uint8_t user_data[PDU_UD_SIZE]; +}; + +#define HEX_BYTE_LEN 2 + +int hex_nibble_scan(const char *buf, size_t len); +int hex_byte_decode(const char *buf); + +int hex_byte_encode(char *buf, size_t len, int byte); + +int nibble_swap(char *buf, size_t len); +int strunpad(char *str, unsigned char c); +int strpad(char *str, size_t len, unsigned char c); + +int pdu_format_timestamp(struct pdu_info *pdu, char *str, size_t len, const char *fmt); +int pdu_format_vp(struct pdu_info *pdu, char *str, size_t len); +int pdu_addr_type_infer(const char *str); +int pdu_addr_fill(struct pdu_addr *addr, const char *addr_str, int type); +int pdu_user_data_read(int fd, struct pdu_info *pdu); + +#define shiftr(a, n) ((a) >> (n)) +#define shiftl(a, n) ((a) << (n)) + +#define bit_mask(n, s) (((1 << (n)) - 1) << (s)) +#define bit_maskr(n) bit_mask(n, 0) +#define bit_maskl(n, b) bit_mask(n, (b) - (n)) + +#define cycleup(n, mod) ((n) % (mod)) +#define cycledown(n, mod) ((mod - 1) - ((n) % (mod))) + +#define octet_align(n) ((((n) * 7) + ((-(n) * 7) & 7)) / 8) + +#define STRLEN_CHECK(str, len, ret) \ +do { \ + if (strnlen(str, len) < len) { \ + log_error("buffer ends unexpectedly early"); \ + return ret; \ + } \ +} while (0) + +#endif /* ~__PDU_H */ diff --git a/src/pdu_decode.c b/src/pdu_decode.c new file mode 100644 index 0000000..643fa15 --- /dev/null +++ b/src/pdu_decode.c @@ -0,0 +1,340 @@ +/* + * PDU Decode + * + * Copyright (C) 2010 by James Maki + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <ctype.h> + +#include "config.h" +#include "log.h" +#include "utils.h" +#include "pdu_decode.h" + +#define DECODE_FAIL(cond, name, ret) \ +do { \ + if (cond) { \ + log_error("decode failed at %s", name); \ + return ret; \ + } \ +} while (0) + +int pdu_decode_timestamp(const char *pdu_str, struct tm *tm) +{ + char buf[PDU_TIMESTAMP_SIZE + 3]; + char *cp; + int off_upper; + int off_lower; + int off; + + STRLEN_CHECK(pdu_str, PDU_TIMESTAMP_LEN, -1); + + memset(tm, 0, sizeof(*tm)); + memset(buf, 0, sizeof(buf)); + + strncpy(buf, pdu_str, PDU_TIMESTAMP_LEN); + nibble_swap(buf, PDU_TIMESTAMP_LEN); + strunpad(buf, 'F'); + + off_upper = hex_nibble_scan(buf + GMT_OFFSET_IDX, 1); + off_lower = hex_nibble_scan(buf + GMT_OFFSET_IDX + 1, 1); + + if (off_upper & BIT(3)) { + off_upper &= ~BIT(3); + buf[GMT_OFFSET_IDX] = '-'; + } else { + buf[GMT_OFFSET_IDX] = '+'; + } + + off = (off_upper * 10 + off_lower) * 15; + + snprintf(buf + GMT_OFFSET_IDX + 1, 5, "%02d%02d", off / 60, off % 60); + + cp = (char *) strptime(buf, "%y%m%d%H%M%S%z", tm); + if (!cp || *cp) { + log_error("timestamp could not be converted to tm"); + return -1; + } + + return PDU_TIMESTAMP_LEN; +} + +int pdu_decode_addr(const char *pdu_str, struct pdu_addr *addr, int smsc) +{ + const char *begin = pdu_str; + int addr_len; + int tmp; + + memset(addr, 0, sizeof(*addr)); + + STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); + + tmp = hex_byte_decode(pdu_str); + DECODE_FAIL(tmp < 0, "addr-len", -1); + pdu_str += HEX_BYTE_LEN; + addr->len = tmp; + + addr_len = addr->len; + if (smsc) { + if (!addr_len) { + goto done; + } + addr_len = addr_len * HEX_BYTE_LEN - HEX_BYTE_LEN; + } else { + if (addr_len & 1) { + addr_len++; + } + } + + STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); + + tmp = hex_byte_decode(pdu_str); + DECODE_FAIL(tmp < 0, "addr-type", -1); + pdu_str += HEX_BYTE_LEN; + addr->type = tmp; + + if (addr_len < 0 || addr_len >= sizeof(addr->addr)) { + log_error("invalid length: 0x%02X", addr_len); + return -1; + } + + log_debug("addr-len [transformed]: 0x%02X", addr_len); + log_debug("addr-type: 0x%02X", addr->type); + + STRLEN_CHECK(pdu_str, addr_len, -1); + + strncpy(addr->addr, pdu_str, addr_len); + + nibble_swap(addr->addr, addr_len); + strunpad(addr->addr, 'F'); + pdu_str += addr_len; + +done: + + log_debug("addr: %s", addr->addr); + log_debug("addr-len: 0x%02X", addr->len); + + return pdu_str - begin; +} + +#define octet_idx(n) octet_align(n) + +#define decode_septet(buf, n) \ +shiftl(bit_maskr(cycledown(n, 8)) & buf[octet_idx(n)], cycleup(n, 8)) | \ +shiftr(bit_maskl(cycleup(n, 8), 8) & buf[octet_idx(n - 1)], cycledown(n - 1, 8)) + +int pdu_decode_user_data(const char *pdu_str, struct pdu_info *pdu) +{ + const char *begin = pdu_str; + int tmp; + uint8_t octets[PDU_OCTETS_MAX + 1]; + int nr_octets; + int i; + + STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); + + tmp = hex_byte_decode(pdu_str); + DECODE_FAIL(tmp < 0, "user-data-len", -1); + pdu_str += HEX_BYTE_LEN; + pdu->user_data_len = tmp; + + if (pdu->data_coding.general.unused) { + log_error("data coding group 0x%02X not implemented", + pdu->data_coding.data_coding & 0xF0); + return -1; + } + + if (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT) { + log_debug("data coding alphabet is default"); + + if (pdu->user_data_len > PDU_UD_7BIT_MAX) { + log_warning("pdu contains invalid user-data-len: 0x%02X", + pdu->user_data_len); + pdu->user_data_len = PDU_UD_7BIT_MAX; + } + + nr_octets = octet_align(pdu->user_data_len); + } else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_EIGHT) { + log_debug("data coding alphabet is eight"); + + if (pdu->user_data_len > PDU_UD_8BIT_MAX) { + log_warning("pdu contains invalid user-data-len: 0x%02X", + pdu->user_data_len); + pdu->user_data_len = PDU_UD_8BIT_MAX; + } + + nr_octets = pdu->user_data_len; + } else { + log_debug("data coding alphabet 0x%02X not implemented", + pdu->data_coding.general.alphabet); + return -1; + } + + STRLEN_CHECK(pdu_str, nr_octets * 2, -1); + + log_debug("user-data-len: 0x%02X", pdu->user_data_len); + log_debug("nr_octets: 0x%02X", nr_octets); + + for (i = 0; i < nr_octets; i++) { + octets[i] = hex_byte_decode(pdu_str); + pdu_str += HEX_BYTE_LEN; + } + + if (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT) { + for (i = 0; i < pdu->user_data_len; i++) { + pdu->user_data[i] = decode_septet(octets, i); + } + pdu->user_data[i] = '\0'; + } else { + for (i = 0; i < pdu->user_data_len; i++) { + pdu->user_data[i] = octets[i]; + } + pdu->user_data[i] = '\0'; + } + + return pdu_str - begin; +} + +int pdu_decode(const char *pdu_str, struct pdu_info *pdu) +{ + const char *begin = pdu_str; + const char *msg_begin; + int tmp; + + memset(pdu, 0, sizeof(*pdu)); + + tmp = pdu_decode_addr(pdu_str, &pdu->smsc_addr, 1); + DECODE_FAIL(tmp < 0, "smsc-addr", -1); + pdu_str += tmp; + + msg_begin = pdu_str; + + log_debug("smsc-addr: %s", pdu->smsc_addr.addr); + log_debug("smsc-addr-type: 0x%02X", pdu->smsc_addr.type); + log_debug("smsc-addr-len: 0x%02X", pdu->smsc_addr.len); + + STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); + + tmp = hex_byte_decode(pdu_str); + DECODE_FAIL(tmp < 0, "type", -1); + pdu_str += HEX_BYTE_LEN; + pdu->type.type = tmp; + + log_debug("type: 0x%02X", pdu->type.type); + + if (pdu->type.msg_type == PDU_MTI_SUBMIT || + pdu->type.msg_type == PDU_MTI_REPORT) { + STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); + + tmp = hex_byte_decode(pdu_str); + DECODE_FAIL(tmp < 0, "msg-reference", -1); + pdu_str += HEX_BYTE_LEN; + pdu->msg_reference = tmp; + + log_debug("msg-reference: 0x%02X", pdu->msg_reference); + } + + tmp = pdu_decode_addr(pdu_str, &pdu->addr, 0); + DECODE_FAIL(tmp < 0, "addr", -1); + pdu_str += tmp; + + log_debug("addr: %s", pdu->addr.addr); + log_debug("addr-type: 0x%02X", pdu->addr.type); + log_debug("addr-len: 0x%02X", pdu->addr.len); + + switch (pdu->type.msg_type) { + case PDU_MTI_REPORT: + tmp = pdu_decode_timestamp(pdu_str, &pdu->timestamp); + DECODE_FAIL(tmp < 0, "report-timestamp", -1); + pdu_str += tmp; + + break; + case PDU_MTI_DELIVER: + case PDU_MTI_SUBMIT: + STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); + + tmp = hex_byte_decode(pdu_str); + DECODE_FAIL(tmp < 0, "protocol-id", -1); + pdu_str += HEX_BYTE_LEN; + pdu->protocol_id = tmp; + + log_debug("protocol-id: 0x%02X", pdu->protocol_id); + + STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); + + tmp = hex_byte_decode(pdu_str); + DECODE_FAIL(tmp < 0, "data-coding-scheme", -1); + pdu_str += HEX_BYTE_LEN; + pdu->data_coding.data_coding = tmp; + + log_debug("data-coding-scheme: 0x%02X", pdu->data_coding.data_coding); + + if (pdu->type.msg_type == PDU_MTI_SUBMIT) { + log_debug("validity-period-format: 0x%02X", + pdu->type.validity_period_format); + switch (pdu->type.validity_period_format) { + case PDU_VPF_RELATIVE: + STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); + + tmp = hex_byte_decode(pdu_str); + DECODE_FAIL(tmp < 0, "validity-period", -1); + pdu_str += HEX_BYTE_LEN; + pdu->validity_period = tmp; + + log_debug("validity-period: 0x%02X", pdu->validity_period); + + break; + + case PDU_VPF_ENHANCED: + log_warning("PDU_VPF_ENHANCED? Falling through to absolute"); + case PDU_VPF_ABSOUTE: + tmp = pdu_decode_timestamp(pdu_str, &pdu->timestamp); + DECODE_FAIL(tmp < 0, "validity-period-timestamp", -1); + pdu_str += tmp; + + return -1; + + case PDU_VPF_NOT_PRESENT: + default: + break; + } + } else if (pdu->type.msg_type == PDU_MTI_DELIVER) { + tmp = pdu_decode_timestamp(pdu_str, &pdu->timestamp); + DECODE_FAIL(tmp < 0, "delivery-timestamp", -1); + pdu_str += tmp; + } + + tmp = pdu_decode_user_data(pdu_str, pdu); + DECODE_FAIL(tmp < 0, "user-data", -1); + pdu_str += tmp; + } + + pdu->msg_len = (pdu_str - msg_begin) / 2; + + log_debug("msg_len: %d", pdu->msg_len); + + return pdu_str - begin; +} diff --git a/src/pdu_decode.h b/src/pdu_decode.h new file mode 100644 index 0000000..004c8ad --- /dev/null +++ b/src/pdu_decode.h @@ -0,0 +1,12 @@ +#ifndef __PDU_DECODE_H +#define __PDU_DECODE_H + +#include "pdu.h" + +int pdu_decode_timestamp(const char *pdu_str, struct tm *tm); +int pdu_decode_addr(const char *pdu_str, struct pdu_addr *addr, int smsc); +int pdu_decode_user_data(const char *pdu_str, struct pdu_info *pdu); + +int pdu_decode(const char *pdu_str, struct pdu_info *pdu); + +#endif /* ~__PDU_DECODE_H */ diff --git a/src/pdu_decoder.c b/src/pdu_decoder.c new file mode 100644 index 0000000..f6a72e4 --- /dev/null +++ b/src/pdu_decoder.c @@ -0,0 +1,157 @@ +/* + * PDU Decoder tool + * + * Copyright (C) 2010 by James Maki + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#define __MAIN_FILE_C 1 + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <ctype.h> +#include <time.h> +#include <getopt.h> + +#include "global.h" +#include "cmd_options.h" +#include "sms_utils.h" +#include "pdu_decode.h" +#include "xprintf.h" +#include "utils.h" + +static void print_version(const char *name) +{ + printf("%s (" PACKAGE ") " VERSION " (" __DATE__ " " __TIME__ ")\n", name); + printf("Copyright (C) 2010 by Multi-Tech Systems\n"); + printf( +"This program is free software; you may redistribute it under the terms of\n" +"the GNU General Public License version 2 or (at your option) any later version.\n" +"This program has absolutely no warranty.\n"); +} + +static void usage(FILE *out) +{ + fprintf(out, "Usage: pdu-decoder [ OPTIONS ... ] PDU [ PDU ... ]\n"); + fprintf(out, "where OPTIONS := { \n"); + fprintf(out, " --verbose\n"); + fprintf(out, " }\n"); + fprintf(out, "\n"); +} + +static char *short_options = "+" + __CMD_OPT_VERBOSE; +static struct option long_options[] = { + {"verbose", 0, NULL, CMD_OPT_VERBOSE}, + {"version", 0, NULL, CMD_OPT_VERSION}, + {"help", 0, NULL, CMD_OPT_HELP}, + {0, 0, 0, 0}, +}; + +int main(int argc, char *argv[]) +{ + int i; + int option_index; + int err; + struct pdu_info pdu; + char buf[64]; + int nr_success = 0; + int nr_fail = 0; + int nr_short = 0; + int nr_long = 0; + + xprintf_init(); + + while ((i = getopt_long(argc, argv, short_options, long_options, &option_index)) >= 0) { + switch (i) { + case 0: + break; + + case CMD_OPT_VERBOSE: + Global.core.verbose = true; + break; + + case CMD_OPT_VERSION: + print_version("pdu-decoder"); + exit(0); + break; + + case CMD_OPT_HELP: + usage(stdout); + exit(0); + break; + + default: + usage(stderr); + exit(1); + } + } + + if (optind >= argc) { + usage(stderr); + exit(1); + } + argc -= optind; + argv += optind; + + for ( ; *argv; argv++) { + err = pdu_decode(*argv, &pdu); + if (err < 0) { + printf("pdu decode failed: %d\n", err); + nr_fail++; + } else { + log_debug("pdu-len: %d", strlen(*argv)); + log_debug("decoded-len: %d", err); + + if (err < strlen(*argv)) { + nr_short++; + } else if (err > strlen(*argv)) { + nr_long++; + } + + printf("message-length: %d\n", pdu.msg_len); + printf("smsc-addr-length: 0x%02X\n", pdu.smsc_addr.len); + printf("smsc-addr-type: 0x%02X\n", pdu.smsc_addr.type); + printf("smsc-addr: %s\n", pdu.smsc_addr.addr); + printf("type: 0x%02X\n", pdu.type.type); + printf("message-reference: 0x%02X\n", pdu.msg_reference); + printf("protocol-id: 0x%02X\n", pdu.protocol_id); + printf("addr-length: 0x%02X\n", pdu.addr.len); + printf("addr-type: 0x%02X\n", pdu.addr.type); + printf("addr: %s\n", pdu.addr.addr); + pdu_format_vp(&pdu, buf, sizeof(buf)); + printf("validity-period: %s\n", buf); + pdu_format_timestamp(&pdu, buf, sizeof(buf), "%Y-%m-%d %T %z"); + printf("timestamp: %s\n", buf); + printf("user-data-length: 0x%02X\n", pdu.user_data_len); + printf("user-data: \"%.*J\"\n", pdu.user_data_len, pdu.user_data); + printf("\n"); + + nr_success++; + } + } + + printf("%d successful, %d failed\n", nr_success, nr_fail); + log_debug("%d short, %d long", nr_short, nr_long); + + return 0; +} + diff --git a/src/pdu_encode.c b/src/pdu_encode.c new file mode 100644 index 0000000..704bc5b --- /dev/null +++ b/src/pdu_encode.c @@ -0,0 +1,307 @@ +/* + * PDU Encode + * + * Copyright (C) 2010 by James Maki + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <ctype.h> + +#include "global.h" +#include "utils.h" +#include "pdu_encode.h" + +#define ENCODE_FAIL(cond, name, ret) \ +do { \ + if (cond) { \ + log_error("encode failed at %s", name); \ + return ret; \ + } \ +} while (0) + +int pdu_encode_timestamp(char *pdu_str, size_t len, struct tm *tm) +{ + int tmp; + int off_upper; + int off_lower; + int off; + int negative; + + if (len < PDU_TIMESTAMP_SIZE) { + log_error("buffer is not large enough to hold a timestamp"); + return -1; + } + + tmp = strftime(pdu_str, PDU_TIMESTAMP_SIZE, "%y%m%d%H%M%S", tm); + ENCODE_FAIL(tmp != GMT_OFFSET_IDX, "timestamp", -1); + + off = tm->tm_gmtoff; + if (off < 0) { + off = -off; + negative = 1; + } + off = off / 60 / 15; + + off_upper = off / 10; + off_lower = off % 10; + + if (negative) { + off_upper |= BIT(3); + } + + snprintf(pdu_str + GMT_OFFSET_IDX, 2, "%X", off_upper & 0xF); + snprintf(pdu_str + GMT_OFFSET_IDX + 1, 2, "%X", off_lower & 0xF); + + nibble_swap(pdu_str, PDU_TIMESTAMP_LEN); + strpad(pdu_str, PDU_TIMESTAMP_LEN, 'F'); + + log_debug("%s", pdu_str); + + return PDU_TIMESTAMP_LEN; +} + +int pdu_encode_addr(char *pdu_str, size_t len, struct pdu_addr *addr, int smsc) +{ + char *begin = pdu_str; + int addr_len; + int tmp; + + addr_len = strlen(addr->addr); + + if (smsc) { + if (addr_len) { + if (addr_len & 1) { + addr_len++; + } + addr_len = addr_len / HEX_BYTE_LEN + 1; + } + } + + log_debug("addr-len [transformed]: 0x%02X", addr_len); + addr->len = addr_len; + + tmp = hex_byte_encode(pdu_str, len, addr_len); + ENCODE_FAIL(tmp != HEX_BYTE_LEN, "addr-len", -1); + pdu_str += tmp; + len -= tmp; + + if (smsc && !addr_len) { + log_debug("smsc addr is empty"); + goto done; + } + + tmp = hex_byte_encode(pdu_str, len, addr->type); + ENCODE_FAIL(tmp != HEX_BYTE_LEN, "addr-type", -1); + pdu_str += tmp; + len -= tmp; + + addr_len = strlen(addr->addr); + if (addr_len & 1) { + addr_len++; + } + + if (len < addr_len + 1) { + log_error("buffer is not large enough to hold the addr"); + return -1; + } + + strcpy(pdu_str, addr->addr); + strpad(pdu_str, addr_len, 'F'); + nibble_swap(pdu_str, addr_len); + len -= addr_len; + pdu_str += addr_len; + +done: + + return pdu_str - begin; +} + +#define septet_idx(n) ((n) * 8 / 7) + +#define encode_octet(buf, n) \ +shiftr(bit_maskl(cycledown(n, 7) + 1, 7) & buf[septet_idx(n)], cycleup(n, 7)) | \ +shiftl(bit_maskr(cycleup(n, 7) + 1) & buf[septet_idx(n) + 1], cycledown(n, 7) + 1) + +int pdu_encode_user_data(char *pdu_str, size_t len, struct pdu_info *pdu) +{ + char *begin = pdu_str; + int tmp; + uint8_t octets[PDU_OCTETS_MAX]; + int nr_octets; + int i; + + if (pdu->data_coding.general.unused) { + log_error("data coding group 0x%02X not implemented", + pdu->data_coding.data_coding & 0xF0); + return -1; + } + + if (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT) { + log_debug("data coding alphabet is default"); + } else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_EIGHT) { + log_debug("data coding alphabet is eight"); + } else { + log_debug("data coding alphabet 0x%02X not implemented", + pdu->data_coding.general.alphabet); + return -1; + } + + if (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT) { + if (pdu->user_data_len > PDU_UD_7BIT_MAX) { + log_error("string exceeds 7-bit data max length"); + return -1; + } + + nr_octets = octet_align(pdu->user_data_len); + for (i = 0; i < nr_octets; i++) { + octets[i] = encode_octet(pdu->user_data, i); + } + } else { + if (pdu->user_data_len > PDU_UD_8BIT_MAX) { + log_error("string exceeds 8-bit data max length"); + return -1; + } + + nr_octets = pdu->user_data_len; + for (i = 0; i < pdu->user_data_len; i++) { + octets[i] = pdu->user_data[i]; + } + } + + if (len < nr_octets * 2 + HEX_BYTE_LEN + 1) { + log_error("buffer is not large enough to hold user-data"); + return -1; + } + + tmp = hex_byte_encode(pdu_str, len, pdu->user_data_len); + ENCODE_FAIL(tmp != HEX_BYTE_LEN, "user-data-len", -1); + pdu_str += tmp; + len -= tmp; + + for (i = 0; i < nr_octets; i++) { + hex_byte_encode(pdu_str, len, octets[i]); + pdu_str += HEX_BYTE_LEN; + } + len -= nr_octets * 2; + pdu_str[i] = '\0'; + + log_debug("user-data-len: 0x%02X", pdu->user_data_len); + log_debug("nr_octets: 0x%02X", nr_octets); + + return pdu_str - begin; +} + +int pdu_encode(char *pdu_str, size_t len, struct pdu_info *pdu) +{ + char *begin = pdu_str; + char *msg_begin = pdu_str; + int tmp; + + tmp = pdu_encode_addr(pdu_str, len, &pdu->smsc_addr, 1); + ENCODE_FAIL(tmp < 0, "smsc-addr", -1); + len -= tmp; + pdu_str += tmp; + + msg_begin = pdu_str; + + tmp = hex_byte_encode(pdu_str, len, pdu->type.type); + ENCODE_FAIL(tmp != HEX_BYTE_LEN, "type", -1); + len -= tmp; + pdu_str += tmp; + + if (pdu->type.msg_type == PDU_MTI_SUBMIT || + pdu->type.msg_type == PDU_MTI_REPORT) { + tmp = hex_byte_encode(pdu_str, len, pdu->msg_reference); + ENCODE_FAIL(tmp != HEX_BYTE_LEN, "msg-reference", -1); + len -= tmp; + pdu_str += tmp; + } + + tmp = pdu_encode_addr(pdu_str, len, &pdu->addr, 0); + ENCODE_FAIL(tmp < 0, "addr", -1); + len -= tmp; + pdu_str += tmp; + + switch (pdu->type.msg_type) { + case PDU_MTI_REPORT: + tmp = pdu_encode_timestamp(pdu_str, len, &pdu->timestamp); + ENCODE_FAIL(tmp < 0, "report-timestamp", -1); + len -= tmp; + pdu_str += tmp; + + break; + case PDU_MTI_DELIVER: + case PDU_MTI_SUBMIT: + tmp = hex_byte_encode(pdu_str, len, pdu->protocol_id); + ENCODE_FAIL(tmp != HEX_BYTE_LEN, "protocol-id", -1); + len -= tmp; + pdu_str += tmp; + + tmp = hex_byte_encode(pdu_str, len, pdu->data_coding.data_coding); + ENCODE_FAIL(tmp != HEX_BYTE_LEN, "data-coding-scheme", -1); + len -= tmp; + pdu_str += tmp; + + if (pdu->type.msg_type == PDU_MTI_SUBMIT) { + switch (pdu->type.validity_period_format) { + case PDU_VPF_RELATIVE: + tmp = hex_byte_encode(pdu_str, len, pdu->validity_period); + ENCODE_FAIL(tmp != HEX_BYTE_LEN, "validity-period", -1); + len -= tmp; + pdu_str += tmp; + + break; + + case PDU_VPF_ENHANCED: + log_warning("PDU_VPF_ENHANCED? Falling through to absolute"); + case PDU_VPF_ABSOUTE: + tmp = pdu_encode_timestamp(pdu_str, len, &pdu->timestamp); + ENCODE_FAIL(tmp < 0, "validity-period-timestamp", -1); + len -= tmp; + pdu_str += tmp; + + break; + + case PDU_VPF_NOT_PRESENT: + default: + break; + } + } else if (pdu->type.msg_type == PDU_MTI_DELIVER) { + tmp = pdu_encode_timestamp(pdu_str, len, &pdu->timestamp); + ENCODE_FAIL(tmp < 0, "delivery-timestamp", -1); + len -= tmp; + pdu_str += tmp; + } + + tmp = pdu_encode_user_data(pdu_str, len, pdu); + ENCODE_FAIL(tmp < 0, "user-data", -1); + len -= tmp; + pdu_str += tmp; + } + + pdu->msg_len = (pdu_str - msg_begin) / 2; + log_debug("msg-len: %d", pdu->msg_len); + + return pdu_str - begin; +} + diff --git a/src/pdu_encode.h b/src/pdu_encode.h new file mode 100644 index 0000000..246ea3d --- /dev/null +++ b/src/pdu_encode.h @@ -0,0 +1,12 @@ +#ifndef __PDU_ENCODE_H +#define __PDU_ENCODE_H + +#include "pdu.h" + +int pdu_encode_timestamp(char *pdu_str, size_t len, struct tm *tm); +int pdu_encode_addr(char *pdu_str, size_t len, struct pdu_addr *addr, int smsc); +int pdu_encode_user_data(char *pdu_str, size_t len, struct pdu_info *pdu); + +int pdu_encode(char *pdu_str, size_t len, struct pdu_info *pdu); + +#endif /* ~__PDU_ENCODE_H */ diff --git a/src/pdu_encoder.c b/src/pdu_encoder.c new file mode 100644 index 0000000..c2a046a --- /dev/null +++ b/src/pdu_encoder.c @@ -0,0 +1,167 @@ +/* + * PDU Encoder tool + * + * Copyright (C) 2010 by James Maki + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#define __MAIN_FILE_C 1 + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "global.h" +#include "cmd_options.h" +#include "utils.h" +#include "sms_utils.h" +#include "sms_send.h" +#include "sms_list.h" +#include "sms_delete.h" +#include "xprintf.h" +#include "pdu_encode.h" +#include "pdu_decode.h" + +static void print_version(const char *name) +{ + printf("%s (" PACKAGE ") " VERSION " (" __DATE__ " " __TIME__ ")\n", name); + printf("Copyright (C) 2010 by Multi-Tech Systems\n"); + printf( +"This program is free software; you may redistribute it under the terms of\n" +"the GNU General Public License version 2 or (at your option) any later version.\n" +"This program has absolutely no warranty.\n"); +} + +static void usage(FILE *out) +{ + fprintf(out, "Usage: pdu-encoder [ OPTIONS ... ] [ <recipient-addr> ]\n"); + fprintf(out, "where OPTIONS := { \n"); + fprintf(out, " --alphabet { seven-bit | eight-bit } |\n"); + fprintf(out, " -f, --file <input-file> (default is to read from stdin) |\n"); + fprintf(out, " --smsc-addr <smsc-addr> |\n"); + fprintf(out, " --verbose\n"); + fprintf(out, " }\n"); + fprintf(out, "\n"); +} + +static char *short_options = + __CMD_OPT_FILE + __CMD_OPT_VERBOSE; +static struct option long_options[] = { + {"alphabet", 1, NULL, CMD_OPT_ALPHABET}, + {"file", 1, NULL, CMD_OPT_FILE}, + {"smsc-addr", 1, NULL, CMD_OPT_SMSC_ADDR}, + {"verbose", 0, NULL, CMD_OPT_VERBOSE}, + {"version", 0, NULL, CMD_OPT_VERSION}, + {"help", 0, NULL, CMD_OPT_HELP}, + {0, 0, 0, 0}, +}; + +int main(int argc, char *argv[]) +{ + int i; + int option_index; + int alphabet = PDU_ALPHABET_DEFAULT; + int in_fd = fileno(stdin); + char *smsc_addr = NULL; + + xprintf_init(); + + while ((i = getopt_long(argc, argv, short_options, long_options, &option_index)) >= 0) { + switch (i) { + case 0: + break; + + case CMD_OPT_ALPHABET: + if (!strcmp(optarg, "seven-bit")) { + alphabet = PDU_ALPHABET_DEFAULT; + } else if (!strcmp(optarg, "eight-bit")) { + alphabet = PDU_ALPHABET_EIGHT; + } + break; + + case CMD_OPT_FILE: + in_fd = open(optarg, O_RDONLY); + if (in_fd < 0) { + fprintf(stderr, "failed to open %s for reading\n", optarg); + return 1; + } + break; + + case CMD_OPT_SMSC_ADDR: + smsc_addr = optarg; + break; + + case CMD_OPT_VERBOSE: + Global.core.verbose = true; + break; + + case CMD_OPT_VERSION: + print_version("pdu-encoder"); + exit(0); + break; + + case CMD_OPT_HELP: + usage(stdout); + exit(0); + break; + + default: + usage(stderr); + exit(1); + } + } + + argc -= optind; + argv += optind; + + struct pdu_info pdu; + char pdu_str[PDU_BUFFER_SIZE]; + + memset(&pdu, 0, sizeof(pdu)); + + pdu.type.msg_type = PDU_MTI_SUBMIT; + pdu.type.validity_period_format = PDU_VPF_RELATIVE; + pdu.validity_period = PDU_VPF_RELATIVE_2DAYS; + pdu.data_coding.general.alphabet = alphabet; + + if (smsc_addr) { + pdu_addr_fill(&pdu.smsc_addr, smsc_addr, SMS_ADDR_UNSPEC); + } + + if (argc) { + pdu_addr_fill(&pdu.addr, *argv, SMS_ADDR_UNSPEC); + argc++; + argv--; + } + + pdu_user_data_read(in_fd, &pdu); + + pdu_encode(pdu_str, sizeof(pdu_str), &pdu); + + printf("pdu: %s\n", pdu_str); + + return 0; +} diff --git a/src/phonebook.c b/src/phonebook.c new file mode 100644 index 0000000..80fbc0e --- /dev/null +++ b/src/phonebook.c @@ -0,0 +1,325 @@ +/* + * Phonebook + * + * Copyright (C) 2010 by Multi-Tech Systems + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> + +#include "global.h" +#include "cmd_options.h" +#include "phonebook.h" +#include "utils.h" +#include "atcmd.h" + +void phonebook_entry_free(struct phonebook_entry *entry) +{ + free(entry); +} + +struct phonebook_entry *phonebook_entry_alloc(void) +{ + return malloc(sizeof(struct phonebook_entry)); +} + +struct phonebook_entry *phonebook_find_by_name(struct list_head *head, + const char *name) +{ + struct phonebook_entry *entry; + + list_for_each_entry(entry, head, list) { + if (!strcmp(entry->name, name)) { + return entry; + } + } + + return NULL; +} + +struct phonebook_entry *phonebook_search_names_i(struct list_head *head, + const char *name) +{ + int tmp; + struct phonebook_entry *entry; + int matches_found = 0; + + list_for_each_entry(entry, head, list) { + if (strstr(entry->name, name)) { + matches_found++; + + tmp = user_yesno(USER_YESNO_YES, "Select contact '%s' (%s)", + entry->name, entry->addr); + if (tmp == USER_YESNO_YES) { + return entry; + } + } + } + + if (!matches_found) { + log_notice("no matches for %s found in phonebook", name); + } + + return NULL; +} + +#define CPBR_HEADER_START "+CPBR: " + +static int process_header(struct phonebook_list_info *list_info, char *buf, size_t len) +{ + char *save = buf; + char *token; + + struct phonebook_entry *entry; + int index; + char *addr; + int type; + char *name; + + token = atcmd_response_brk(&save); + if (!token) { + log_debug("response tokenizing failed at start"); + return -1; + } + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("response tokenizing failed at index"); + return -2; + } + index = atoi(token); + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("response tokenizing failed at addr"); + return -3; + } + addr = token; + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("response tokenizing failed at type"); + return -4; + } + type = atoi(token); + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("response tokenizing failed at name"); + return -4; + } + name = token; + + entry = phonebook_entry_alloc(); + if (!entry) { + log_error("out of memory"); + return -6; + } + + memset(entry, 0, sizeof(*entry)); + + entry->index = index; + snprintf(entry->addr, sizeof(entry->addr), "%s", addr); + entry->type = type; + snprintf(entry->name, sizeof(entry->name), "%s", name); + + list_add_tail(&entry->list, &list_info->entry_list); + + list_info->nr_entries++; + + return 0; +} + +static int print_list_yaml(struct phonebook_list_info *list_info) +{ + struct phonebook_entry *entry; + int indent; + + indent = 0; + + indentf(indent, "---\n"); + indentf(indent, "\n"); + + indentf(indent, "contacts:\n"); + + list_for_each_entry(entry, &list_info->entry_list, list) { + indent = YAML_INDENT; + + indentf(indent, "- index: %d\n", entry->index); + indent += YAML_INDENT; + + indentf(indent, "addr: %s\n", entry->addr); + indentf(indent, "type: 0x%02X\n", entry->type); + indentf(indent, "name: %s\n", entry->name); + } + + indent = 0; + + if (Global.core.verbose) { + indentf(indent, "\n"); + + indentf(indent, "statistics:\n"); + indent += YAML_INDENT; + + indentf(indent, "total: %d\n", list_info->nr_entries); + indentf(indent, "\n"); + } + + indent = 0; + + indentf(indent, "...\n"); + + return 1; +} + +enum { + LIST_STATE_BLANK, + LIST_STATE_OPEN, + LIST_STATE_CLOSE, +}; + +static int list_info_callback(char *buf, size_t len, void *data) +{ + struct phonebook_list_info *list_info = (struct phonebook_list_info *) data; + + list_info->nr_lines++; + + switch (list_info->state) { + case LIST_STATE_BLANK: + log_debug("state: LIST_STATE_BLANK"); + + if (*buf) { + list_info->state = LIST_STATE_CLOSE; + log_debug("AT response error: %s", buf); + return -1; + } + + list_info->state = LIST_STATE_OPEN; + + return 0; + + case LIST_STATE_OPEN: + log_debug("state: LIST_STATE_OPEN"); + + if (!strncmp(buf, CPBR_HEADER_START, strlen(CPBR_HEADER_START))) { + return process_header(list_info, buf, len); + } else if (!strcmp(buf, "OK")) { + list_info->state = LIST_STATE_CLOSE; + return 1; + } else if (*buf) { + list_info->state = LIST_STATE_CLOSE; + + log_debug("AT response error: %s", buf); + + return -1; + } else { + return 0; + } + + default: + return -1; + } + + return -1; +} + +void phonebook_list_free(struct phonebook_list_info *list_info) +{ + struct phonebook_entry *entry; + struct phonebook_entry *entry_save; + + list_for_each_entry_safe(entry, entry_save, &list_info->entry_list, list) { + list_del(&entry->list); + phonebook_entry_free(entry); + } +} + +int phonebook_list_get(int fd, struct phonebook_list_info *list_info) +{ + int tmp; + + INIT_LIST_HEAD(&list_info->entry_list); + + if (*list_info->store_select) { + tmp = atcmd_plus_cpbs_write(fd, list_info->store_select); + if (tmp < 0) { + log_error("failed to get selected store info"); + return -1; + } + } + tmp = atcmd_plus_cpbs_test(fd, &list_info->store.choices); + if (tmp < 0) { + log_error("failed to get selected store info"); + return -1; + } + tmp = atcmd_plus_cpbs_read(fd, &list_info->store.selected); + if (tmp < 0) { + log_error("failed to get selected store info"); + return -1; + } + tmp = atcmd_plus_cpbr_test(fd, &list_info->store); + if (tmp < 0) { + log_error("failed to get selected store info"); + return -1; + } + + atcmd_writeline(fd, "AT+CPBR=%d,%d", + list_info->store.index_min, list_info->store.index_max); + tmp = atcmd_response_foreach_line(fd, list_info_callback, list_info); + if (tmp < 0) { + log_error("listing failed"); + phonebook_list_free(list_info); + + return tmp; + } + + return 0; +} + +int print_phonebook_list(int fd, const char *store) +{ + int tmp; + struct phonebook_list_info list_info; + + memset(&list_info, 0, sizeof(list_info)); + + if (store) { + strncpy(list_info.store_select, store, STORE_NAME_LEN); + } + + tmp = phonebook_list_get(fd, &list_info); + if (tmp < 0) { + log_error("failed to get entry list"); + return false; + } + + print_list_yaml(&list_info); + + phonebook_list_free(&list_info); + + return true; +} diff --git a/src/phonebook.h b/src/phonebook.h new file mode 100644 index 0000000..3b4cf79 --- /dev/null +++ b/src/phonebook.h @@ -0,0 +1,41 @@ +#ifndef __PHONEBOOK_H +#define __PHONEBOOK_H + +#include "atcmd.h" +#include "list.h" + +#define PHONEBOOK_ADDR_SIZE 64 +#define PHONEBOOK_TEXT_SIZE 64 + +struct phonebook_entry { + int index; + char addr[PHONEBOOK_ADDR_SIZE]; + int type; + char name[PHONEBOOK_TEXT_SIZE]; + struct list_head list; +}; + +void phonebook_entry_free(struct phonebook_entry *entry); +struct phonebook_entry *phonebook_entry_alloc(void); +struct phonebook_entry *phonebook_find_by_name(struct list_head *head, + const char *name); +struct phonebook_entry *phonebook_search_names_i(struct list_head *head, + const char *name); + +struct phonebook_list_info { + struct phonebook_store store; + char store_select[STORE_NAME_SIZE]; + int state; + + int nr_lines; + int nr_entries; + + struct list_head entry_list; +}; + +void phonebook_list_free(struct phonebook_list_info *list_info); +int phonebook_list_get(int fd, struct phonebook_list_info *list_info); + +int print_phonebook_list(int fd, const char *store); + +#endif /* ~__PHONEBOOK_H */ diff --git a/src/sms.config.example b/src/sms.config.example new file mode 100644 index 0000000..96ee1b0 --- /dev/null +++ b/src/sms.config.example @@ -0,0 +1,28 @@ +user: + name: User Name + email: user@gmail.com + +core: + verbose: false + interactive: true + baud-rate: 115200 + read-timeout: 5000 + device: /dev/ttyS1 + #device: /dev/rfcomm0 + msg-store-read: MT + msg-store-send: MT + msg-store-new: MT + pb-store: ME + editor: vi + edit-file: "${HOME}/.smsmsg" + +smtp: + server: smtp.gmail.com + port: 587 + user: user@gmail.com + passwd: passwd + encryption: tls + +send-email: + domain: txt.att.net + diff --git a/src/sms_config.c b/src/sms_config.c new file mode 100644 index 0000000..79271a3 --- /dev/null +++ b/src/sms_config.c @@ -0,0 +1,361 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <termios.h> +#include <linux/limits.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +#include "global.h" + +#if HAVE_LIBYAML +#include <yaml.h> +#endif + +#include "atcmd.h" +#include "utils.h" +#include "log.h" + +#if HAVE_LIBYAML + +struct config_info { + yaml_parser_t parser; + char *filename; + FILE *file; + + yaml_event_t event; + + int map_level; + char *section; + char *key; + char *value; + + int complete; +}; + +static int boolean_value(const char *str) +{ + return !strcmp(str, "true") ? true : false; +} + +static int config_open(struct config_info *config, char *filename) +{ + int err; + + memset(config, 0, sizeof(*config)); + + config->filename = strdup(filename); + if (!config->filename) { + log_error("out of memory"); + } + + config->file = fopen(config->filename, "r"); + if (!config->file) { + log_error("open config %s: %m", config->filename); + return -1; + } + + err = yaml_parser_initialize(&config->parser); + if (!err) { + log_error("yaml_parser_initialize"); + fclose(config->file); + return -1; + } + + yaml_parser_set_input_file(&config->parser, config->file); + + config->complete = false; + + return 0; +} + +static int config_close(struct config_info *config) +{ + yaml_parser_delete(&config->parser); + fclose(config->file); + free(config->filename); + free(config->section); + free(config->key); + free(config->value); + + return 0; +} + +static int config_next_event(struct config_info *config) +{ + int err; + + err = yaml_parser_parse(&config->parser, &config->event); + if (!err) { + log_error("yaml parse error"); + return -1; + } + + return 0; +} + +static void config_delete_event(struct config_info *config) +{ + yaml_event_delete(&config->event); +} + +static int config_set_core_value(const char *key, const char *value) +{ + log_debug("try setting core.%s to %s", key, value); + + if (!strcmp("verbose", key)) { + Global.core.verbose = boolean_value(value); + } else if (!strcmp("interactive", key)) { + Global.core.interactive = boolean_value(value); + } else if (!strcmp("baud-rate", key)) { + Global.core.baud_rate = atoi(value); + Global.core.baud_rate = value_to_baud(Global.core.baud_rate); + } else if (!strcmp("read-timeout", key)) { + Global.core.read_timeout = atoi(value); + } else if (!strcmp("device", key)) { + Global.core.device = strdup(value); + } else if (!strcmp("msg-store-read", key)) { + Global.core.msg_store_read = strdup(value); + } else if (!strcmp("msg-store-send", key)) { + Global.core.msg_store_send = strdup(value); + } else if (!strcmp("msg-store-new", key)) { + Global.core.msg_store_new = strdup(value); + } else if (!strcmp("pb-store", key)) { + Global.core.pb_store = strdup(value); + } else if (!strcmp("editor", key)) { + Global.core.editor = strdup(value); + } else if (!strcmp("edit-file", key)) { + Global.core.edit_file = strdup(value); + } + + return 0; +} + +static int config_set_smtp_value(const char *key, const char *value) +{ + log_debug("try setting smtp.%s to '%s'", key, value); + + if (!strcmp("server", key)) { + Global.smtp.server = strdup(value); + } else if (!strcmp("port", key)) { + Global.smtp.port = atoi(value); + } else if (!strcmp("user", key)) { + Global.smtp.user = strdup(value); + } else if (!strcmp("passwd", key)) { + Global.smtp.passwd = strdup(value); + } else if (!strcmp("encryption", key)) { + Global.smtp.encryption = strdup(value); + } + + return 0; +} + +static int config_set_send_email_value(const char *key, const char *value) +{ + log_debug("try setting send-email.%s to %s", key, value); + + if (!strcmp("domain", key)) { + Global.send_email.domain = strdup(value); + } + + return 0; +} + +static int config_set_user_value(const char *key, const char *value) +{ + log_debug("try setting user.%s to %s", key, value); + + if (!strcmp("name", key)) { + Global.user.name = strdup(value); + } else if (!strcmp("email", key)) { + Global.user.email = strdup(value); + } + + return 0; +} + +static int config_handle_event(struct config_info *config) +{ + switch (config->event.type) { + case YAML_STREAM_START_EVENT: + return 0; + case YAML_STREAM_END_EVENT: + return 0; + case YAML_DOCUMENT_START_EVENT: + return 0; + case YAML_DOCUMENT_END_EVENT: + config->complete = true; + return 0; + + case YAML_SCALAR_EVENT: + if (config->map_level == 1) { + config->section = strdup((char *) config->event.data.scalar.value); + if (!config->section) { + log_error("out of memory"); + return -1; + } + log_debug("section: %s", config->section); + + return 0; + } else if (config->map_level != 2) { + log_debug("bad map level: %d", config->map_level); + return -1; + } + + if (!config->key) { + config->key = strdup((char *) config->event.data.scalar.value); + if (!config->key) { + log_error("out of memory"); + return -1; + } + + return 0; + } else if (!config->value) { + config->value = strdup((char *) config->event.data.scalar.value); + if (!config->value) { + log_error("out of memory"); + return -1; + } + } + + if (config->key && config->value) { + if (!strcmp(config->section, "core")) { + config_set_core_value(config->key, config->value); + } else if (!strcmp(config->section, "smtp")) { + config_set_smtp_value(config->key, config->value); + } else if (!strcmp(config->section, "send-email")) { + config_set_send_email_value(config->key, config->value); + } else if (!strcmp(config->section, "user")) { + config_set_user_value(config->key, config->value); + } + + free(config->key); + config->key = NULL; + + free(config->value); + config->value = NULL; + } + + return 0; + + case YAML_MAPPING_START_EVENT: + config->map_level++; + + return 0; + + case YAML_MAPPING_END_EVENT: + free(config->section); + config->section = NULL; + + free(config->key); + config->key = NULL; + + free(config->value); + config->value = NULL; + + config->map_level--; + + return 0; + + default: + log_error("event type: %d", config->event.type); + return -1; + } +} + +#define SMS_CONFIG_FILE ".smsconfig" +static int _sms_config_load(void) +{ + int err; + struct config_info config; + struct stat sbuf; + + err = stat(SMS_CONFIG_FILE, &sbuf); + if (err < 0) { + if (errno == ENOENT) { + log_notice("sms config file missing"); + return 0; + } + + return -1; + } + + err = config_open(&config, SMS_CONFIG_FILE); + if (err < 0) { + log_error("open config failed"); + return -1; + } + + while (1) { + err = config_next_event(&config); + if (err < 0) { + log_error("handle event failed"); + break; + } + + err = config_handle_event(&config); + if (err < 0) { + log_error("handle event failed"); + config_delete_event(&config); + break; + } + config_delete_event(&config); + + if (config.complete) { + break; + } + } + + int complete = config.complete; + + err = config_close(&config); + if (err < 0) { + log_error("close config failed"); + return -1; + } + + if (!complete) { + log_error("config load did not complete"); + return -1; + } + + log_debug("config loaded"); + + return 0; +} + +int sms_config_load(void) +{ + int err; + char *cp; + char prev[PATH_MAX]; + char *home; + + home = getenv("HOME"); + if (!home) { + log_error("HOME is not set"); + return -1; + } + + cp = getcwd(prev, sizeof(prev)); + if (!cp) { + log_error("getcwd %m"); + return -1; + } + + chdir(home); + + err = _sms_config_load(); + + chdir(prev); + + return err; +} +#else +int sms_config_load(void) +{ + return 0; +} +#endif diff --git a/src/sms_config.h b/src/sms_config.h new file mode 100644 index 0000000..0d82a4a --- /dev/null +++ b/src/sms_config.h @@ -0,0 +1,6 @@ +#ifndef __SMS_CONFIG_H +#define __SMS_CONFIG_H + +int sms_config_load(void); + +#endif /* ~__SMS_CONFIG_H */ diff --git a/src/sms_delete.c b/src/sms_delete.c new file mode 100644 index 0000000..bcef866 --- /dev/null +++ b/src/sms_delete.c @@ -0,0 +1,181 @@ +/* + * SMS Delete Messages + * + * Copyright (C) 2010 by Multi-Tech Systems + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "global.h" +#include "log.h" +#include "utils.h" +#include "cmd_options.h" +#include "sms_utils.h" +#include "sms_delete.h" +#include "sms_list.h" +#include "atcmd.h" + +static int do_delete(int fd, int argc, char **argv) +{ + int tmp; + char *arg; + struct msg_list_info list_info; + + memset(&list_info, 0, sizeof(list_info)); + + if (argc < 1) { + log_error("msg-status expected"); + return false; + } + arg = *argv; + argc--; argv++; + + if (!strcmp(arg, "unread")) { + list_info.status = SMS_MSG_REC_UNREAD; + list_info.cmd_type = CMD_TYPE_CMGL; + } else if (!strcmp(arg, "read")) { + list_info.status = SMS_MSG_REC_READ; + list_info.cmd_type = CMD_TYPE_CMGL; + } else if (!strcmp(arg, "unsent")) { + list_info.status = SMS_MSG_STO_UNSENT; + list_info.cmd_type = CMD_TYPE_CMGL; + } else if (!strcmp(arg, "sent")) { + list_info.status = SMS_MSG_STO_SENT; + list_info.cmd_type = CMD_TYPE_CMGL; + } else if (!strcmp(arg, "all")) { + list_info.status = SMS_MSG_ALL; + list_info.cmd_type = CMD_TYPE_CMGL; + } else if (!strcmp(arg, "index")) { + if (argc < 1) { + log_error("index expected"); + return false; + } + arg = *argv; + argc--; argv++; + + list_info.index = atoi(arg); + + list_info.status = SMS_MSG_ALL; + list_info.cmd_type = CMD_TYPE_CMGR; + } else { + log_error("invalid msg-status %s", arg); + return false; + } + + if (Global.core.verbose) { + printf("preparing for delete...\n"); + } + + tmp = atcmd_plus_cmgf_write(fd, SMS_PDU_MODE); + if (tmp < 0) { + log_error("atcmd_plus_cmgf_write failed"); + return false; + } + + tmp = sms_list_get(fd, &list_info); + if (tmp < 0) { + log_error("failed to get msg list"); + return false; + } + + struct sms_msg *msg; + int failed = 0; + + list_for_each_entry(msg, &list_info.msg_list, list) { + if (Global.core.verbose) { + printf("deleting message at index %d", msg->index); + } + + tmp = atcmd_plus_cmgd_write(fd, msg->index); + if (tmp < 0) { + printf("deleting message at index %d failed\n", msg->index); + failed++; + continue; + } + } + + sms_list_free(&list_info); + + return failed ? false : true; +} + +static char *short_options = ""; +static struct option long_options[] = { + {NULL, 0, NULL, 0}, +}; + +void sms_delete_help(FILE *out) { + fprintf(out, "usage: delete STATUS [ ARGUMENTS ]\n"); + fprintf(out, "where STATUS := { \n"); + fprintf(out, " index <index>\n"); + fprintf(out, " unread\n"); + fprintf(out, " read\n"); + fprintf(out, " unsent\n"); + fprintf(out, " sent\n"); + fprintf(out, " all\n"); + fprintf(out, " }\n"); + fprintf(out, "\n"); +} + +int sms_delete(int argc, char **argv) +{ + int i; + int option_index; + int ret; + int fd; + + while ((i = getopt_long(argc, argv, short_options, long_options, &option_index)) >= 0) { + switch (i) { + case 0: + break; + + default: + sms_delete_help(stderr); + return false; + } + } + + if (optind >= argc) { + sms_delete_help(stderr); + return false; + } + argc -= optind; + argv += optind; + + fd = tty_open(Global.core.device, Global.core.baud_rate); + if (fd < 0) { + fprintf(stderr, "failed to open tty device %s\n", Global.core.device); + return false; + } + atcmd_init(fd, Global.core.read_timeout); + + ret = do_delete(fd, argc, argv); + + tty_close(fd); + + return ret; +} diff --git a/src/sms_delete.h b/src/sms_delete.h new file mode 100644 index 0000000..ae7a53b --- /dev/null +++ b/src/sms_delete.h @@ -0,0 +1,7 @@ +#ifndef __SMS_DELETE_H +#define __SMS_DELETE_H + +void sms_delete_help(FILE *out); +int sms_delete(int argc, char **argv); + +#endif /* ~__SMS_DELETE_H */ diff --git a/src/sms_list.c b/src/sms_list.c new file mode 100644 index 0000000..0f9a9e6 --- /dev/null +++ b/src/sms_list.c @@ -0,0 +1,502 @@ +/* + * SMS List Messages + * + * Copyright (C) 2010 by Multi-Tech Systems + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "global.h" +#include "cmd_options.h" +#include "sms_utils.h" +#include "list.h" +#include "sms_list.h" +#include "pdu_decode.h" +#include "utils.h" +#include "atcmd.h" + +#define CMGL_HEADER_START "+CMGL: " +#define CMGR_HEADER_START "+CMGR: " + +static int process_header(struct msg_list_info *list_info, char *buf, size_t len) +{ + char *save = buf; + char *token; + + struct sms_msg *msg; + int index; + int status; + char *name; + int msg_len; + + token = atcmd_response_brk(&save); + if (!token) { + log_debug("response tokenizing failed at start"); + return -1; + } + + switch (list_info->cmd_type) { + case CMD_TYPE_CMGL: + token = atcmd_value_tok(&save); + if (!token) { + log_debug("response tokenizing failed at index"); + return -2; + } + index = atoi(token); + + break; + case CMD_TYPE_CMGR: + index = list_info->index; + break; + default: + return false; + } + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("response tokenizing failed at msg-status"); + return -3; + } + status = atoi(token); + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("response tokenizing failed at name"); + return -4; + } + name = token; + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("response tokenizing failed at msg-len"); + return -5; + } + msg_len = atoi(token); + + switch (status) { + case SMS_MSG_REC_UNREAD: + list_info->nr_unread++; + break; + case SMS_MSG_REC_READ: + list_info->nr_read++; + break; + case SMS_MSG_STO_UNSENT: + list_info->nr_unsent++; + break; + case SMS_MSG_STO_SENT: + list_info->nr_sent++; + break; + default: + log_warning("msg-status unknown"); + list_info->nr_unknown++; + } + + msg = sms_msg_alloc(); + if (!msg) { + log_error("out of memory"); + return -6; + } + + memset(msg, 0, sizeof(*msg)); + + msg->index = index; + msg->status = status; + snprintf(msg->name, sizeof(msg->name), "%s", name); + msg->len = msg_len; + + list_add_tail(&msg->list, &list_info->msg_list); + + list_info->nr_msgs++; + + return 0; +} + +static int print_list_yaml(struct msg_list_info *list_info) +{ + struct sms_msg *msg; + char buf[64]; + int indent; + + indent = 0; + + indentf(indent, "---\n"); + indentf(indent, "\n"); + + indentf(indent, "messages:\n"); + + list_for_each_entry(msg, &list_info->msg_list, list) { + indent = YAML_INDENT; + + indentf(indent, "- index: %d\n", msg->index); + indent += YAML_INDENT; + + indentf(indent, "message-status: %d\n", msg->status); + indentf(indent, "name: %s\n", msg->name); + if (Global.core.verbose) { + indentf(indent, "length: %d\n", msg->len); + } + + indentf(indent, "pdu:\n"); + indent += YAML_INDENT; + + if (Global.core.verbose) { + indentf(indent, "message-length: %d\n", msg->pdu.msg_len); + } + + if (Global.core.verbose) { + indentf(indent, "smsc-addr-length: 0x%02X\n", msg->pdu.smsc_addr.len); + } + indentf(indent, "smsc-addr-type: 0x%02X\n", msg->pdu.smsc_addr.type); + indentf(indent, "smsc-addr: %s\n", msg->pdu.smsc_addr.addr); + + indentf(indent, "type: 0x%02X\n", msg->pdu.type.type); + indentf(indent, "message-reference: 0x%02X\n", msg->pdu.msg_reference); + indentf(indent, "protocol-id: 0x%02X\n", msg->pdu.protocol_id); + + if (Global.core.verbose) { + indentf(indent, "addr-length: 0x%02X\n", msg->pdu.addr.len); + } + indentf(indent, "addr-type: 0x%02X\n", msg->pdu.addr.type); + indentf(indent, "addr: %s\n", msg->pdu.addr.addr); + + pdu_format_vp(&msg->pdu, buf, sizeof(buf)); + indentf(indent, "validity-period: %s\n", buf); + pdu_format_timestamp(&msg->pdu, buf, sizeof(buf), "%Y-%m-%d %T %z"); + indentf(indent, "timestamp: %s\n", buf); + + if (Global.core.verbose) { + indentf(indent, "user-data-length: 0x%02X\n", msg->pdu.user_data_len); + } + indentf(indent, "user-data: \"%.*J\"\n", msg->pdu.user_data_len, msg->pdu.user_data); + indentf(indent, "\n"); + } + + indent = 0; + + if (Global.core.verbose) { + indentf(indent, "\n"); + + indentf(indent, "statistics:\n"); + indent += YAML_INDENT; + + indentf(indent, "unread: %d\n", list_info->nr_unread); + indentf(indent, "read: %d\n", list_info->nr_read); + indentf(indent, "unsent: %d\n", list_info->nr_unsent); + indentf(indent, "sent: %d\n", list_info->nr_sent); + indentf(indent, "unknown: %d\n", list_info->nr_unknown); + indentf(indent, "total: %d\n", list_info->nr_msgs); + indentf(indent, "\n"); + } + + indent = 0; + + indentf(indent, "...\n"); + + return 1; +} + +enum { + LIST_STATE_BLANK, + LIST_STATE_OPEN, + LIST_STATE_PDU, + LIST_STATE_CLOSE, +}; + +static int list_info_callback(char *buf, size_t len, void *data) +{ + int err; + struct msg_list_info *list_info = (struct msg_list_info *) data; + struct sms_msg *msg; + + list_info->nr_lines++; + + switch (list_info->state) { + case LIST_STATE_BLANK: + log_debug("state: LIST_STATE_BLANK"); + + if (*buf) { + list_info->state = LIST_STATE_CLOSE; + log_debug("AT response error: %s", buf); + return -1; + } + + list_info->state = LIST_STATE_OPEN; + + return 0; + + case LIST_STATE_OPEN: + log_debug("state: LIST_STATE_OPEN"); + + if (!strncmp(buf, CMGL_HEADER_START, strlen(CMGL_HEADER_START)) || + !strncmp(buf, CMGR_HEADER_START, strlen(CMGR_HEADER_START))) { + list_info->state = LIST_STATE_PDU; + + return process_header(list_info, buf, len); + } else if (!strcmp(buf, "OK")) { + list_info->state = LIST_STATE_CLOSE; + return 1; + } else if (*buf) { + list_info->state = LIST_STATE_CLOSE; + + log_debug("AT response error: %s", buf); + + return -1; + } else { + return 0; + } + + case LIST_STATE_PDU: + log_debug("state: LIST_STATE_PDU"); + + if (list_empty(&list_info->msg_list)) { + log_debug("empty list"); + return -1; + } + + msg = list_entry(list_info->msg_list.prev, typeof(*msg), list); + if (msg->len > 0) { + err = pdu_decode(buf, &msg->pdu); + if (err < 0) { + log_warning("pdu decode failed: %d", err); + } + } + + list_info->state = LIST_STATE_OPEN; + + return 0; + + default: + return -1; + } + + return -1; +} + +void sms_list_free(struct msg_list_info *list_info) +{ + struct sms_msg *msg; + struct sms_msg *msg_save; + + list_for_each_entry_safe(msg, msg_save, &list_info->msg_list, list) { + list_del(&msg->list); + sms_msg_free(msg); + } +} + +int sms_list_get(int fd, struct msg_list_info *list_info) +{ + int tmp; + + INIT_LIST_HEAD(&list_info->msg_list); + + list_info->state = LIST_STATE_BLANK; + list_info->nr_lines = 0; + list_info->nr_unread = 0; + list_info->nr_read = 0; + list_info->nr_unsent = 0; + list_info->nr_sent = 0; + list_info->nr_unknown = 0; + list_info->nr_msgs = 0; + + tmp = atcmd_plus_cmgf_write(fd, SMS_PDU_MODE); + if (tmp < 0) { + log_error("setting pdu mode failed"); + return -1; + } + + switch (list_info->cmd_type) { + case CMD_TYPE_CMGL: + atcmd_writeline(fd, "AT+CMGL=%d", list_info->status); + break; + + case CMD_TYPE_CMGR: + atcmd_writeline(fd, "AT+CMGR=%d", list_info->index); + break; + + default: + return -1; + } + + tmp = atcmd_response_foreach_line(fd, list_info_callback, list_info); + if (tmp < 0) { + log_error("listing failed"); + sms_list_free(list_info); + + return tmp; + } + + return 0; +} + +static int do_list(int fd, int cmd_type, int argc, char **argv) +{ + int tmp; + char *arg; + struct msg_list_info list_info; + + memset(&list_info, 0, sizeof(list_info)); + + if (argc < 1) { + log_error("msg-status or index expected"); + return false; + } + arg = *argv; + argc--; argv++; + + list_info.cmd_type = cmd_type; + + switch (list_info.cmd_type) { + case CMD_TYPE_CMGL: + if (!strcmp(arg, "unread")) { + list_info.status = SMS_MSG_REC_UNREAD; + } else if (!strcmp(arg, "read")) { + list_info.status = SMS_MSG_REC_READ; + } else if (!strcmp(arg, "unsent")) { + list_info.status = SMS_MSG_STO_UNSENT; + } else if (!strcmp(arg, "sent")) { + list_info.status = SMS_MSG_STO_SENT; + } else if (!strcmp(arg, "all")) { + list_info.status = SMS_MSG_ALL; + } else { + log_error("invalid msg-status %s", arg); + return false; + } + + break; + + case CMD_TYPE_CMGR: + list_info.index = atoi(arg); + break; + + default: + return false; + } + + tmp = sms_list_get(fd, &list_info); + if (tmp < 0) { + log_error("failed to get msg list"); + return false; + } + + print_list_yaml(&list_info); + + sms_list_free(&list_info); + + return true; +} + +static char *short_options = ""; +static struct option long_options[] = { + {NULL, 0, NULL, 0}, +}; + +void sms_list_help(FILE *out) { + fprintf(out, "usage: list STATUS\n"); + fprintf(out, "where STATUS := { \n"); + fprintf(out, " unread\n"); + fprintf(out, " read\n"); + fprintf(out, " unsent\n"); + fprintf(out, " sent\n"); + fprintf(out, " all\n"); + fprintf(out, " }\n"); + fprintf(out, "\n"); +} + +void sms_read_help(FILE *out) { + fprintf(out, "usage: read <index>\n"); + fprintf(out, "\n"); +} + +static void help_select(FILE *out, int cmd_type) +{ + switch (cmd_type) { + case CMD_TYPE_CMGL: + sms_list_help(out); + break; + case CMD_TYPE_CMGR: + sms_read_help(out); + break; + default: + fprintf(out, "usage: ?\n"); + } +} + +int sms_list(int argc, char **argv) +{ + int i; + int option_index; + int ret; + int fd; + + if (argc < 1) { + log_debug("should have received at least one argument"); + return false; + } + + char *object = *argv; + int cmd_type; + + if (!tokcmp(object, "list")) { + cmd_type = CMD_TYPE_CMGL; + } else if (!tokcmp(object, "read")) { + cmd_type = CMD_TYPE_CMGR; + } else { + log_debug("object should be one of { list | read }"); + return false; + } + + while ((i = getopt_long(argc, argv, short_options, long_options, &option_index)) >= 0) { + switch (i) { + case 0: + break; + + default: + help_select(stderr, cmd_type); + return false; + } + } + + if (optind >= argc) { + help_select(stderr, cmd_type); + return false; + } + argc -= optind; + argv += optind; + + fd = tty_open(Global.core.device, Global.core.baud_rate); + if (fd < 0) { + fprintf(stderr, "failed to open tty device %s\n", Global.core.device); + return false; + } + atcmd_init(fd, Global.core.read_timeout); + + ret = do_list(fd, cmd_type, argc, argv); + + tty_close(fd); + + return ret; +} diff --git a/src/sms_list.h b/src/sms_list.h new file mode 100644 index 0000000..ae04d38 --- /dev/null +++ b/src/sms_list.h @@ -0,0 +1,32 @@ +#ifndef __SMS_LIST_H +#define __SMS_LIST_H + +enum { + CMD_TYPE_CMGL, + CMD_TYPE_CMGR, +}; + +struct msg_list_info { + int cmd_type; + int index; + int status; + int state; + + int nr_lines; + int nr_unread; + int nr_read; + int nr_unsent; + int nr_sent; + int nr_unknown; + int nr_msgs; + + struct list_head msg_list; +}; + +void sms_list_free(struct msg_list_info *list_info); +int sms_list_get(int fd, struct msg_list_info *list_info); + +void sms_list_help(FILE *out); +int sms_list(int argc, char **argv); + +#endif /* ~__SMS_LIST_H */ diff --git a/src/sms_main.c b/src/sms_main.c new file mode 100644 index 0000000..5dda451 --- /dev/null +++ b/src/sms_main.c @@ -0,0 +1,283 @@ +/* + * SMS Main + * + * Copyright (C) 2010 by Multi-Tech Systems + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#define __MAIN_FILE_C 1 + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <time.h> + +#include "global.h" +#include "cmd_options.h" +#include "sms_utils.h" +#include "sms_send.h" +#include "sms_send_email.h" +#include "sms_list.h" +#include "sms_delete.h" +#include "xprintf.h" +#include "utils.h" +#include "atcmd.h" +#include "sms_config.h" + +struct sms_cmd { + const char *object; + int (*call)(int argc, char **argv); +}; + +static int pb_list(int argc, char **argv) +{ + int fd; + int ret; + + fd = tty_open(Global.core.device, Global.core.baud_rate); + if (fd < 0) { + fprintf(stderr, "failed to open tty device %s\n", Global.core.device); + return false; + } + atcmd_init(fd, Global.core.read_timeout); + + ret = print_phonebook_list(fd, Global.core.pb_store); + + tty_close(fd); + + return ret; +} + +static struct sms_cmd top_cmds[] = { + { + .object = "send", + .call = sms_send, + }, +#if HAVE_LIBESMTP + { + .object = "send-email", + .call = sms_send_email, + }, +#endif + { + .object = "delete", + .call = sms_delete, + }, + { + .object = "list", + .call = sms_list, + }, + { + .object = "read", + .call = sms_list, + }, + { + .object = "pb-list", + .call = pb_list, + }, + { + .object = NULL, + .call = NULL, + }, +}; + +int sms_process_cmd(int argc, char **argv) { + int err; + const struct sms_cmd *cmd; + char *object; + + if (argc <= 0) { + log_notice("object expected"); + return false; + } + + object = *argv; + cmd = top_cmds; + + while (cmd->object) { + if (!tokcmp(object, cmd->object)) { + if (cmd->call) { + err = cmd->call(argc, argv); + return err; + } else { + log_notice("command does not have any actions"); + return false; + } + } else { + cmd++; + } + } + + log_notice("unknown object"); + + return false; +} + +static void print_version(const char *name) +{ + printf("%s (" PACKAGE ") " VERSION " (" __DATE__ " " __TIME__ ")\n", name); + printf("Copyright (C) 2010 by Multi-Tech Systems\n"); + printf( +"This program is free software; you may redistribute it under the terms of\n" +"the GNU General Public License version 2 or (at your option) any later version.\n" +"This program has absolutely no warranty.\n"); +} + +static void usage(FILE *out) +{ + fprintf(out, "Usage: sms [ OPTIONS ... ] { send | read | list | delete }\n"); + fprintf(out, "where OPTIONS := { \n"); + fprintf(out, " -d, --device <device> |\n"); + fprintf(out, " -b, --baud-rate <rate> |\n"); + fprintf(out, " -i, --interactive |\n"); + fprintf(out, " -n, --non-interactive |\n"); + fprintf(out, " --read-timeout <timeout> (default: 5000 milliseconds) |\n"); + fprintf(out, " --verbose\n"); + fprintf(out, " }\n"); + fprintf(out, "\n"); +} + +static char *short_options = "+" + __CMD_OPT_DEVICE + __CMD_OPT_BAUD_RATE + __CMD_OPT_INTERACTIVE + __CMD_OPT_NON_INTERACTIVE + __CMD_OPT_VERBOSE; +static struct option long_options[] = { + {"device", 1, NULL, CMD_OPT_DEVICE}, + {"baud-rate", 1, NULL, CMD_OPT_BAUD_RATE}, + {"read-timeout", 1, NULL, CMD_OPT_READ_TIMEOUT}, + {"msg-store-read", 1, NULL, CMD_OPT_MSG_STORE_READ}, + {"msg-store-send", 1, NULL, CMD_OPT_MSG_STORE_SEND}, + {"msg-store-new", 1, NULL, CMD_OPT_MSG_STORE_NEW}, + {"pb-store", 1, NULL, CMD_OPT_PHONEBOOK_STORE}, + {"interactive", 0, NULL, CMD_OPT_INTERACTIVE}, + {"non-interactive", 0, NULL, CMD_OPT_NON_INTERACTIVE}, + {"verbose", 0, NULL, CMD_OPT_VERBOSE}, + {"version", 0, NULL, CMD_OPT_VERSION}, + {"help", 0, NULL, CMD_OPT_HELP}, + {0, 0, 0, 0}, +}; + +int main(int argc, char *argv[]) +{ + int i; + int option_index; + int ret; + int err; + +#if CONFIG_USE_SYSLOG + openlog("sms-utils", LOG_NDELAY, LOG_FACILITY); +# if DEBUG + setlogmask(LOG_UPTO(LOG_DEBUG)); +# else + setlogmask(LOG_UPTO(LOG_INFO)); +# endif +#endif + + xprintf_init(); + + err = sms_config_load(); + if (err < 0) { + log_error("sms config load error"); + exit(1); + } + + while ((i = getopt_long(argc, argv, short_options, long_options, &option_index)) >= 0) { + switch (i) { + case 0: + break; + + case CMD_OPT_INTERACTIVE: + Global.core.interactive = true; + break; + + case CMD_OPT_NON_INTERACTIVE: + Global.core.interactive = false; + break; + + case CMD_OPT_VERBOSE: + Global.core.verbose = true; + break; + + case CMD_OPT_DEVICE: + Global.core.device = optarg; + break; + + case CMD_OPT_BAUD_RATE: + Global.core.baud_rate = atoi(optarg); + Global.core.baud_rate = value_to_baud(Global.core.baud_rate); + if (Global.core.baud_rate == (speed_t) -1) { + sms_send_help(stderr); + exit(1); + } + break; + + case CMD_OPT_READ_TIMEOUT: + Global.core.read_timeout = atoi(optarg); + break; + + case CMD_OPT_MSG_STORE_READ: + Global.core.msg_store_read = optarg; + break; + + case CMD_OPT_MSG_STORE_SEND: + Global.core.msg_store_send = optarg; + break; + + case CMD_OPT_MSG_STORE_NEW: + Global.core.msg_store_new = optarg; + break; + + case CMD_OPT_PHONEBOOK_STORE: + Global.core.pb_store = optarg; + break; + + case CMD_OPT_VERSION: + print_version("sms"); + exit(0); + break; + + case CMD_OPT_HELP: + usage(stdout); + exit(0); + break; + + default: + usage(stderr); + exit(1); + } + } + + if (optind >= argc) { + usage(stderr); + exit(1); + } + argc -= optind; + argv += optind; + + optind = 1; + + ret = sms_process_cmd(argc, argv); + + return ret ? 0 : 1; +} + diff --git a/src/sms_send.c b/src/sms_send.c new file mode 100644 index 0000000..3cae12e --- /dev/null +++ b/src/sms_send.c @@ -0,0 +1,392 @@ +/* + * SMS Send + * + * Copyright (C) 2010 by Multi-Tech Systems + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "global.h" +#include "utils.h" +#include "cmd_options.h" +#include "sms_utils.h" +#include "atcmd.h" +#include "sms_send.h" +#include "pdu_encode.h" +#include "phonebook.h" + +struct send_options { + char *file; + int alphabet; + const char *smsc_addr; + int cmgw_first; +}; + +static int auto_fill_smsc(int fd, struct pdu_addr *addr) +{ + char buf[ATCMD_LINE_SIZE]; + char *save; + char *token; + char *addr_str; + int type; + int tmp; + + atcmd_writeline(fd, "AT+CSCA?"); + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CSCA: "); + if (tmp <= 0) { + if (*buf) { + log_debug("expected +CPMS: but got %s", buf); + } else { + log_debug("expected +CPMS"); + } + return -1; + } + + save = buf; + token = atcmd_response_brk(&save); + if (!token) { + log_debug("response tokenizing failed"); + return -1; + } + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("addr token not present"); + return -1; + } + addr_str = token; + + token = atcmd_value_tok(&save); + if (!token) { + log_debug("addr-len token not present"); + return -1; + } + type = atoi(token); + + tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); + if (tmp <= 0) { + if (*buf) { + log_debug("expected OK but got %s", buf); + } else { + log_debug("expected OK"); + } + return -1; + } + + return pdu_addr_fill(addr, addr_str, type); +} + +static int fill_addr(struct phonebook_list_info *list_info, + struct pdu_addr *addr, const char *addr_str) +{ + struct phonebook_entry *entry; + int type; + int tmp; + + type = pdu_addr_type_infer(addr_str); + if (type != SMS_ADDR_TEXT) { + goto fill; + } + + log_debug("text addr found: searching phonebook for match"); + + if (Global.core.interactive) { + entry = phonebook_search_names_i(&list_info->entry_list, addr_str); + if (!entry) { + return -1; + } + } else { + entry = phonebook_find_by_name(&list_info->entry_list, addr_str); + if (!entry) { + log_notice("%s not found in phonebook", addr_str); + return -1; + } + } + addr_str = entry->addr; + +fill: + + tmp = pdu_addr_fill(addr, addr_str, SMS_ADDR_UNSPEC); + if (tmp < 0) { + log_debug("pdu fill failed with addr %s", addr_str); + return -1; + } + + return 0; +} + +static int fill_user_data(struct send_options *options, struct pdu_info *pdu) +{ + int fd; + int tmp; + + if (options->file) { + fd = open(options->file, O_RDONLY); + if (fd < 0) { + log_error("failed to open %s for reading", options->file); + return -1; + } + } else if (Global.core.interactive && Global.core.edit_file && Global.core.editor) { + tmp = systemf("%s %s", Global.core.editor, Global.core.edit_file); + if (tmp < 0 || !WIFEXITED(tmp) || WEXITSTATUS(tmp)) { + log_error("edit failed"); + return -1; + } + + char *path = shell_path_expand(Global.core.edit_file); + if (!path) { + return -1; + } + + fd = open(path, O_RDONLY); + if (fd < 0) { + log_error("failed to open %s for reading", path); + free(path); + return -1; + } + free(path); + } else { + fd = fileno(stdin); + } + + tmp = pdu_user_data_read(fd, pdu); + + close(fd); + + if (tmp < 0) { + log_error("read user data failed"); + return -1; + } + + return 0; +} + +static int do_send(int fd, struct send_options *options, int argc, char **argv) +{ + char buf[PDU_BUFFER_SIZE]; + struct pdu_info pdu; + struct phonebook_list_info list_info; + int failed = 0; + int mem_index = 0; + int tmp; + int i; + + if (argc < 1) { + log_error("at least one recipient addr is required"); + return false; + } + + memset(&pdu, 0, sizeof(pdu)); + + pdu.type.msg_type = PDU_MTI_SUBMIT; + pdu.type.validity_period_format = PDU_VPF_RELATIVE; + pdu.validity_period = PDU_VPF_RELATIVE_2DAYS; + pdu.data_coding.general.alphabet = options->alphabet; + + if (options->smsc_addr) { + tmp = pdu_addr_fill(&pdu.smsc_addr, options->smsc_addr, SMS_ADDR_UNSPEC); + if (tmp < 0) { + return false; + } + } + + tmp = fill_user_data(options, &pdu); + if (tmp < 0) { + log_error("read user data failed"); + return false; + } + + if (Global.core.verbose) { + printf("preparing for send...\n"); + } + + tmp = atcmd_plus_cmgf_write(fd, SMS_PDU_MODE); + if (tmp < 0) { + log_error("setting pdu mode failed"); + return false; + } + + if (options->cmgw_first) { + tmp = pdu_encode(buf, sizeof(buf), &pdu); + if (tmp < 0) { + log_error("pdu encode failed"); + return false; + } + + if (Global.core.verbose) { + printf("writing message to memory\n"); + } + + mem_index = atcmd_plus_cmgw_write(fd, buf, pdu.msg_len); + if (mem_index < 0) { + log_error("write message to memory failed"); + return false; + } + } + + memset(&list_info, 0, sizeof(list_info)); + + if (Global.core.pb_store) { + strncpy(list_info.store_select, Global.core.pb_store, STORE_NAME_LEN); + } + tmp = phonebook_list_get(fd, &list_info); + if (tmp < 0) { + log_error("failed to get phonebook list"); + return false; + } + + for (i = 0; i < argc; i++) { + if (Global.core.verbose) { + printf("sending message to %s\n", argv[i]); + } + + tmp = fill_addr(&list_info, &pdu.addr, argv[i]); + if (tmp < 0) { + printf("sending message to %s failed\n", argv[i]); + failed++; + continue; + } + + if (options->cmgw_first) { + tmp = atcmd_plus_cmss_write(fd, mem_index, + pdu.addr.addr, SMS_ADDR_UNSPEC); + if (tmp < 0) { + printf("sending message to %s failed\n", argv[i]); + failed++; + continue; + } + } else { + tmp = pdu_encode(buf, sizeof(buf), &pdu); + if (tmp < 0) { + printf("sending message to %s failed\n", argv[i]); + failed++; + continue; + } + + tmp = atcmd_plus_cmgs_write(fd, buf, pdu.msg_len); + if (tmp < 0) { + printf("sending message to %s failed\n", argv[i]); + failed++; + continue; + } + } + } + + if (!failed && Global.core.interactive && Global.core.edit_file) { + systemf("rm -f %s", Global.core.edit_file); + } + + phonebook_list_free(&list_info); + + return failed ? false : true; +} + +static char *short_options = __CMD_OPT_FILE; +static struct option long_options[] = { + {"alphabet", 1, NULL, CMD_OPT_ALPHABET}, + {"file", 1, NULL, CMD_OPT_FILE}, + {"smsc-addr", 1, NULL, CMD_OPT_SMSC_ADDR}, + {"cmgw-first", 0, NULL, CMD_OPT_CMGW_FIRST}, + {NULL, 0, NULL, 0}, +}; + +void sms_send_help(FILE *out) { + fprintf(out, "usage: send [ OPTIONS ... ] <number>\n"); + fprintf(out, "where OPTIONS := { \n"); + fprintf(out, " --alphabet { seven-bit | eight-bit } |\n"); + fprintf(out, " -f, --file <input-file> |\n"); + fprintf(out, " --smsc-addr <smsc-addr> |\n"); + fprintf(out, " --cmgw-first\n"); + fprintf(out, " }\n"); + fprintf(out, "\n"); +} + +int sms_send(int argc, char **argv) +{ + int i; + int option_index; + int ret; + int fd; + + struct send_options options; + options.alphabet = PDU_ALPHABET_DEFAULT; + options.file = NULL; + options.cmgw_first = false; + options.smsc_addr = NULL; + options.cmgw_first = false; + + while ((i = getopt_long(argc, argv, short_options, long_options, &option_index)) >= 0) { + switch (i) { + case 0: + break; + + case CMD_OPT_ALPHABET: + if (!strcmp(optarg, "seven-bit")) { + options.alphabet = PDU_ALPHABET_DEFAULT; + } else if (!strcmp(optarg, "eight-bit")) { + options.alphabet = PDU_ALPHABET_EIGHT; + } + break; + + case CMD_OPT_FILE: + options.file = optarg; + break; + + case CMD_OPT_SMSC_ADDR: + options.smsc_addr = optarg; + break; + + case CMD_OPT_CMGW_FIRST: + options.cmgw_first = true; + break; + + default: + sms_send_help(stderr); + return false; + } + } + + if (optind >= argc) { + sms_send_help(stderr); + return false; + } + argc -= optind; + argv += optind; + + fd = tty_open(Global.core.device, Global.core.baud_rate); + if (fd < 0) { + fprintf(stderr, "failed to open tty device %s\n", Global.core.device); + return false; + } + atcmd_init(fd, Global.core.read_timeout); + + ret = do_send(fd, &options, argc, argv); + + tty_close(fd); + + return ret; +} diff --git a/src/sms_send.h b/src/sms_send.h new file mode 100644 index 0000000..61714a7 --- /dev/null +++ b/src/sms_send.h @@ -0,0 +1,7 @@ +#ifndef __SMS_SEND_H +#define __SMS_SEND_H + +void sms_send_help(FILE *out); +int sms_send(int argc, char **argv); + +#endif /* ~__SMS_SEND_H */ diff --git a/src/sms_send_email.c b/src/sms_send_email.c new file mode 100644 index 0000000..90f9296 --- /dev/null +++ b/src/sms_send_email.c @@ -0,0 +1,488 @@ +/* + * Send SMS through email + * + * Copyright (C) 2010 by Multi-Tech Systems + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <linux/limits.h> +#include <string.h> +#include <ctype.h> + +#include "global.h" + +#if HAVE_LIBESMTP +#include <openssl/ssl.h> +#include <auth-client.h> +#include <libesmtp.h> +#endif + +#include "utils.h" +#include "cmd_options.h" +#include "sms_utils.h" +#include "atcmd.h" +#include "phonebook.h" + +struct send_options { + char *file; + char *subject; +}; + +#if HAVE_LIBESMTP +static FILE *open_message_file(struct send_options *options) +{ + FILE *fp; + int tmp; + + if (options->file) { + fp = fopen(options->file, "r"); + if (!fp) { + log_error("failed to open %s for reading", options->file); + return NULL; + } + } else if (Global.core.interactive && Global.core.edit_file && Global.core.editor) { + tmp = systemf("%s %s", Global.core.editor, Global.core.edit_file); + if (tmp < 0 || !WIFEXITED(tmp) || WEXITSTATUS(tmp)) { + log_error("edit failed"); + return NULL; + } + + char *path = shell_path_expand(Global.core.edit_file); + if (!path) { + return NULL; + } + + fp = fopen(path, "r"); + if (!fp) { + log_error("failed to open %s for reading", path); + free(path); + return NULL; + } + free(path); + } else { + fp = stdin; + } + + return fp; +} + +char *fgets_noecho(char *buf, size_t len, FILE *stream) +{ + int err; + struct termios params; + char *ret = NULL; + tcflag_t c_lflag; + + err = tcgetattr(fileno(stream), ¶ms); + if (err < 0) { + log_error("tcgetattr failed: %m"); + return NULL; + } + + c_lflag = params.c_lflag; + + params.c_lflag &= ~ECHO; + params.c_lflag |= ECHONL; + + err = tcsetattr(fileno(stream), TCSAFLUSH, ¶ms); + if (err < 0) { + log_error("tcsetattr failed: %m"); + return NULL; + } + + ret = fgets(buf, len, stream); + + params.c_lflag = c_lflag; + + err = tcsetattr(fileno(stream), TCSAFLUSH, ¶ms); + if (err < 0) { + log_error("tcsetattr failed: %m"); + return NULL; + } + + return ret; +} + +struct auth_callback_data { + char user[128]; + char passwd[128]; + char realm[128]; +}; + +int auth_callback(auth_client_request_t request, char **result, int fields, void *arg) +{ + struct auth_callback_data *data = (struct auth_callback_data *) arg; + char *cp; + int i; + + for (i = 0; i < fields; i++) { + if (request[i].flags & AUTH_PASS) { + if (!Global.smtp.passwd) { + printf("Password: "); + fflush(stdout); + + cp = fgets_noecho(data->passwd, + sizeof(data->passwd), stdin); + if (!cp) { + *data->passwd = '\0'; + } + + cp = strrchr(data->passwd, '\n'); + if (cp) { + *cp = '\0'; + } + + result[i] = data->passwd; + } else { + result[i] = Global.smtp.passwd; + } + } else if (request[i].flags & AUTH_USER) { + if (!Global.smtp.user) { + printf("Username: "); + fflush(stdout); + + cp = fgets(data->user, sizeof(data->user), stdin); + if (!cp) { + *data->user = '\0'; + } + + cp = strrchr(data->user, '\n'); + if (cp) { + *cp = '\0'; + } + + result[i] = data->user; + } else { + result[i] = Global.smtp.user; + } + } else if (request[i].flags & AUTH_REALM) { + printf("Realm: "); + fflush(stdout); + + cp = fgets(data->realm, sizeof(data->realm), stdin); + if (!cp) { + *data->realm = '\0'; + } + + cp = strrchr(data->realm, '\n'); + if (cp) { + *cp = '\0'; + } + + result[i] = data->realm; + } else { + result[i] = NULL; + } + } + + return 1; +} + +int tls_callback(char *buf, int len, int flag, void *arg) +{ + char *cp; + + printf("Certificate Password: "); + fflush(stdout); + + cp = fgets_noecho(buf, len, stdin); + if (!cp) { + *buf = '\0'; + } + cp = strrchr(buf, '\n'); + if (cp) { + *cp = '\0'; + } + + return strlen(buf); +} + +void event_callback(smtp_session_t session, int event, void *args, ...) +{ + va_list ap; + long long_arg; + int *ok; + + va_start(ap, args); + + switch (event) { + case SMTP_EV_CONNECT: + case SMTP_EV_MAILSTATUS: + case SMTP_EV_RCPTSTATUS: + case SMTP_EV_MESSAGEDATA: + case SMTP_EV_MESSAGESENT: + case SMTP_EV_DISCONNECT: + break; + case SMTP_EV_WEAK_CIPHER: + long_arg = va_arg(ap, long); + log_debug("SMTP_EV_WEAK_CIPHER: bits: %ld", long_arg); + ok = va_arg(ap, int *); + *ok = 1; + break; + + case SMTP_EV_STARTTLS_OK: + log_debug("SMTP_EV_STARTTLS_OK"); + break; + + case SMTP_EV_INVALID_PEER_CERTIFICATE: + long_arg = va_arg(ap, long); + log_debug("SMTP_EV_INVALID_PEER_CERTIFICATE: error: %ld", long_arg); + ok = va_arg(ap, int *); + *ok = 1; + break; + + case SMTP_EV_NO_PEER_CERTIFICATE: + log_debug("SMTP_EV_NO_PEER_CERTIFICATE"); + ok = va_arg(ap, int *); + *ok = 1; + break; + + case SMTP_EV_WRONG_PEER_CERTIFICATE: + log_debug("SMTP_EV_WRONG_PEER_CERTIFICATE"); + ok = va_arg(ap, int *); + *ok = 1; + break; + + case SMTP_EV_NO_CLIENT_CERTIFICATE: + log_debug("SMTP_EV_NO_CLIENT_CERTIFICATE"); + ok = va_arg(ap, int *); + *ok = 1; + break; + + default: + log_debug("unknown event %d", event); + } + + va_end(ap); +} + +static void print_recipient_status(smtp_recipient_t recipient, + const char *mailbox, void *arg) +{ + const smtp_status_t *status; + + status = smtp_recipient_status(recipient); + + printf(" - addr: %s\n", mailbox); + printf(" code: %d\n", status->code); + printf(" message: %s\n", status->text); +} + +static int do_send_email(struct send_options *options, int argc, char **argv) +{ + char buf[1024]; + struct sigaction sa; + struct auth_callback_data auth_arg; + FILE *fp = NULL; + int ret = false; + int tmp; + int i; + + smtp_session_t session = NULL; + smtp_message_t message; + auth_context_t authctx = NULL; + const smtp_status_t *status; + + auth_client_init(); + + if (argc < 1) { + log_error("at least one recipient addr is required"); + goto done; + } + + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGPIPE, &sa, NULL); + + if (!Global.smtp.server) { + log_error("smtp config missing"); + goto done; + } + + session = smtp_create_session(); + message = smtp_add_message(session); + + if (Global.smtp.encryption && !strcmp(Global.smtp.encryption, "tls")) { + smtp_starttls_enable(session, Starttls_REQUIRED); + } + + if (strchr(Global.smtp.server, ':') || !Global.smtp.port) { + snprintf(buf, sizeof(buf), "%s", Global.smtp.server); + } else { + snprintf(buf, sizeof(buf), "%s:%d", + Global.smtp.server, Global.smtp.port); + } + smtp_set_server(session, buf); + + authctx = auth_create_context(); + if (!authctx) { + log_error("auth_create_context failed"); + goto done; + } + auth_set_mechanism_flags(authctx, AUTH_PLUGIN_PLAIN, 0); + auth_set_interact_cb(authctx, auth_callback, &auth_arg); + smtp_starttls_set_password_cb(tls_callback, NULL); + smtp_set_eventcb(session, event_callback, NULL); + smtp_auth_set_context(session, authctx); + + if (Global.user.email) { + smtp_set_reverse_path(message, Global.user.email); + smtp_set_header(message, "From", Global.user.name, + Global.user.email); + } + if (options->subject) { + smtp_set_header(message, "Subject", options->subject); + } + + fp = open_message_file(options); + if (!fp) { + log_error("opening sms message file failed"); + goto done; + } + + tmp = smtp_set_message_fp(message, fp); + + int nr_rcpts = 0; + for (i = 0; i < argc; i++) { + if (strchr(argv[i], '@')) { + smtp_add_recipient(message, argv[i]); + nr_rcpts++; + } else if (Global.send_email.domain) { + snprintf(buf, sizeof(buf), "%s@%s", + argv[i], Global.send_email.domain); + smtp_add_recipient(message, buf); + nr_rcpts++; + } else { + log_notice("skipping recipient %s", argv[i]); + } + } + + if (!nr_rcpts) { + log_error("email contains no recipients"); + goto done; + } + + tmp = smtp_start_session(session); + if (!tmp) { + smtp_strerror(smtp_errno(), buf, sizeof(buf)); + log_error("smtp_start_session: %s", buf); + goto done; + } + + status = smtp_message_transfer_status(message); + printf("transfer-status:\n"); + printf(" - code: %d\n", status->code); + printf(" message: %s\n", status->text ?: ""); + printf("recipient-status:\n"); + + smtp_enumerate_recipients(message, print_recipient_status, NULL); + + if (status->code == 250 && Global.core.interactive && Global.core.edit_file) { + systemf("rm -f %s", Global.core.edit_file); + } + + ret = true; + +done: + if (authctx) { + auth_destroy_context(authctx); + } + if (session) { + smtp_destroy_session(session); + } + if (fp) { + fclose(fp); + } + + auth_client_exit(); + + return ret; +} +#else +static int do_send_email(struct send_options *options, int argc, char **argv) +{ + return false; +} +#endif + +static char *short_options = __CMD_OPT_FILE; +static struct option long_options[] = { + {"file", 1, NULL, CMD_OPT_FILE}, + {"subject", 1, NULL, CMD_OPT_SUBJECT}, + {NULL, 0, NULL, 0}, +}; + +void sms_send_email_help(FILE *out) { + fprintf(out, "usage: send-email [ OPTIONS ... ] <number>\n"); + fprintf(out, "where OPTIONS := { \n"); + fprintf(out, " -f, --file <input-file> |\n"); + fprintf(out, " --subject <email-subject>\n"); + fprintf(out, " }\n"); + fprintf(out, "\n"); +} + +int sms_send_email(int argc, char **argv) +{ + int i; + int option_index; + int ret; + + struct send_options options; + options.file = NULL; + options.subject = NULL; + + while ((i = getopt_long(argc, argv, short_options, long_options, &option_index)) >= 0) { + switch (i) { + case 0: + break; + + case CMD_OPT_FILE: + options.file = optarg; + break; + + case CMD_OPT_SUBJECT: + options.subject = optarg; + break; + + default: + sms_send_email_help(stderr); + return false; + } + } + + if (optind >= argc) { + sms_send_email_help(stderr); + return false; + } + argc -= optind; + argv += optind; + + ret = do_send_email(&options, argc, argv); + + return ret; +} diff --git a/src/sms_send_email.h b/src/sms_send_email.h new file mode 100644 index 0000000..e60dfd1 --- /dev/null +++ b/src/sms_send_email.h @@ -0,0 +1,7 @@ +#ifndef __SMS_SEND_EMAIL_H +#define __SMS_SEND_EMAIL_H + +void sms_send_email_help(FILE *out); +int sms_send_email(int argc, char **argv); + +#endif /* ~__SMS_SEND_EMAIL_H */ diff --git a/src/sms_utils.c b/src/sms_utils.c new file mode 100644 index 0000000..b4228d0 --- /dev/null +++ b/src/sms_utils.c @@ -0,0 +1,91 @@ +/* + * SMS Utilities + * + * Copyright (C) 2010 by Multi-Tech Systems + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#define _GNU_SOURCE +#define __SMS_UTILS_C 1 + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "global.h" +#include "utils.h" +#include "sms_utils.h" + +static const struct { + const char *name; + int value; +} __msg_status_map[] = { + {"REC UNREAD", SMS_MSG_REC_UNREAD}, + {"REC READ", SMS_MSG_REC_READ}, + {"STO UNSENT", SMS_MSG_STO_UNSENT}, + {"STO SENT", SMS_MSG_STO_SENT}, + {"ALL", SMS_MSG_ALL}, +}; + +int msg_status_name_to_value(const char *name) +{ + int n = ARRAY_SIZE(__msg_status_map); + int i; + + for (i = 0; i < n; ++i) { + if (!strcmp(__msg_status_map[i].name, name)) { + return __msg_status_map[i].value; + } + } + + log_warning("message status is not valid: %s", name); + + return -1; +} + +const char *msg_status_value_to_name(int value) +{ + int n = ARRAY_SIZE(__msg_status_map); + int i; + + for (i = 0; i < n; ++i) { + if (value == __msg_status_map[i].value) { + return __msg_status_map[i].name; + } + } + + log_warning("message status is not valid: %d", value); + + return NULL; +} + +void sms_msg_free(struct sms_msg *msg) +{ + free(msg); +} + +struct sms_msg *sms_msg_alloc(void) +{ + return malloc(sizeof(struct sms_msg)); +} diff --git a/src/sms_utils.h b/src/sms_utils.h new file mode 100644 index 0000000..01d0e1f --- /dev/null +++ b/src/sms_utils.h @@ -0,0 +1,57 @@ +#ifndef __SMS_UTILS_H +#define __SMS_UTILS_H + +#include <stdarg.h> + +#include "pdu.h" +#include "list.h" +#include "phonebook.h" + +#ifdef __SMS_UTILS_C +#define SMS_UTILS_EXTERN +#else +#define SMS_UTILS_EXTERN extern +#endif + +#define SMS_PDU_MODE 0 +#define SMS_TEXT_MODE 1 + +#define SMS_ADDR_UNSPEC 0x00 +#define SMS_ADDR_LOCAL 0x81 +#define SMS_ADDR_GLOBAL 0x91 +#define SMS_ADDR_NATIONAL 0xA1 +#define SMS_ADDR_TEXT 0xD1 +#define SMS_ADDR_CMD 0xFF + +enum { + SMS_MSG_REC_UNREAD = 0, + SMS_MSG_REC_READ = 1, + SMS_MSG_STO_UNSENT = 2, + SMS_MSG_STO_SENT = 3, + SMS_MSG_ALL = 4, +}; + +enum { + SMS_DELETE_INDEX = 0, + SMS_DELETE_READ = 1, + SMS_DELETE_READSENT = 2, + SMS_DELETE_READSTO = 3, + SMS_DELETE_ALL = 4, +}; + +int msg_status_name_to_value(const char *name); +const char *msg_status_value_to_name(int value); + +struct sms_msg { + int index; + int status; + char name[PHONEBOOK_TEXT_SIZE]; + int len; + struct pdu_info pdu; + struct list_head list; +}; + +void sms_msg_free(struct sms_msg *msg); +struct sms_msg *sms_msg_alloc(void); + +#endif /* ~__SMS_UTILS_H */ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..9149319 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,236 @@ +/* + * Utilities + * + * Copyright (C) 2010 by Multi-Tech Systems + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <linux/limits.h> + +#include "utils.h" +#include "log.h" + +int tokcmp(const char *cmd, const char *pattern) +{ + int len = strlen(cmd); + + if (len > strlen(pattern)) { + return -1; + } + + return memcmp(pattern, cmd, len); +} + +char *strrstrip(char *str) +{ + char *end; + char *prev; + + if (!str || !*str) { + return NULL; + } + prev = end = str + strlen(str); + + while (end > str && isspace(*(end - 1))) { + end--; + } + *end = '\0'; + + return prev == end ? NULL : str; +} + +ssize_t safe_read(int fd, void *buf, size_t count) +{ + ssize_t n; + + do { + n = read(fd, buf, count); + } while (n < 0 && errno == EINTR); + + return n; +} + +ssize_t safe_readn(int fd, void *buf, size_t len) +{ + ssize_t cc; + ssize_t total; + + total = 0; + + while (len) { + cc = safe_read(fd, buf, len); + + if (cc < 0) { + if (total) { + return total; + } + return cc; + } + + total += cc; + buf = buf + cc; + len -= cc; + } + + return total; +} + +ssize_t safe_write(int fd, const void *buf, size_t count) +{ + ssize_t n; + + do { + n = write(fd, buf, count); + } while (n < 0 && errno == EINTR); + + return n; +} + +ssize_t full_write(int fd, const void *buf, size_t len) +{ + ssize_t cc; + ssize_t total; + + total = 0; + + while (len) { + cc = safe_write(fd, buf, len); + + if (cc < 0) { + if (total) { + return total; + } + return cc; + } + + total += cc; + buf = ((const char *)buf) + cc; + len -= cc; + } + + return total; +} + +int user_yesno(int def, const char *fmt, ...) +{ + char buf[128]; + char *line; + va_list ap; + + while (1) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + printf("? [%c/%c] ", + def == USER_YESNO_YES ? 'Y' : 'y', + def == USER_YESNO_NO ? 'N' : 'n'); + + line = fgets(buf, sizeof(buf), stdin); + if (!line) { + return -1; + } + + switch (toupper(*line)) { + case '\n': + return def; + case 'Y': + return USER_YESNO_YES; + case 'N': + return USER_YESNO_NO; + } + } + + return -1; +} + +int systemf(const char *fmt, ...) +{ + int err; + va_list ap; + char *buf; + + va_start(ap, fmt); + err = vasprintf(&buf, fmt, ap); + va_end(ap); + if (err < 0) { + log_error("out of memory"); + return -1; + } + + err = system(buf); + free(buf); + + return err; +} + +FILE *popenf(const char *mode, const char *fmt, ...) +{ + int err; + va_list ap; + char *buf; + FILE *pipe; + + va_start(ap, fmt); + err = vasprintf(&buf, fmt, ap); + va_end(ap); + if (err < 0) { + log_error("out of memory"); + return NULL; + } + + pipe = popen(buf, mode); + free(buf); + + return pipe; +} + +char *shell_path_expand(const char *path) +{ + char buf[PATH_MAX + 1]; + FILE *file; + char *line; + + file = popenf("r", "echo -n \"%s\"", path); + if (!file) { + log_error("popen failed"); + return NULL; + } + + line = fgets(buf, sizeof(buf), file); + + pclose(file); + + if (!line) { + log_error("no line read"); + return NULL; + } + + return strdup(line); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..3254b3c --- /dev/null +++ b/src/utils.h @@ -0,0 +1,42 @@ +#ifndef __UTILS_H +#define __UTILS_H + +#include <stdarg.h> +#include <sys/types.h> +#include <sys/wait.h> + +#define BIT(nr) (1UL << (nr)) + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) +#define __STRINGIFY(x...) #x +#define STRINGIFY(x...) __STRINGIFY(x) + +enum { + false = 0, + true = 1, +}; + +int tokcmp(const char *cmd, const char *pattern); +char *strrstrip(char *str); + +ssize_t safe_read(int fd, void *buf, size_t count); +ssize_t safe_readn(int fd, void *buf, size_t len); +ssize_t safe_write(int fd, const void *buf, size_t count); +ssize_t full_write(int fd, const void *buf, size_t len); + +enum { + USER_YESNO_NO = 0, + USER_YESNO_YES = 1, +}; + +int user_yesno(int def, const char *fmt, ...); +int systemf(const char *fmt, ...); +FILE *popenf(const char *mode, const char *fmt, ...); +char *shell_path_expand(const char *path); + +#define YAML_INDENT 2 + +#define indentf(n, format, args...) \ + printf("%*s" format , n, "" , ## args) + +#endif /* ~__UTILS_H */ diff --git a/src/xprintf.c b/src/xprintf.c new file mode 100644 index 0000000..8fbfab6 --- /dev/null +++ b/src/xprintf.c @@ -0,0 +1,217 @@ +/* + * extended printf functions and specifiers + * + * Copyright (C) 2010 by James Maki + * + * Author: James Maki <jmaki@multitech.com> + * + * 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 + * + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <alloca.h> +#include "xprintf.h" + +#include "log.h" + +#if HAVE_REGISTER_PRINTF_SPECIFIER +int print_buffer_inspect_arginfo(const struct printf_info *info, size_t n, int *argtypes, int *size) +#else +int print_buffer_inspect_arginfo(const struct printf_info *info, size_t n, int *argtypes) +#endif +{ + if (n > 0) { + argtypes[0] = PA_POINTER; +#if HAVE_REGISTER_PRINTF_SPECIFIER + size[0] = sizeof(void *); +#endif + } + return 1; +} + +static const char *__char_inspect[] = { + "\\x00", "\\x01", "\\x02", "\\x03", + "\\x04", "\\x05", "\\x06", "\\a", + "\\b", "\\t", "\\n", "\\v", + "\\f", "\\r", "\\x0E", "\\x0F", + "\\x10", "\\x11", "\\x12", "\\x13", + "\\x14", "\\x15", "\\x16", "\\x17", + "\\x18", "\\x19", "\\x1A", "\\x1B", + "\\x1C", "\\x1D", "\\x1E", "\\x1F", + " ", "!", "\\\"", "#", + "$", "%", "&", "'", + "(", ")", "*", "+", + ",", "-", ".", "/", + "0", "1", "2", "3", + "4", "5", "6", "7", + "8", "9", ":", ";", + "<", "=", ">", "?", + "@", "A", "B", "C", + "D", "E", "F", "G", + "H", "I", "J", "K", + "L", "M", "N", "O", + "P", "Q", "R", "S", + "T", "U", "V", "W", + "X", "Y", "Z", "[", + "\\\\", "]", "^", "_", + "`", "a", "b", "c", + "d", "e", "f", "g", + "h", "i", "j", "k", + "l", "m", "n", "o", + "p", "q", "r", "s", + "t", "u", "v", "w", + "x", "y", "z", "{", + "|", "}", "~", "\\x7F", + "\\x80", "\\x81", "\\x82", "\\x83", + "\\x84", "\\x85", "\\x86", "\\x87", + "\\x88", "\\x89", "\\x8A", "\\x8B", + "\\x8C", "\\x8D", "\\x8E", "\\x8F", + "\\x90", "\\x91", "\\x92", "\\x93", + "\\x94", "\\x95", "\\x96", "\\x97", + "\\x98", "\\x99", "\\x9A", "\\x9B", + "\\x9C", "\\x9D", "\\x9E", "\\x9F", + "\\xA0", "\\xA1", "\\xA2", "\\xA3", + "\\xA4", "\\xA5", "\\xA6", "\\xA7", + "\\xA8", "\\xA9", "\\xAA", "\\xAB", + "\\xAC", "\\xAD", "\\xAE", "\\xAF", + "\\xB0", "\\xB1", "\\xB2", "\\xB3", + "\\xB4", "\\xB5", "\\xB6", "\\xB7", + "\\xB8", "\\xB9", "\\xBA", "\\xBB", + "\\xBC", "\\xBD", "\\xBE", "\\xBF", + "\\xC0", "\\xC1", "\\xC2", "\\xC3", + "\\xC4", "\\xC5", "\\xC6", "\\xC7", + "\\xC8", "\\xC9", "\\xCA", "\\xCB", + "\\xCC", "\\xCD", "\\xCE", "\\xCF", + "\\xD0", "\\xD1", "\\xD2", "\\xD3", + "\\xD4", "\\xD5", "\\xD6", "\\xD7", + "\\xD8", "\\xD9", "\\xDA", "\\xDB", + "\\xDC", "\\xDD", "\\xDE", "\\xDF", + "\\xE0", "\\xE1", "\\xE2", "\\xE3", + "\\xE4", "\\xE5", "\\xE6", "\\xE7", + "\\xE8", "\\xE9", "\\xEA", "\\xEB", + "\\xEC", "\\xED", "\\xEE", "\\xEF", + "\\xF0", "\\xF1", "\\xF2", "\\xF3", + "\\xF4", "\\xF5", "\\xF6", "\\xF7", + "\\xF8", "\\xF9", "\\xFA", "\\xFB", + "\\xFC", "\\xFD", "\\xFE", "\\xFF", +}; + +int print_buffer_inspect(FILE *stream, const struct printf_info *info, const void *const *args) +{ + int i; + int total; + char *arg; + char *buf; + int prec = info->prec; + + arg = (char *) *((const char **) (args[0])); + + if (prec < 0) { + prec = strlen(arg); + } + + total = 0; + for (i = 0; i < prec; i++) { + total += strlen(__char_inspect[((unsigned char *) arg)[i]]); + } + + buf = alloca(total + 1); + if (!buf) { + return -1; + } + + *buf = '\0'; + for (i = 0; i < prec; i++) { + strcat(buf, __char_inspect[((unsigned char *) arg)[i]]); + } + + total = fprintf(stream, "%*s", (info->left ? -info->width : info->width), buf); + + return total; +} + +#if HAVE_REGISTER_PRINTF_SPECIFIER +int print_buffer_bin_arginfo(const struct printf_info *info, size_t n, int *argtypes, int *size) +#else +int print_buffer_bin_arginfo(const struct printf_info *info, size_t n, int *argtypes) +#endif +{ + if (n > 0) { + argtypes[0] = PA_POINTER; +#if HAVE_REGISTER_PRINTF_SPECIFIER + size[0] = sizeof(void *); +#endif + } + return 1; +} + +int print_buffer_bin(FILE *stream, const struct printf_info *info, const void *const *args) +{ + int i; + int j; + int total; + char *arg; + unsigned char c; + int prec = info->prec; + char *buf; + + arg = (char *) *((const char **) (args[0])); + + if (prec < 0) { + prec = strlen(arg); + } + + buf = alloca(prec * 8 + 1); + if (!buf) { + return -1; + } + + total = 0; + for (i = 0; i < prec; i++) { + c = ((unsigned char *) arg)[i]; + for (j = 7; j >= 0; j--) { + buf[total++] = (c & (1 << j)) ? '1' : '0'; + } + } + if (total) { + buf[total] = '\0'; + } + + total = fprintf(stream, "%*s", (info->left ? -info->width : info->width), buf); + + return total; +} + +int xprintf_init() +{ +#if HAVE_REGISTER_PRINTF_SPECIFIER + register_printf_specifier(XPRINTF_INSPECT_SPEC, print_buffer_inspect, + print_buffer_inspect_arginfo); + register_printf_specifier(XPRINTF_BIN_SPEC, print_buffer_bin, + print_buffer_bin_arginfo); +#else + register_printf_function(XPRINTF_INSPECT_SPEC, print_buffer_inspect, + print_buffer_inspect_arginfo); + register_printf_function(XPRINTF_BIN_SPEC, print_buffer_bin, + print_buffer_bin_arginfo); +#endif + + return 0; +} diff --git a/src/xprintf.h b/src/xprintf.h new file mode 100644 index 0000000..94342c6 --- /dev/null +++ b/src/xprintf.h @@ -0,0 +1,26 @@ +#ifndef __XPRINTF_H +#define __XPRINTF_H + +#include "config.h" +#include <printf.h> + +#define XPRINTF_INSPECT_SPEC 'J' +#define XPRINTF_BIN_SPEC 'K' + +#if HAVE_REGISTER_PRINTF_SPECIFIER +int print_buffer_inspect_arginfo(const struct printf_info *info, size_t n, int *argtypes, int *size); +#else +int print_buffer_inspect_arginfo(const struct printf_info *info, size_t n, int *argtypes); +#endif +int print_buffer_inspect(FILE *stream, const struct printf_info *info, const void *const *args); + +#if HAVE_REGISTER_PRINTF_SPECIFIER +int print_buffer_bin_arginfo(const struct printf_info *info, size_t n, int *argtypes, int *size); +#else +int print_buffer_bin_arginfo(const struct printf_info *info, size_t n, int *argtypes); +#endif +int print_buffer_bin(FILE *stream, const struct printf_info *info, const void *const *args); + +int xprintf_init(void); + +#endif /* ~__XPRINTF_H */ |