summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJames Maki <jmaki@multitech.com>2010-04-23 11:58:20 -0500
committerJames Maki <jmaki@multitech.com>2010-04-23 11:58:20 -0500
commit14fb44b17123b27e562379f51b75ee889982688d (patch)
tree3c2344f5c42396ab839638ee12f7c2f66a2656cb /src
downloadsms-utils-14fb44b17123b27e562379f51b75ee889982688d.tar.gz
sms-utils-14fb44b17123b27e562379f51b75ee889982688d.tar.bz2
sms-utils-14fb44b17123b27e562379f51b75ee889982688d.zip
initial commit
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am52
-rw-r--r--src/Makefile.in496
-rw-r--r--src/atcmd.c1087
-rw-r--r--src/atcmd.h125
-rw-r--r--src/cmd_options.h33
-rw-r--r--src/global.h92
-rw-r--r--src/list.h245
-rw-r--r--src/log.h47
-rw-r--r--src/pdu.c306
-rw-r--r--src/pdu.h132
-rw-r--r--src/pdu_decode.c340
-rw-r--r--src/pdu_decode.h12
-rw-r--r--src/pdu_decoder.c157
-rw-r--r--src/pdu_encode.c307
-rw-r--r--src/pdu_encode.h12
-rw-r--r--src/pdu_encoder.c167
-rw-r--r--src/phonebook.c325
-rw-r--r--src/phonebook.h41
-rw-r--r--src/sms.config.example28
-rw-r--r--src/sms_config.c361
-rw-r--r--src/sms_config.h6
-rw-r--r--src/sms_delete.c181
-rw-r--r--src/sms_delete.h7
-rw-r--r--src/sms_list.c502
-rw-r--r--src/sms_list.h32
-rw-r--r--src/sms_main.c283
-rw-r--r--src/sms_send.c392
-rw-r--r--src/sms_send.h7
-rw-r--r--src/sms_send_email.c488
-rw-r--r--src/sms_send_email.h7
-rw-r--r--src/sms_utils.c91
-rw-r--r--src/sms_utils.h57
-rw-r--r--src/utils.c236
-rw-r--r--src/utils.h42
-rw-r--r--src/xprintf.c217
-rw-r--r--src/xprintf.h26
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, &params);
+ if (err < 0) {
+ log_error("tcgetattr failed: %m");
+ return -1;
+ }
+
+ cfmakeraw(&params);
+ cfsetspeed(&params, baud_rate);
+
+ err = tcsetattr(fd, TCSANOW, &params);
+ 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, &params);
+ 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, &params);
+ 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), &params);
+ 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, &params);
+ 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, &params);
+ 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 */