/* * $Id: fifo_fnc.c 1351 2006-12-08 22:49:57Z bogdan_iancu $ * * Copyright (C) 2001-2003 FhG Fokus * Copyright (C) 2006 Voice Sistem SRL * * This file is part of openser, a free SIP server. * * openser 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. * * openser 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. * * * History: * --------- * 2006-09-25 first version (bogdan) */ #include #include #include #include #include #include #include #include #include #include "../../dprint.h" #include "../../ut.h" #include "../../mi/mi.h" #include "../../mem/mem.h" #include "../../mem/shm_mem.h" #include "mi_fifo.h" #include "fifo_fnc.h" #include "mi_parser.h" #include "mi_writer.h" static int mi_fifo_read = 0; static int mi_fifo_write = 0; static char *mi_buf = 0; static char *reply_fifo_s = 0; static int reply_fifo_len = 0; FILE* mi_init_fifo_server(char *fifo_name, int mi_fifo_mode, int mi_fifo_uid, int mi_fifo_gid, char* fifo_reply_dir) { FILE *fifo_stream; long opt; /* create FIFO ... */ if ((mkfifo(fifo_name, mi_fifo_mode)<0)) { LOG(L_ERR, "ERROR:mi_fifo:mi_init_fifo_server: can't create FIFO: " "%s (mode=%d)\n", strerror(errno), mi_fifo_mode); return 0; } DBG("DEBUG:mi_fifo:mi_init_fifo_server: FIFO created @ %s\n", fifo_name ); if ((chmod(fifo_name, mi_fifo_mode)<0)) { LOG(L_ERR, "ERROR:mi_fifo:mi_init_fifo_server: can't chmod FIFO: " "%s (mode=%d)\n", strerror(errno), mi_fifo_mode); return 0; } if ((mi_fifo_uid!=-1) || (mi_fifo_gid!=-1)){ if (chown(fifo_name, mi_fifo_uid, mi_fifo_gid)<0){ LOG(L_ERR, "ERROR:mi_fifo:mi_init_fifo_server: failed to change " "the owner/group for %s to %d.%d; %s[%d]\n", fifo_name, mi_fifo_uid, mi_fifo_gid, strerror(errno), errno); return 0; } } DBG("DEBUG:mi_fifo:mi_init_fifo_server: fifo %s opened, mode=%o\n", fifo_name, mi_fifo_mode ); /* open it non-blocking or else wait here until someone * opens it for writing */ mi_fifo_read=open(fifo_name, O_RDONLY|O_NONBLOCK, 0); if (mi_fifo_read<0) { LOG(L_ERR, "ERROR:mi_fifo:mi_init_fifo_server: mi_fifo_read did " "not open: %s\n", strerror(errno)); return 0; } fifo_stream = fdopen(mi_fifo_read, "r"); if (fifo_stream==NULL) { LOG(L_ERR, "ERROR:mi_fifo:mi_init_fifo_server: fdopen failed: %s\n", strerror(errno)); return 0; } /* make sure the read fifo will not close */ mi_fifo_write=open( fifo_name, O_WRONLY|O_NONBLOCK, 0); if (mi_fifo_write<0) { LOG(L_ERR, "ERROR:mi_fifo:mi_init_fifo_server: fifo_write did not " "open: %s\n", strerror(errno)); return 0; } /* set read fifo blocking mode */ if ((opt=fcntl(mi_fifo_read, F_GETFL))==-1){ LOG(L_ERR, "ERROR:mi_fifo:mi_init_fifo_server: fcntl(F_GETFL) " "failed: %s [%d]\n", strerror(errno), errno); return 0; } if (fcntl(mi_fifo_read, F_SETFL, opt & (~O_NONBLOCK))==-1){ LOG(L_ERR, "ERROR:mi_fifo:mi_init_fifo_server: fcntl(F_SETFL) " "failed: %s [%d]\n", strerror(errno), errno); return 0; } /* allocate all static buffers */ mi_buf = pkg_malloc(MAX_MI_FIFO_BUFFER); reply_fifo_s = pkg_malloc(MAX_MI_FILENAME); if ( mi_buf==NULL|| reply_fifo_s==NULL) { LOG(L_ERR,"ERROR:mi_fifo:mi_init_fifo_server: no more pkg memory\n"); return 0; } /* init fifo reply dir buffer */ reply_fifo_len = strlen(fifo_reply_dir); memcpy( reply_fifo_s, fifo_reply_dir, reply_fifo_len); return fifo_stream; } /* reply fifo security checks: * checks if fd is a fifo, is not hardlinked and it's not a softlink * opened file descriptor + file name (for soft link check) * returns 0 if ok, <0 if not */ static int mi_fifo_check(int fd, char* fname) { struct stat fst; struct stat lst; if (fstat(fd, &fst)<0){ LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_check: fstat failed: %s\n", strerror(errno)); return -1; } /* check if fifo */ if (!S_ISFIFO(fst.st_mode)){ LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_check: %s is not a fifo\n", fname); return -1; } /* check if hard-linked */ if (fst.st_nlink>1){ LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_check: security: fifo_check: " "%s is hard-linked %d times\n", fname, (unsigned)fst.st_nlink); return -1; } /* lstat to check for soft links */ if (lstat(fname, &lst)<0){ LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_check: lstat failed: %s\n", strerror(errno)); return -1; } if (S_ISLNK(lst.st_mode)){ LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_check: security: fifo_check: %s is " "a soft link\n", fname); return -1; } /* if this is not a symbolic link, check to see if the inode didn't * change to avoid possible sym.link, rm sym.link & replace w/ fifo race */ if ((lst.st_dev!=fst.st_dev)||(lst.st_ino!=fst.st_ino)){ LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_check: security: fifo_check: " "inode/dev number differ: %d %d (%s)\n", (int)fst.st_ino, (int)lst.st_ino, fname); return -1; } /* success */ return 0; } static FILE *mi_open_reply_pipe( char *pipe_name ) { int fifofd; FILE *file_handle; int flags; int retries=FIFO_REPLY_RETRIES; if (!pipe_name || *pipe_name==0) { DBG("DEBUG:mi_fifo:mi_open_reply_pipe: no file to write to " "about missing cmd\n"); return 0; } tryagain: /* open non-blocking to make sure that a broken client will not * block the FIFO server forever */ fifofd=open( pipe_name, O_WRONLY | O_NONBLOCK ); if (fifofd==-1) { /* retry several times if client is not yet ready for getting feedback via a reply pipe */ if (errno==ENXIO) { /* give up on the client - we can't afford server blocking */ if (retries==0) { LOG(L_ERR, "ERROR:mi_fifo:mi_open_reply_pipe: no client " "at %s\n",pipe_name ); return 0; } /* don't be noisy on the very first try */ if (retries!=FIFO_REPLY_RETRIES) DBG("DEBUG:mi_fifo:mi_open_reply_pipe: retry countdown: %d\n", retries ); sleep_us( FIFO_REPLY_WAIT ); retries--; goto tryagain; } /* some other opening error */ LOG(L_ERR, "ERROR:mi_fifo:mi_open_reply_pipe: open error (%s): %s\n", pipe_name, strerror(errno)); return 0; } /* security checks: is this really a fifo?, is * it hardlinked? is it a soft link? */ if (mi_fifo_check(fifofd, pipe_name)<0) goto error; /* we want server blocking for big writes */ if ( (flags=fcntl(fifofd, F_GETFL, 0))<0) { LOG(L_ERR, "ERROR:mi_fifo:mi_open_reply_pipe (%s): getfl failed: %s\n", pipe_name, strerror(errno)); goto error; } flags&=~O_NONBLOCK; if (fcntl(fifofd, F_SETFL, flags)<0) { LOG(L_ERR, "ERROR:mi_fifo:mi_open_reply_pipe (%s): setfl cntl " "failed: %s\n", pipe_name, strerror(errno)); goto error; } /* create an I/O stream */ file_handle=fdopen( fifofd, "w"); if (file_handle==NULL) { LOG(L_ERR, "ERROR:mi_fifo:mi_open_reply_pipe: open error (%s): %s\n", pipe_name, strerror(errno)); goto error; } return file_handle; error: close(fifofd); return 0; } int mi_read_line( char *b, int max, FILE *stream, int *read) { int retry_cnt; int len; retry_cnt=0; retry: if (fgets(b, max, stream)==NULL) { LOG(L_ERR, "ERROR:mi_fifo:mi_read_line: fifo_server fgets failed: " "%s\n", strerror(errno)); /* on Linux, fgets sometimes returns ESPIPE -- give it few more chances */ if (errno==ESPIPE) { retry_cnt++; if (retry_cnt<4) goto retry; } /* interrupted by signal or ... */ if ((errno==EINTR)||(errno==EAGAIN)) goto retry; kill(0, SIGTERM); } /* if we did not read whole line, our buffer is too small and we cannot process the request; consume the remainder of request */ len=strlen(b); if (len && !(b[len-1]=='\n' || b[len-1]=='\r')) { LOG(L_ERR, "ERROR:mi_fifo:mi_read_line: request line too long\n"); return -1; } *read = len; return 0; } static inline char *get_reply_filename( char * file, int len ) { if ( strchr(file,'.') || strchr(file,'/') || strchr(file, '\\') ) { LOG(L_ERR, "ERROR:mi_fifo:get_reply_filename: forbidden filename: " "%s\n", file); return 0; } if (reply_fifo_len + len + 1 > MAX_MI_FILENAME) { LOG(L_ERR, "ERROR:mi_fifo:get_reply_filename: reply fifoname " "too long %d\n",reply_fifo_len + len); return 0; } memcpy( reply_fifo_s+reply_fifo_len, file, len ); reply_fifo_s[reply_fifo_len+len]=0; return reply_fifo_s; } static inline void free_async_handler( struct mi_handler *hdl ) { if (hdl) shm_free(hdl); } static void fifo_close_async( struct mi_root *mi_rpl, struct mi_handler *hdl, int done) { FILE *reply_stream; char *name; name = (char*)hdl->param; if ( mi_rpl!=0 || done ) { /*open fifo reply*/ reply_stream = mi_open_reply_pipe( name ); if (reply_stream==NULL) { LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server: cannot open reply pipe " "%s\n", name ); return; } if (mi_rpl!=0) { mi_write_tree( reply_stream, mi_rpl); free_mi_tree( mi_rpl ); } else { mi_fifo_reply( reply_stream, "500 command failed\n"); } fclose(reply_stream); } if (done) free_async_handler( hdl ); return; } static inline struct mi_handler* build_async_handler( char *name, int len) { struct mi_handler *hdl; char *p; hdl = (struct mi_handler*)shm_malloc( sizeof(struct mi_handler) + len + 1); if (hdl==0) { LOG(L_ERR,"ERROR:mi_fifo:build_async_handler: no more shm mem\n"); return 0; } p = (char*)(hdl) + sizeof(struct mi_handler); memcpy( p, name, len+1 ); hdl->handler_f = fifo_close_async; hdl->param = (void*)p; return hdl; } #define mi_do_consume() \ do { \ DBG("DEBUG:mi_fifo:mi_fifo_server: entered consume\n"); \ /* consume the rest of the fifo request */ \ do { \ mi_read_line(mi_buf,MAX_MI_FIFO_BUFFER,fifo_stream,&line_len); \ } while(line_len>1); \ DBG("DEBUG:mi_fifo:mi_fifo_server: **** done consume\n"); \ } while(0) #define mi_open_reply(_name,_file,_err) \ do { \ _file = mi_open_reply_pipe( _name ); \ if (_file==NULL) { \ LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server: cannot open reply pipe "\ "%s\n", _name); \ goto _err; \ } \ } while(0) void mi_fifo_server(FILE *fifo_stream) { struct mi_root *mi_cmd; struct mi_root *mi_rpl; struct mi_handler *hdl; int line_len; char *file_sep, *command, *file; struct mi_cmd *f; FILE *reply_stream; while(1) { reply_stream = NULL; /* commands must look this way '::[filename]' */ if (mi_read_line(mi_buf,MAX_MI_FIFO_BUFFER,fifo_stream, &line_len)) { LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server: failed to read " "command\n"); goto consume1; } /* trim from right */ while(line_len) { if(mi_buf[line_len-1]=='\n' || mi_buf[line_len-1]=='\r' || mi_buf[line_len-1]==' ' || mi_buf[line_len-1]=='\t' ) { line_len--; mi_buf[line_len]=0; } else break; } if (line_len==0) { LOG(L_DBG, "INFO:mi_fifo:mi_fifo_server: command empty\n"); goto consume1; } if (line_len<3) { LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server: command must have " "at least 3 chars\n"); goto consume1; } if (*mi_buf!=MI_CMD_SEPARATOR) { LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server: command must begin " "with %c: %.*s\n", MI_CMD_SEPARATOR, line_len, mi_buf ); goto consume1; } command = mi_buf+1; file_sep=strchr(command, MI_CMD_SEPARATOR ); if (file_sep==NULL) { LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server: file separator " "missing\n"); goto consume1; } if (file_sep==command) { LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server: empty command\n"); goto consume1; } if (*(file_sep+1)==0) { file = NULL; } else { file = file_sep+1; file = get_reply_filename(file, mi_buf+line_len-file); if (file==NULL) { LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server: trimming " "filename\n"); goto consume1; } } /* make command zero-terminated */ *file_sep=0; f=lookup_mi_cmd( command, strlen(command) ); if (f==0) { LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server: command %s is not " "available\n", command); mi_open_reply( file, reply_stream, consume1); mi_fifo_reply( reply_stream, "500 command '%s' not available\n", command); goto consume2; } /* if asyncron cmd, build the async handler */ if (f->flags&MI_ASYNC_RPL_FLAG) { hdl = build_async_handler( file, strlen(file) ); if (hdl==0) { LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server: failed to build " "async handler\n"); mi_open_reply( file, reply_stream, consume1); mi_fifo_reply( reply_stream, "500 Internal server error\n"); goto consume2; } } else { hdl = 0; mi_open_reply( file, reply_stream, consume1); } if (f->flags&MI_NO_INPUT_FLAG) { mi_cmd = 0; mi_do_consume(); } else { mi_cmd = mi_parse_tree(fifo_stream); if (mi_cmd==NULL){ LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server:error parsing " "MI tree\n"); if (!reply_stream) mi_open_reply( file, reply_stream, consume3); mi_fifo_reply( reply_stream, "400 parse error in " "command '%s'\n", command); goto consume3; } mi_cmd->async_hdl = hdl; } DBG("DEBUG:mi_fifo:mi_fifo_server: done parsing the mi tree\n"); if ( (mi_rpl=run_mi_cmd(f, mi_cmd))==0 ){ if (!reply_stream) mi_open_reply( file, reply_stream, failure); mi_fifo_reply(reply_stream, "500 command '%s' failed\n", command); LOG(L_ERR, "ERROR:mi_fifo:mi_fifo_server: command (%s) " "processing failed\n", command ); } else if (mi_rpl!=MI_ROOT_ASYNC_RPL) { if (!reply_stream) mi_open_reply( file, reply_stream, failure); mi_write_tree( reply_stream, mi_rpl); free_mi_tree( mi_rpl ); } else { if (mi_cmd) free_mi_tree( mi_cmd ); continue; } free_async_handler(hdl); /* close reply fifo */ fclose(reply_stream); /* destroy request tree */ if (mi_cmd) free_mi_tree( mi_cmd ); continue; failure: free_async_handler(hdl); /* destroy request tree */ if (mi_cmd) free_mi_tree( mi_cmd ); /* destroy the reply tree */ if (mi_rpl) free_mi_tree(mi_rpl); continue; consume3: free_async_handler(hdl); if (reply_stream) consume2: fclose(reply_stream); consume1: mi_do_consume(); } }