/* * SMS Send * * Copyright (C) 2010 by Multi-Tech Systems * * Author: James Maki * * 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 #include #include #include #include #include #include #include #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"); } //LE910-SVG SMS SEND WORKAROUND if (is_telit_lte_vzw_3gpp2_format()) { log_info("setting text mode for Verizon LTE"); tmp = atcmd_plus_cmgf_write(fd, SMS_TEXT_MODE); if (tmp < 0) { log_error("failed to set text mode for sending with Verizon LTE."); return false; } } if (options->cmgw_first) { if (is_telit_3gpp2_format()) { log_debug("using CDMA pdu encoding for cmgw"); tmp = pdu_encode_cdma(buf, sizeof(buf), &pdu); } else { log_debug("using GSM pdu encoding for cmgw"); 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"); } //LE910-SVG SMS SEND WORKAROUND if (is_telit_lte_vzw_3gpp2_format()) { mem_index = atcmd_plus_cmgw_write_text(fd, NULL, SMS_ADDR_UNSPEC, NULL, pdu.user_data, pdu.user_data_len); } else { 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_warning("failed to get phonebook list"); memset(&list_info, 0, sizeof(list_info)); INIT_LIST_HEAD(&list_info.entry_list); } 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 { if (is_telit_3gpp2_format()) { log_debug("using CDMA pdu encoding for cmgs"); tmp = pdu_encode_cdma(buf, sizeof(buf), &pdu); } else { log_debug("using GSM pdu encoding for cmgs"); tmp = pdu_encode(buf, sizeof(buf), &pdu); } if (tmp < 0) { printf("sending message to %s failed\n", argv[i]); failed++; continue; } //LE910-SVG SMS SEND WORKAROUND if (is_telit_lte_vzw_3gpp2_format()) { tmp = atcmd_plus_cmgs_write_text(fd, pdu.addr.addr, pdu.user_data, pdu.user_data_len); } else { 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 ... ] \n"); fprintf(out, "where OPTIONS := { \n"); fprintf(out, " --alphabet { seven-bit | eight-bit } |\n"); fprintf(out, " -f, --file |\n"); fprintf(out, " --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 = sms_device_open(); if (fd < 0) { return false; } ret = do_send(fd, &options, argc, argv); sms_device_close(fd); return ret; }